From df1334b7ff02bfaa637ba2fe180c3a8ea966f6da Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 6 May 2025 12:39:50 -0400 Subject: [PATCH 001/460] Enable strict type checking in CI --- .github/workflows/typecheck.yml | 2 +- Rakefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 5b1b5e151..8f9119592 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -31,4 +31,4 @@ jobs: - name: Install gems run: bundle install - name: Typecheck self - run: bundle exec solargraph typecheck --level typed + run: bundle exec solargraph typecheck --level strict diff --git a/Rakefile b/Rakefile index 33b91bfa4..a7fea9b13 100755 --- a/Rakefile +++ b/Rakefile @@ -15,7 +15,7 @@ end desc "Run the type checker" task :typecheck do - sh "bundle exec solargraph typecheck --level typed" + sh "bundle exec solargraph typecheck --level strict" end desc "Run all tests" From 5bb5d418d0aa04c4716ad6b679220d90508523b4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 9 Jul 2025 15:33:52 -0400 Subject: [PATCH 002/460] Add some RBS shims to help handle strict-level typechecking issues --- .../parser/parser_gem/node_methods.rb | 14 ---------- rbs_collection.yaml | 6 ++-- sig/shims/ast/node.rbs | 5 ++++ sig/shims/open3/0/open3.rbs | 28 +++++++++++++++++++ sig/shims/rubygems/basic_specification.rbs | 3 ++ sig/shims/rubygems/dependency.rbs | 5 ++++ sig/shims/rubygems/errors.rbs | 17 +++++++++++ sig/shims/rubygems/spec_fetcher.rbs | 9 ++++++ sig/shims/rubygems/specification.rbs | 7 +++++ 9 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 sig/shims/ast/node.rbs create mode 100644 sig/shims/open3/0/open3.rbs create mode 100644 sig/shims/rubygems/basic_specification.rbs create mode 100644 sig/shims/rubygems/dependency.rbs create mode 100644 sig/shims/rubygems/errors.rbs create mode 100644 sig/shims/rubygems/spec_fetcher.rbs create mode 100644 sig/shims/rubygems/specification.rbs diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index b716b352d..cd9ce7728 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -3,20 +3,6 @@ require 'parser' require 'ast' -# Teach AST::Node#children about its generic type -# -# @todo contribute back to https://github.com/ruby/gem_rbs_collection/blob/main/gems/ast/2.4/ast.rbs -# -# @!parse -# module ::AST -# class Node -# # New children -# -# # @return [Array] -# attr_reader :children -# end -# end - # https://github.com/whitequark/parser module Solargraph module Parser diff --git a/rbs_collection.yaml b/rbs_collection.yaml index 66e30ecfe..551a475d6 100644 --- a/rbs_collection.yaml +++ b/rbs_collection.yaml @@ -6,9 +6,9 @@ sources: revision: main repo_dir: gems -# You can specify local directories as sources also. -# - type: local -# path: path/to/your/local/repository + # You can specify local directories as sources also. + - type: local + path: sig/shims # A directory to install the downloaded RBSs path: .gem_rbs_collection diff --git a/sig/shims/ast/node.rbs b/sig/shims/ast/node.rbs new file mode 100644 index 000000000..fab1a4de0 --- /dev/null +++ b/sig/shims/ast/node.rbs @@ -0,0 +1,5 @@ +module ::AST + class Node + def children: () -> [self, Integer, String, Symbol, nil] + end +end diff --git a/sig/shims/open3/0/open3.rbs b/sig/shims/open3/0/open3.rbs new file mode 100644 index 000000000..d1397e549 --- /dev/null +++ b/sig/shims/open3/0/open3.rbs @@ -0,0 +1,28 @@ +module Open3 + def self.capture2: (?Hash[String, String] env, *String cmds) -> [String, Process::Status] + + def self.capture2e: (?Hash[String, String] env, *String cmds) -> [String, Process::Status] + + def self.capture3: (?Hash[String, String] env, *String cmds) -> [String, String, Process::Status] + + def self.pipeline: (?Hash[String, String] env, *String cmds) -> Array[Process::Status] + + def self.pipeline_r: (?Hash[String, String] env, *String cmds) -> [IO, Process::Waiter] + + def self.pipeline_rw: (?Hash[String, String] env, *String cmds) -> [IO, IO, Process::Waiter] + + def self.pipeline_start: (?Hash[String, String] env, *String cmds) -> Array[Process::Waiter] + + def self.pipeline_w: (?Hash[String, String] env, *String cmds) -> [IO, Process::Waiter] + + def self.popen2: (?Hash[String, String] env, *String exe_path_or_cmd_with_args) -> [IO, IO, Process::Waiter] + | [U] (?Hash[String, String] env, *String exe_path_or_cmd_with_args) { (IO stdin, IO stdout, Process::Waiter wait_thread) -> U } -> U + + def self.popen2e: (?Hash[String, String] env, *String exe_path_or_cmd_with_args) -> [IO, IO, Process::Waiter] + | [U] (?Hash[String, String] env, *String exe_path_or_cmd_with_args) { (IO stdin, IO stdout_and_stderr, Process::Waiter wait_thread) -> U } -> U + + def self.popen3: (?Hash[String, String] env, *String exe_path_or_cmd_with_args) -> [IO, IO, IO, Process::Waiter] + | [U] (?Hash[String, String] env, *String exe_path_or_cmd_with_args) { (IO stdin, IO stdout, IO stderr, Process::Waiter wait_thread) -> U } -> U + + VERSION: ::String +end diff --git a/sig/shims/rubygems/basic_specification.rbs b/sig/shims/rubygems/basic_specification.rbs new file mode 100644 index 000000000..f254b1b36 --- /dev/null +++ b/sig/shims/rubygems/basic_specification.rbs @@ -0,0 +1,3 @@ +class Gem::BasicSpecification + def name: () -> String +end diff --git a/sig/shims/rubygems/dependency.rbs b/sig/shims/rubygems/dependency.rbs new file mode 100644 index 000000000..13f4549ca --- /dev/null +++ b/sig/shims/rubygems/dependency.rbs @@ -0,0 +1,5 @@ +class Gem::Dependency + # Version of the gem + # + def version: () -> untyped +end diff --git a/sig/shims/rubygems/errors.rbs b/sig/shims/rubygems/errors.rbs new file mode 100644 index 000000000..0f107d78c --- /dev/null +++ b/sig/shims/rubygems/errors.rbs @@ -0,0 +1,17 @@ +module Gem + class LoadError < ::LoadError + attr_accessor name: String + + attr_accessor requirement: untyped + end + + class MissingSpecError < Gem::LoadError + def initialize: (untyped name, untyped requirement, ?untyped? extra_message) -> void + + def message: () -> untyped + end + + class MissingSpecVersionError < MissingSpecError + def initialize: (untyped name, untyped requirement, untyped specs) -> void + end +end diff --git a/sig/shims/rubygems/spec_fetcher.rbs b/sig/shims/rubygems/spec_fetcher.rbs new file mode 100644 index 000000000..7a0297a98 --- /dev/null +++ b/sig/shims/rubygems/spec_fetcher.rbs @@ -0,0 +1,9 @@ +class Gem::SpecFetcher + include Gem::UserInteraction + + include Gem::Text + + def search_for_dependency: (untyped dependency, ?bool matching_platform) -> [::Array[[Gem::Dependency, untyped]], ::Array[untyped]] + + def spec_for_dependency: (untyped dependency, ?bool matching_platform) -> ::Array[untyped] +end diff --git a/sig/shims/rubygems/specification.rbs b/sig/shims/rubygems/specification.rbs new file mode 100644 index 000000000..3fb3c0386 --- /dev/null +++ b/sig/shims/rubygems/specification.rbs @@ -0,0 +1,7 @@ +class Gem::Specification < Gem::BasicSpecification + def self.find_by_name: (untyped name, *untyped requirements) -> instance + + def self.find_by_full_name: (untyped full_name) -> instance + + def self.find_by_path: (untyped path) -> instance +end From b4e09642932a6c765531e4c2d596c748bded417c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 12 Jul 2025 10:58:39 -0400 Subject: [PATCH 003/460] Add stdlib deps to gemspec - needed for rbs collection to notice Drop rubygems as rbs collection won't let us shim core --- rbs_collection.yaml | 2 +- sig/shims/ast/{ => 0}/node.rbs | 0 sig/shims/rubygems/basic_specification.rbs | 3 --- sig/shims/rubygems/dependency.rbs | 5 ----- sig/shims/rubygems/errors.rbs | 17 ----------------- sig/shims/rubygems/spec_fetcher.rbs | 9 --------- sig/shims/rubygems/specification.rbs | 7 ------- solargraph.gemspec | 2 ++ 8 files changed, 3 insertions(+), 42 deletions(-) rename sig/shims/ast/{ => 0}/node.rbs (100%) delete mode 100644 sig/shims/rubygems/basic_specification.rbs delete mode 100644 sig/shims/rubygems/dependency.rbs delete mode 100644 sig/shims/rubygems/errors.rbs delete mode 100644 sig/shims/rubygems/spec_fetcher.rbs delete mode 100644 sig/shims/rubygems/specification.rbs diff --git a/rbs_collection.yaml b/rbs_collection.yaml index 551a475d6..450dea132 100644 --- a/rbs_collection.yaml +++ b/rbs_collection.yaml @@ -6,8 +6,8 @@ sources: revision: main repo_dir: gems - # You can specify local directories as sources also. - type: local + name: shims path: sig/shims # A directory to install the downloaded RBSs diff --git a/sig/shims/ast/node.rbs b/sig/shims/ast/0/node.rbs similarity index 100% rename from sig/shims/ast/node.rbs rename to sig/shims/ast/0/node.rbs diff --git a/sig/shims/rubygems/basic_specification.rbs b/sig/shims/rubygems/basic_specification.rbs deleted file mode 100644 index f254b1b36..000000000 --- a/sig/shims/rubygems/basic_specification.rbs +++ /dev/null @@ -1,3 +0,0 @@ -class Gem::BasicSpecification - def name: () -> String -end diff --git a/sig/shims/rubygems/dependency.rbs b/sig/shims/rubygems/dependency.rbs deleted file mode 100644 index 13f4549ca..000000000 --- a/sig/shims/rubygems/dependency.rbs +++ /dev/null @@ -1,5 +0,0 @@ -class Gem::Dependency - # Version of the gem - # - def version: () -> untyped -end diff --git a/sig/shims/rubygems/errors.rbs b/sig/shims/rubygems/errors.rbs deleted file mode 100644 index 0f107d78c..000000000 --- a/sig/shims/rubygems/errors.rbs +++ /dev/null @@ -1,17 +0,0 @@ -module Gem - class LoadError < ::LoadError - attr_accessor name: String - - attr_accessor requirement: untyped - end - - class MissingSpecError < Gem::LoadError - def initialize: (untyped name, untyped requirement, ?untyped? extra_message) -> void - - def message: () -> untyped - end - - class MissingSpecVersionError < MissingSpecError - def initialize: (untyped name, untyped requirement, untyped specs) -> void - end -end diff --git a/sig/shims/rubygems/spec_fetcher.rbs b/sig/shims/rubygems/spec_fetcher.rbs deleted file mode 100644 index 7a0297a98..000000000 --- a/sig/shims/rubygems/spec_fetcher.rbs +++ /dev/null @@ -1,9 +0,0 @@ -class Gem::SpecFetcher - include Gem::UserInteraction - - include Gem::Text - - def search_for_dependency: (untyped dependency, ?bool matching_platform) -> [::Array[[Gem::Dependency, untyped]], ::Array[untyped]] - - def spec_for_dependency: (untyped dependency, ?bool matching_platform) -> ::Array[untyped] -end diff --git a/sig/shims/rubygems/specification.rbs b/sig/shims/rubygems/specification.rbs deleted file mode 100644 index 3fb3c0386..000000000 --- a/sig/shims/rubygems/specification.rbs +++ /dev/null @@ -1,7 +0,0 @@ -class Gem::Specification < Gem::BasicSpecification - def self.find_by_name: (untyped name, *untyped requirements) -> instance - - def self.find_by_full_name: (untyped full_name) -> instance - - def self.find_by_path: (untyped path) -> instance -end diff --git a/solargraph.gemspec b/solargraph.gemspec index 5008b6247..e400ed9f2 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -23,6 +23,7 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 3.0' + s.add_runtime_dependency 'ast', '~> 2.4.3' s.add_runtime_dependency 'backport', '~> 1.2' s.add_runtime_dependency 'benchmark', '~> 0.4' s.add_runtime_dependency 'bundler', '~> 2.0' @@ -33,6 +34,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'logger', '~> 1.6' s.add_runtime_dependency 'observer', '~> 0.1' s.add_runtime_dependency 'ostruct', '~> 0.6' + s.add_runtime_dependency 'open3', '~> 0.2.1' s.add_runtime_dependency 'parser', '~> 3.0' s.add_runtime_dependency 'prism', '~> 1.4' s.add_runtime_dependency 'rbs', '~> 3.3' From de058421585644b8c4429bed97e763e782777c57 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 13 Jul 2025 09:48:53 -0400 Subject: [PATCH 004/460] Prioritize local shims over rbs collection git repo shims --- rbs_collection.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rbs_collection.yaml b/rbs_collection.yaml index 450dea132..898239cac 100644 --- a/rbs_collection.yaml +++ b/rbs_collection.yaml @@ -1,15 +1,15 @@ # Download sources sources: + - type: local + name: shims + path: sig/shims + - type: git name: ruby/gem_rbs_collection remote: https://github.com/ruby/gem_rbs_collection.git revision: main repo_dir: gems - - type: local - name: shims - path: sig/shims - # A directory to install the downloaded RBSs path: .gem_rbs_collection From 8b1107501abeb326df1f8fecf3030f8f4b632b9e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 13 Jul 2025 12:44:19 -0400 Subject: [PATCH 005/460] Add 'solargraph method_pin' command for debugging ```sh $ SOLARGRAPH_ASSERTS=on bundle exec solargraph method_pin --rbs 'RuboCop::AST::ArrayNode#values' def values: () -> Array $ bundle exec solargraph help method_pin Usage: solargraph method_pin [PATH] Options: [--rbs], [--no-rbs], [--skip-rbs] # Output the pin as RBS # Default: false [--typify], [--no-typify], [--skip-typify] # Output the calculated return type of the pin from annotations # Default: false [--probe], [--no-probe], [--skip-probe] # Output the calculated return type of the pin from annotations and inference # Default: false [--stack], [--no-stack], [--skip-stack] # Show entire stack by including definitions in superclasses # Default: false Describe a method pin $ ``` --- lib/solargraph/shell.rb | 48 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 8f02f6ec9..938c31a11 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -239,6 +239,54 @@ def list puts "#{workspace.filenames.length} files total." end + desc 'method_pin [PATH]', 'Describe a method pin' + option :rbs, type: :boolean, desc: 'Output the pin as RBS', default: false + option :typify, type: :boolean, desc: 'Output the calculated return type of the pin from annotations', default: false + option :probe, type: :boolean, desc: 'Output the calculated return type of the pin from annotations and inference', default: false + option :stack, type: :boolean, desc: 'Show entire stack 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 method_pin path + api_map = Solargraph::ApiMap.load_with_cache('.', STDERR) + + pins = if options[:stack] + scope, ns, meth = if path.include? '#' + [:instance, *path.split('#', 2)] + else + [:class, *path.split('.', 2)] + end + api_map.get_method_stack(ns, meth, scope: scope) + else + api_map.get_path_pins path + end + if pins.empty? + STDERR.puts "Pin not found for path '#{path}'" + exit 1 + end + pins.each do |pin| + if options[:typify] || options[:probe] + type = ComplexType::UNDEFINED + if options[:typify] + type = pin.typify(api_map) + end + if options[:probe] && type.undefined? + type = pin.probe(api_map) + end + if options[:rbs] + puts type.to_rbs + else + puts type.rooted_tag + end + else + if options[:rbs] + puts pin.to_rbs + else + puts pin.inspect + end + end + end + end + private # @param pin [Solargraph::Pin::Base] From bf612959ec8b43b888dac18b4ee6823af5e6ffc5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 07:27:58 -0400 Subject: [PATCH 006/460] RuboCop and Solargraph fixes --- lib/solargraph/shell.rb | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 938c31a11..153e77f0e 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -272,18 +272,11 @@ def method_pin path if options[:probe] && type.undefined? type = pin.probe(api_map) end - if options[:rbs] - puts type.to_rbs - else - puts type.rooted_tag - end - else - if options[:rbs] - puts pin.to_rbs - else - puts pin.inspect - end + print_type(type) + next end + + print_pin(pin) end end @@ -312,5 +305,25 @@ 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) + 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) + if options[:rbs] + puts pin.to_rbs + else + puts pin.inspect + end + end end end From bd911392ea2af58cb8ce2c7e94fd64ccee2fb483 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 10:59:07 -0400 Subject: [PATCH 007/460] Add RBS fill for Bundler::Dsl Pending types for this class being added to the RBS repo and then to the RBS version the user has loaded, this will allow Gemfiles to be typechecked. --- lib/solargraph/pin_cache.rb | 8 +- lib/solargraph/rbs_map/core_map.rb | 18 ++- lib/solargraph/shell.rb | 4 +- rbs/fills/bundler/dsl.rbs | 200 +++++++++++++++++++++++++++++ rbs/fills/{ => tuple}/tuple.rbs | 0 spec/convention/gemfile_spec.rb | 21 +++ 6 files changed, 240 insertions(+), 11 deletions(-) create mode 100644 rbs/fills/bundler/dsl.rbs rename rbs/fills/{ => tuple}/tuple.rbs (100%) create mode 100644 spec/convention/gemfile_spec.rb diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 9013dd0d9..c78cb6088 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -110,8 +110,10 @@ def has_rbs_collection?(gemspec, hash) exist?(rbs_collection_path(gemspec, hash)) end - def uncache_core - uncache(core_path) + # @param out [IO, nil] + # @return [void] + def uncache_core(out: nil) + uncache(core_path, out: out) end def uncache_stdlib @@ -165,6 +167,8 @@ def uncache *path_segments, out: nil if File.exist?(path) FileUtils.rm_rf path, secure: true out.puts "Clearing pin cache in #{path}" unless out.nil? + else + out.puts "Pin cache file #{path} does not exist" unless out.nil? end end diff --git a/lib/solargraph/rbs_map/core_map.rb b/lib/solargraph/rbs_map/core_map.rb index 0d265d773..e80520982 100644 --- a/lib/solargraph/rbs_map/core_map.rb +++ b/lib/solargraph/rbs_map/core_map.rb @@ -5,12 +5,13 @@ class RbsMap # Ruby core pins # class CoreMap + include Logging def resolved? true end - FILLS_DIRECTORY = File.join(File.dirname(__FILE__), '..', '..', '..', 'rbs', 'fills') + FILLS_DIRECTORY = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'rbs', 'fills')) def initialize; end @@ -22,8 +23,15 @@ def pins if cache @pins.replace cache else - loader.add(path: Pathname(FILLS_DIRECTORY)) - @pins = conversions.pins + Dir.glob(File.join(FILLS_DIRECTORY, '*')).each do |path| + next unless File.directory?(path) + fill_loader = RBS::EnvironmentLoader.new(repository: RBS::Repository.new(no_stdlib: false)) + fill_loader.add(path: Pathname(path)) + fill_conversions = Conversions.new(loader: fill_loader) + @pins.concat fill_conversions.pins + rescue RBS::DuplicatedDeclarationError => e + logger.debug "RBS already contains declarations in #{path}, skipping: #{e.message}" + end @pins.concat RbsMap::CoreFills::ALL processed = ApiMap::Store.new(pins).pins.reject { |p| p.is_a?(Solargraph::Pin::Reference::Override) } @pins.replace processed @@ -33,10 +41,6 @@ def pins @pins end - def loader - @loader ||= RBS::EnvironmentLoader.new(repository: RBS::Repository.new(no_stdlib: false)) - end - private def loader diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 8f02f6ec9..f7539891b 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -118,12 +118,12 @@ def uncache *gems raise ArgumentError, 'No gems specified.' if gems.empty? gems.each do |gem| if gem == 'core' - PinCache.uncache_core + PinCache.uncache_core(out: $stdout) next end if gem == 'stdlib' - PinCache.uncache_stdlib + PinCache.uncache_stdlib(out: $stdout) next end diff --git a/rbs/fills/bundler/dsl.rbs b/rbs/fills/bundler/dsl.rbs new file mode 100644 index 000000000..82dbe2ac4 --- /dev/null +++ b/rbs/fills/bundler/dsl.rbs @@ -0,0 +1,200 @@ +# +# Bundler provides a consistent environment for Ruby projects by tracking and +# installing the exact gems and versions that are needed. +# +# Bundler is a part of Ruby's standard library. +# +# Bundler is used by creating *gemfiles* listing all the project dependencies +# and (optionally) their versions and then using +# +# require 'bundler/setup' +# +# or Bundler.setup to setup environment where only specified gems and their +# specified versions could be used. +# +# See [Bundler website](https://bundler.io/docs.html) for extensive +# documentation on gemfiles creation and Bundler usage. +# +# As a standard library inside project, Bundler could be used for introspection +# of loaded and required modules. +# +module Bundler + class Dsl + @source: untyped + + @sources: untyped + + @git_sources: untyped + + @dependencies: untyped + + @groups: untyped + + @install_conditionals: untyped + + @optional_groups: untyped + + @platforms: untyped + + @env: untyped + + @ruby_version: untyped + + @gemspecs: untyped + + @gemfile: untyped + + @gemfiles: untyped + + @valid_keys: untyped + + include RubyDsl + + def self.evaluate: (untyped gemfile, untyped lockfile, untyped unlock) -> untyped + + VALID_PLATFORMS: untyped + + VALID_KEYS: ::Array["group" | "groups" | "git" | "path" | "glob" | "name" | "branch" | "ref" | "tag" | "require" | "submodules" | "platform" | "platforms" | "source" | "install_if" | "force_ruby_platform"] + + GITHUB_PULL_REQUEST_URL: ::Regexp + + GITLAB_MERGE_REQUEST_URL: ::Regexp + + attr_reader gemspecs: untyped + + attr_reader gemfile: untyped + + attr_accessor dependencies: untyped + + def initialize: () -> void + + def eval_gemfile: (untyped gemfile, ?untyped? contents) -> untyped + + def gemspec: (?untyped? opts) -> void + + def gem: (untyped name, *untyped args) -> void + + def source: (untyped source, *untyped args) ?{ (?) -> untyped } -> void + + def git_source: (untyped name) ?{ (?) -> untyped } -> untyped + + def path: (untyped path, ?::Hash[untyped, untyped] options) ?{ (?) -> untyped } -> untyped + + def git: (untyped uri, ?::Hash[untyped, untyped] options) ?{ (?) -> untyped } -> untyped + + def github: (untyped repo, ?::Hash[untyped, untyped] options) ?{ () -> untyped } -> untyped + + def to_definition: (untyped lockfile, untyped unlock) -> untyped + + def group: (*untyped args) { () -> untyped } -> untyped + + def install_if: (*untyped args) { () -> untyped } -> untyped + + def platforms: (*untyped platforms) { () -> untyped } -> untyped + + alias platform platforms + + def env: (untyped name) { () -> untyped } -> untyped + + def plugin: (*untyped args) -> nil + + def method_missing: (untyped name, *untyped args) -> untyped + + def check_primary_source_safety: () -> untyped + + private + + def add_dependency: (untyped name, ?untyped? version, ?::Hash[untyped, untyped] options) -> (nil | untyped) + + def with_gemfile: (untyped gemfile) { (untyped) -> untyped } -> untyped + + def add_git_sources: () -> untyped + + def with_source: (untyped source) ?{ () -> untyped } -> untyped + + def normalize_hash: (untyped opts) -> untyped + + def valid_keys: () -> untyped + + def normalize_options: (untyped name, untyped version, untyped opts) -> untyped + + def normalize_group_options: (untyped opts, untyped groups) -> untyped + + def validate_keys: (untyped command, untyped opts, untyped valid_keys) -> (true | untyped) + + def normalize_source: (untyped source) -> untyped + + def deprecate_legacy_windows_platforms: (untyped platforms) -> (nil | untyped) + + def check_path_source_safety: () -> (nil | untyped) + + def check_rubygems_source_safety: () -> (untyped | nil) + + def multiple_global_source_warning: () -> untyped + + class DSLError < GemfileError + @status_code: untyped + + @description: untyped + + @dsl_path: untyped + + @backtrace: untyped + + @contents: untyped + + @to_s: untyped + + # @return [String] the description that should be presented to the user. + # + attr_reader description: untyped + + # @return [String] the path of the dsl file that raised the exception. + # + attr_reader dsl_path: untyped + + # @return [Exception] the backtrace of the exception raised by the + # evaluation of the dsl file. + # + attr_reader backtrace: untyped + + # @param [Exception] backtrace @see backtrace + # @param [String] dsl_path @see dsl_path + # + def initialize: (untyped description, untyped dsl_path, untyped backtrace, ?untyped? contents) -> void + + def status_code: () -> untyped + + # @return [String] the contents of the DSL that cause the exception to + # be raised. + # + def contents: () -> untyped + + # The message of the exception reports the content of podspec for the + # line that generated the original exception. + # + # @example Output + # + # Invalid podspec at `RestKit.podspec` - undefined method + # `exclude_header_search_paths=' for # + # + # from spec-repos/master/RestKit/0.9.3/RestKit.podspec:36 + # ------------------------------------------- + # # because it would break: #import + # > ns.exclude_header_search_paths = 'Code/RestKit.h' + # end + # ------------------------------------------- + # + # @return [String] the message of the exception. + # + def to_s: () -> untyped + + private + + def parse_line_number_from_description: () -> ::Array[untyped] + end + + def gemfile_root: () -> untyped + end +end diff --git a/rbs/fills/tuple.rbs b/rbs/fills/tuple/tuple.rbs similarity index 100% rename from rbs/fills/tuple.rbs rename to rbs/fills/tuple/tuple.rbs diff --git a/spec/convention/gemfile_spec.rb b/spec/convention/gemfile_spec.rb new file mode 100644 index 000000000..7cbb681a1 --- /dev/null +++ b/spec/convention/gemfile_spec.rb @@ -0,0 +1,21 @@ +describe Solargraph::Convention::Gemfile do + describe 'parsing Gemfiles' do + def type_checker(code) + Solargraph::TypeChecker.load_string(code, 'Gemfile', :strong) + end + + it 'typechecks valid files without error' do + checker = type_checker(%( + source 'https://rubygems.org' + + gemspec name: 'solargraph' + + # Local gemfile for development tools, etc. + local_gemfile = File.expand_path(".Gemfile", __dir__) + instance_eval File.read local_gemfile if File.exist? local_gemfile + )) + + expect(checker.problems).to be_empty + end + end +end From 8f443406e0f7e3d2d3677857fa32a8bdf48f0822 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 11:20:14 -0400 Subject: [PATCH 008/460] Ensure conversions.pins is added --- lib/solargraph/rbs_map/core_map.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/rbs_map/core_map.rb b/lib/solargraph/rbs_map/core_map.rb index e80520982..818d36404 100644 --- a/lib/solargraph/rbs_map/core_map.rb +++ b/lib/solargraph/rbs_map/core_map.rb @@ -23,6 +23,7 @@ def pins if cache @pins.replace cache else + @pins.concat conversions.pins Dir.glob(File.join(FILLS_DIRECTORY, '*')).each do |path| next unless File.directory?(path) fill_loader = RBS::EnvironmentLoader.new(repository: RBS::Repository.new(no_stdlib: false)) From 9e0fd38989dd795785c65c69850d0f856d0c7dae Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 12:08:36 -0400 Subject: [PATCH 009/460] Define some types in dsl.rbs --- rbs/fills/bundler/dsl.rbs | 16 ++++++++-------- spec/convention/gemfile_spec.rb | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/rbs/fills/bundler/dsl.rbs b/rbs/fills/bundler/dsl.rbs index 82dbe2ac4..d7c5b3442 100644 --- a/rbs/fills/bundler/dsl.rbs +++ b/rbs/fills/bundler/dsl.rbs @@ -70,11 +70,11 @@ module Bundler def eval_gemfile: (untyped gemfile, ?untyped? contents) -> untyped - def gemspec: (?untyped? opts) -> void + def gemspec: (?path: String, ?glob: String, ?name: String, ?development_group: Symbol) -> void def gem: (untyped name, *untyped args) -> void - def source: (untyped source, *untyped args) ?{ (?) -> untyped } -> void + def source: (String source, ?type: Symbol) ?{ (?) -> untyped } -> void def git_source: (untyped name) ?{ (?) -> untyped } -> untyped @@ -147,28 +147,28 @@ module Bundler # @return [String] the description that should be presented to the user. # - attr_reader description: untyped + attr_reader description: String # @return [String] the path of the dsl file that raised the exception. # - attr_reader dsl_path: untyped + attr_reader dsl_path: String # @return [Exception] the backtrace of the exception raised by the # evaluation of the dsl file. # - attr_reader backtrace: untyped + attr_reader backtrace: Exception # @param [Exception] backtrace @see backtrace # @param [String] dsl_path @see dsl_path # - def initialize: (untyped description, untyped dsl_path, untyped backtrace, ?untyped? contents) -> void + def initialize: (untyped description, String dsl_path, Exception backtrace, ?untyped? contents) -> void def status_code: () -> untyped # @return [String] the contents of the DSL that cause the exception to # be raised. # - def contents: () -> untyped + def contents: () -> String # The message of the exception reports the content of podspec for the # line that generated the original exception. @@ -188,7 +188,7 @@ module Bundler # # @return [String] the message of the exception. # - def to_s: () -> untyped + def to_s: () -> String private diff --git a/spec/convention/gemfile_spec.rb b/spec/convention/gemfile_spec.rb index 7cbb681a1..cefb6f1ad 100644 --- a/spec/convention/gemfile_spec.rb +++ b/spec/convention/gemfile_spec.rb @@ -8,6 +8,8 @@ def type_checker(code) checker = type_checker(%( source 'https://rubygems.org' + ruby "~> 3.3.5" + gemspec name: 'solargraph' # Local gemfile for development tools, etc. @@ -17,5 +19,21 @@ def type_checker(code) expect(checker.problems).to be_empty end + + it 'finds bad arguments to DSL methods' do + checker = type_checker(%( + source File + + gemspec bad_name: 'solargraph' + + # Local gemfile for development tools, etc. + local_gemfile = File.expand_path(".Gemfile", __dir__) + instance_eval File.read local_gemfile if File.exist? local_gemfile + )) + + expect(checker.problems.map(&:message).sort). + to eq(["Unrecognized keyword argument bad_name to Bundler::Dsl#gemspec", + "Wrong argument type for Bundler::Dsl#source: source expected String, received Class"].sort) + end end end From ab67325b21fac521b3b5d450113485e58ecbb867 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 12:28:37 -0400 Subject: [PATCH 010/460] Fix typechecking error --- lib/solargraph/pin_cache.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index c78cb6088..a084fa0f9 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -116,8 +116,10 @@ def uncache_core(out: nil) uncache(core_path, out: out) end - def uncache_stdlib - uncache(stdlib_path) + # @param out [IO, nil] + # @return [void] + def uncache_stdlib(out: nil) + uncache(stdlib_path, out: out) end def uncache_gem(gemspec, out: nil) From 5b3d080add36ace27ca541b2c85b8eab60f73e51 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 12:28:57 -0400 Subject: [PATCH 011/460] Fix typechecker to give expected errors for gemspec test (!) --- lib/solargraph/type_checker.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index aa215f97b..5a9c11905 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -324,10 +324,10 @@ def argument_problems_for chain, api_map, block_pin, locals, location end break if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper) - params = first_param_hash(pins) all_errors = [] pin.signatures.sort { |sig| sig.parameters.length }.each do |sig| + params = first_param_hash([sig]) errors = [] sig.parameters.each_with_index do |par, idx| # @todo add logic mapping up restarg parameters with @@ -467,7 +467,7 @@ def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kw result end - # @param pin [Pin::Method] + # @param pin [Pin::Callable] # @return [Hash{String => Hash{Symbol => String, ComplexType}}] def param_hash(pin) # @type [Hash{String => Hash{Symbol => String, ComplexType}}] @@ -494,7 +494,7 @@ def param_hash(pin) result end - # @param pins [Array] + # @param pins [Array] # @return [Hash{String => Hash{Symbol => String, ComplexType}}] def first_param_hash(pins) return {} if pins.empty? From 3f64bbe7645eb5ccdafb396cc4e1d9673f34b5ad Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 13:56:03 -0400 Subject: [PATCH 012/460] Fix duplicate definitions issue on other core pins --- lib/solargraph/rbs_map/core_map.rb | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/solargraph/rbs_map/core_map.rb b/lib/solargraph/rbs_map/core_map.rb index 818d36404..cb12b3caa 100644 --- a/lib/solargraph/rbs_map/core_map.rb +++ b/lib/solargraph/rbs_map/core_map.rb @@ -24,16 +24,15 @@ def pins @pins.replace cache else @pins.concat conversions.pins - Dir.glob(File.join(FILLS_DIRECTORY, '*')).each do |path| - next unless File.directory?(path) - fill_loader = RBS::EnvironmentLoader.new(repository: RBS::Repository.new(no_stdlib: false)) - fill_loader.add(path: Pathname(path)) - fill_conversions = Conversions.new(loader: fill_loader) - @pins.concat fill_conversions.pins - rescue RBS::DuplicatedDeclarationError => e - logger.debug "RBS already contains declarations in #{path}, skipping: #{e.message}" - end + + # Avoid RBS::DuplicatedDeclarationError by loading in a different EnvironmentLoader + fill_loader = RBS::EnvironmentLoader.new(core_root: nil, repository: RBS::Repository.new(no_stdlib: false)) + fill_loader.add(path: Pathname(FILLS_DIRECTORY)) + fill_conversions = Conversions.new(loader: fill_loader) + @pins.concat fill_conversions.pins + @pins.concat RbsMap::CoreFills::ALL + processed = ApiMap::Store.new(pins).pins.reject { |p| p.is_a?(Solargraph::Pin::Reference::Override) } @pins.replace processed From 8d6b6ea98086eec7929066c77a1048dec0ed68bf Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 13:56:52 -0400 Subject: [PATCH 013/460] Fix related typechecker issues --- lib/solargraph/type_checker.rb | 131 ++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 58 deletions(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 5a9c11905..0bcecdb00 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -147,33 +147,33 @@ def virtual_pin? pin # @return [Array] def method_param_type_problems_for pin stack = api_map.get_method_stack(pin.namespace, pin.name, scope: pin.scope) - params = first_param_hash(stack) result = [] - if rules.require_type_tags? - pin.signatures.each do |sig| - sig.parameters.each do |par| - break if par.decl == :restarg || par.decl == :kwrestarg || par.decl == :blockarg - unless params[par.name] - if pin.attribute? - inferred = pin.probe(api_map).self_to_type(pin.full_context) - if inferred.undefined? + pin.signatures.each do |sig| + params = param_details_from_stack(sig, stack) + if rules.require_type_tags? + sig.parameters.each do |par| + break if par.decl == :restarg || par.decl == :kwrestarg || par.decl == :blockarg + unless params[par.name] + if pin.attribute? + inferred = pin.probe(api_map).self_to_type(pin.full_context) + if inferred.undefined? + result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin) + end + else result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin) end - else - result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin) end end - end end - end - # @todo Should be able to probe type of name and data here - # @param name [String] - # @param data [Hash{Symbol => BasicObject}] - params.each_pair do |name, data| - # @type [ComplexType] - type = data[:qualified] - if type.undefined? - result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin) + # @todo Should be able to probe type of name and data here + # @param name [String] + # @param data [Hash{Symbol => BasicObject}] + params.each_pair do |name, data| + # @type [ComplexType] + type = data[:qualified] + if type.undefined? + result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin) + end end end result @@ -301,7 +301,7 @@ def argument_problems_for chain, api_map, block_pin, locals, location first_pin = pins.first if first_pin.is_a?(Pin::DelegatedMethod) && !first_pin.resolvable?(api_map) - # Do nothing, as we can't find the actual method implementation + # Do nothing, as we can't find the actual method implementation elsif first_pin.is_a?(Pin::Method) # @type [Pin::Method] pin = first_pin @@ -324,10 +324,9 @@ def argument_problems_for chain, api_map, block_pin, locals, location end break if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper) - all_errors = [] pin.signatures.sort { |sig| sig.parameters.length }.each do |sig| - params = first_param_hash([sig]) + params = param_details_from_stack(sig, pins) errors = [] sig.parameters.each_with_index do |par, idx| # @todo add logic mapping up restarg parameters with @@ -380,7 +379,7 @@ def argument_problems_for chain, api_map, block_pin, locals, location ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED ptype = ptype.self_to_type(par.context) if ptype.nil? - # @todo Some level (strong, I guess) should require the param here + # @todo Some level (strong, I guess) should require the param here else argtype = argchain.infer(api_map, block_pin, locals) if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype) @@ -429,7 +428,7 @@ def kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, if argchain data = params[par.name] if data.nil? - # @todo Some level (strong, I guess) should require the param here + # @todo Some level (strong, I guess) should require the param here else ptype = data[:qualified] unless ptype.undefined? @@ -467,9 +466,24 @@ def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kw result end - # @param pin [Pin::Callable] + # @param pin [Pin::Method] # @return [Hash{String => Hash{Symbol => String, ComplexType}}] - def param_hash(pin) + def add_restkwarg_param_tag_details(param_details, pin) + # see if we have additional tags to pay attention to from YARD - + # e.g., kwargs in a **restkwargs splat + tags = pin.docstring.tags(:param) + tags.each do |tag| + next if param_details.key? tag.name.to_s + next if tag.types.nil? + param_details[tag.name.to_s] = { + tagged: tag.types.join(', '), + qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace) + } + end + end + + # @param pin [Pin::Signature] + def signature_param_details(pin) # @type [Hash{String => Hash{Symbol => String, ComplexType}}] result = {} pin.parameters.each do |param| @@ -480,44 +494,45 @@ def param_hash(pin) qualified: type } end - # see if we have additional tags to pay attention to from YARD - - # e.g., kwargs in a **restkwargs splat - tags = pin.docstring.tags(:param) - tags.each do |tag| - next if result.key? tag.name.to_s - next if tag.types.nil? - result[tag.name.to_s] = { - tagged: tag.types.join(', '), - qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace) - } - end result end - # @param pins [Array] + # The original signature defines the parameters, but other + # signatures and method pins can help by adding type information + # + # @param param_details [Hash{String => Hash{Symbol => String, ComplexType}}] + # @param new_param_details [Hash{String => Hash{Symbol => String, ComplexType}}] + # @return [void] + def add_to_param_details(param_details, param_names, new_param_details) + new_param_details.each do |param_name, details| + next unless param_names.include?(param_name) + + param_details[param_name] ||= {} + param_details[param_name][:tagged] ||= details[:tagged] + param_details[param_name][:qualified] ||= details[:qualified] + end + end + + # @param signature [Pin::Signature] + # @param pins [Array] # @return [Hash{String => Hash{Symbol => String, ComplexType}}] - def first_param_hash(pins) - return {} if pins.empty? - first_pin_type = pins.first.typify(api_map) - first_pin = pins.first.proxy first_pin_type - param_names = first_pin.parameter_names - results = param_hash(first_pin) - pins[1..].each do |pin| - # @todo this assignment from parametric use of Hash should not lose its generic - # @type [Hash{String => Hash{Symbol => BasicObject}}] + def param_details_from_stack(signature, method_pin_stack) + signature_type = signature.typify(api_map) + signature = signature.proxy signature_type + param_details = signature_param_details(signature) + param_names = signature.parameter_names + + method_pin_stack.each do |method_pin| + add_restkwarg_param_tag_details(param_details, method_pin) # documentation of types in superclasses should fail back to # subclasses if the subclass hasn't documented something - superclass_results = param_hash(pin) - superclass_results.each do |param_name, details| - next unless param_names.include?(param_name) - - results[param_name] ||= {} - results[param_name][:tagged] ||= details[:tagged] - results[param_name][:qualified] ||= details[:qualified] + method_pin.signatures.each do |sig| + add_restkwarg_param_tag_details(param_details, sig) + add_to_param_details param_details, param_names, signature_param_details(sig) end end - results + param_details end # @param pin [Pin::Base] @@ -695,5 +710,5 @@ def without_ignored problems node && source_map.source.comments_for(node)&.include?('@sg-ignore') end end - end +end end From 8521e570dea35f68bbfeac88b2b5ce3a475c9396 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 13:57:14 -0400 Subject: [PATCH 014/460] Ensure all types are rooted --- rbs/fills/bundler/dsl.rbs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/rbs/fills/bundler/dsl.rbs b/rbs/fills/bundler/dsl.rbs index d7c5b3442..b63cd736c 100644 --- a/rbs/fills/bundler/dsl.rbs +++ b/rbs/fills/bundler/dsl.rbs @@ -70,11 +70,11 @@ module Bundler def eval_gemfile: (untyped gemfile, ?untyped? contents) -> untyped - def gemspec: (?path: String, ?glob: String, ?name: String, ?development_group: Symbol) -> void + def gemspec: (?path: ::String, ?glob: ::String, ?name: ::String, ?development_group: ::Symbol) -> void def gem: (untyped name, *untyped args) -> void - def source: (String source, ?type: Symbol) ?{ (?) -> untyped } -> void + def source: (::String source, ?type: ::Symbol) ?{ (?) -> untyped } -> void def git_source: (untyped name) ?{ (?) -> untyped } -> untyped @@ -145,30 +145,30 @@ module Bundler @to_s: untyped - # @return [String] the description that should be presented to the user. + # @return [::String] the description that should be presented to the user. # - attr_reader description: String + attr_reader description: ::String - # @return [String] the path of the dsl file that raised the exception. + # @return [::String] the path of the dsl file that raised the exception. # - attr_reader dsl_path: String + attr_reader dsl_path: ::String - # @return [Exception] the backtrace of the exception raised by the + # @return [::Exception] the backtrace of the exception raised by the # evaluation of the dsl file. # - attr_reader backtrace: Exception + attr_reader backtrace: ::Exception - # @param [Exception] backtrace @see backtrace - # @param [String] dsl_path @see dsl_path + # @param [::Exception] backtrace @see backtrace + # @param [::String] dsl_path @see dsl_path # - def initialize: (untyped description, String dsl_path, Exception backtrace, ?untyped? contents) -> void + def initialize: (untyped description, ::String dsl_path, ::Exception backtrace, ?untyped? contents) -> void def status_code: () -> untyped - # @return [String] the contents of the DSL that cause the exception to + # @return [::String] the contents of the DSL that cause the exception to # be raised. # - def contents: () -> String + def contents: () -> ::String # The message of the exception reports the content of podspec for the # line that generated the original exception. @@ -186,9 +186,9 @@ module Bundler # end # ------------------------------------------- # - # @return [String] the message of the exception. + # @return [::String] the message of the exception. # - def to_s: () -> String + def to_s: () -> ::String private From 8799369a461a8de9f421979b1537207470ce9595 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 14:02:17 -0400 Subject: [PATCH 015/460] Restore whitespace --- lib/solargraph/type_checker.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 0bcecdb00..fc00bdab2 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -379,7 +379,7 @@ def argument_problems_for chain, api_map, block_pin, locals, location ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED ptype = ptype.self_to_type(par.context) if ptype.nil? - # @todo Some level (strong, I guess) should require the param here + # @todo Some level (strong, I guess) should require the param here else argtype = argchain.infer(api_map, block_pin, locals) if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype) @@ -428,7 +428,7 @@ def kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, if argchain data = params[par.name] if data.nil? - # @todo Some level (strong, I guess) should require the param here + # @todo Some level (strong, I guess) should require the param here else ptype = data[:qualified] unless ptype.undefined? From 5f83ea37d0f5ea9bde47a2c762131096bf7bbc72 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 14:53:55 -0400 Subject: [PATCH 016/460] Progress towards eventually typechecking 'ruby' directive --- lib/solargraph/rbs_map/conversions.rb | 10 ++++++- rbs/fills/bundler/ruby_dsl.rbs | 42 +++++++++++++++++++++++++++ spec/convention/gemfile_spec.rb | 10 +++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 rbs/fills/bundler/ruby_dsl.rbs diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 6e50c022a..c9cdedbc5 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -476,7 +476,15 @@ def parts_of_function type, pin end if type.type.rest_positionals name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, source: :rbs, type_location: type_location) + inner_rest_positional_type = + ComplexType.try_parse(other_type_to_tag(type.type.rest_positionals.type)) + rest_positional_type = ComplexType::UniqueType.new('Array', + [], + [inner_rest_positional_type], + rooted: true, parameters_type: :list) + parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, + source: :rbs, type_location: type_location, + return_type: rest_positional_type,) end type.type.trailing_positionals.each do |param| name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" diff --git a/rbs/fills/bundler/ruby_dsl.rbs b/rbs/fills/bundler/ruby_dsl.rbs new file mode 100644 index 000000000..35b30c681 --- /dev/null +++ b/rbs/fills/bundler/ruby_dsl.rbs @@ -0,0 +1,42 @@ +# +# Bundler provides a consistent environment for Ruby projects by tracking and +# installing the exact gems and versions that are needed. +# +# Bundler is a part of Ruby's standard library. +# +# Bundler is used by creating *gemfiles* listing all the project dependencies +# and (optionally) their versions and then using +# +# require 'bundler/setup' +# +# or Bundler.setup to setup environment where only specified gems and their +# specified versions could be used. +# +# See [Bundler website](https://bundler.io/docs.html) for extensive +# documentation on gemfiles creation and Bundler usage. +# +# As a standard library inside project, Bundler could be used for introspection +# of loaded and required modules. +# +module Bundler + module RubyDsl + @ruby_version: untyped + + def ruby: (*::String ruby_version) -> void + + # Support the various file formats found in .ruby-version files. + # + # 3.2.2 + # ruby-3.2.2 + # + # Also supports .tool-versions files for asdf. Lines not starting with "ruby" are ignored. + # + # ruby 2.5.1 # comment is ignored + # ruby 2.5.1# close comment and extra spaces doesn't confuse + # + # Intentionally does not support `3.2.1@gemset` since rvm recommends using .ruby-gemset instead + # + # Loads the file relative to the dirname of the Gemfile itself. + def normalize_ruby_file: (::String filename) -> ::String + end +end diff --git a/spec/convention/gemfile_spec.rb b/spec/convention/gemfile_spec.rb index cefb6f1ad..0148d7e57 100644 --- a/spec/convention/gemfile_spec.rb +++ b/spec/convention/gemfile_spec.rb @@ -35,5 +35,15 @@ def type_checker(code) to eq(["Unrecognized keyword argument bad_name to Bundler::Dsl#gemspec", "Wrong argument type for Bundler::Dsl#source: source expected String, received Class"].sort) end + + # @todo add rest arg support to type checker + xit 'finds bad arguments to DSL ruby method' do + checker = type_checker(%( + ruby 123 + )) + + expect(checker.problems.map(&:message)). + to eq(["Wrong argument type for Bundler::Dsl#ruby: ruby_version expected String, received Integer"]) + end end end From e37757f670517b327926010575a78dfea4722449 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 15:00:33 -0400 Subject: [PATCH 017/460] Fix annotations --- lib/solargraph/type_checker.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index fc00bdab2..a24be8044 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -467,7 +467,7 @@ def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kw end # @param pin [Pin::Method] - # @return [Hash{String => Hash{Symbol => String, ComplexType}}] + # @return [void] def add_restkwarg_param_tag_details(param_details, pin) # see if we have additional tags to pay attention to from YARD - # e.g., kwargs in a **restkwargs splat @@ -483,6 +483,7 @@ def add_restkwarg_param_tag_details(param_details, pin) end # @param pin [Pin::Signature] + # @return [Hash{String => Hash{Symbol => String, ComplexType}}] def signature_param_details(pin) # @type [Hash{String => Hash{Symbol => String, ComplexType}}] result = {} From 225987cc6077ee84382b4ad8518c8e35a0cb7368 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 15:29:35 -0400 Subject: [PATCH 018/460] RuboCop fixes --- lib/solargraph/type_checker.rb | 2 +- spec/convention/gemfile_spec.rb | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index a24be8044..ba2284b21 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -73,7 +73,7 @@ def load_string code, filename = nil, level = :normal end end - private + private # @return [Array] def method_tag_problems diff --git a/spec/convention/gemfile_spec.rb b/spec/convention/gemfile_spec.rb index 0148d7e57..62a346ca0 100644 --- a/spec/convention/gemfile_spec.rb +++ b/spec/convention/gemfile_spec.rb @@ -31,19 +31,20 @@ def type_checker(code) instance_eval File.read local_gemfile if File.exist? local_gemfile )) - expect(checker.problems.map(&:message).sort). - to eq(["Unrecognized keyword argument bad_name to Bundler::Dsl#gemspec", - "Wrong argument type for Bundler::Dsl#source: source expected String, received Class"].sort) + expect(checker.problems.map(&:message).sort) + .to eq(["Unrecognized keyword argument bad_name to Bundler::Dsl#gemspec", + "Wrong argument type for Bundler::Dsl#source: source expected String, received Class"].sort) end - # @todo add rest arg support to type checker - xit 'finds bad arguments to DSL ruby method' do + it 'finds bad arguments to DSL ruby method' do + pending 'missing support for restargs in the typechecker' + checker = type_checker(%( ruby 123 )) - expect(checker.problems.map(&:message)). - to eq(["Wrong argument type for Bundler::Dsl#ruby: ruby_version expected String, received Integer"]) + expect(checker.problems.map(&:message)) + .to eq(["Wrong argument type for Bundler::Dsl#ruby: ruby_version expected String, received Integer"]) end end end From 90e93c67f9d7de6ceb135b6827cc2b07bc72eff5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 18 Jul 2025 18:31:46 -0400 Subject: [PATCH 019/460] Improve typechecking of generics --- lib/solargraph/complex_type.rb | 57 +++++- lib/solargraph/complex_type/type_methods.rb | 9 + lib/solargraph/complex_type/unique_type.rb | 173 ++++++++++++++++-- lib/solargraph/gem_pins.rb | 3 +- lib/solargraph/parser/node_methods.rb | 2 +- .../parser/parser_gem/node_methods.rb | 2 +- lib/solargraph/pin/parameter.rb | 12 +- lib/solargraph/range.rb | 3 +- lib/solargraph/source.rb | 2 +- lib/solargraph/type_checker.rb | 40 +++- lib/solargraph/type_checker/checks.rb | 124 ------------- lib/solargraph/type_checker/rules.rb | 6 +- spec/complex_type_spec.rb | 28 +++ spec/type_checker/levels/strong_spec.rb | 16 ++ spec/type_checker/levels/typed_spec.rb | 49 +++++ 15 files changed, 373 insertions(+), 153 deletions(-) delete mode 100644 lib/solargraph/type_checker/checks.rb diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 9e23eb502..53c28ed6e 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -102,7 +102,7 @@ def each_unique_type &block # @param atype [ComplexType] type which may be assigned to this type # @param api_map [ApiMap] The ApiMap that performs qualification def can_assign?(api_map, atype) - any? { |ut| ut.can_assign?(api_map, atype) } + atype.conforms_to?(api_map, self, :assignment) end # @return [Integer] @@ -176,6 +176,61 @@ def desc rooted_tags end + # @param api_map [ApiMap] + # @param expected [ComplexType, ComplexType::UniqueType] + # @param situation [:method_call, :return_type, :assignment] + # @param allow_subtype_skew [Boolean] if false, check if any + # subtypes of the expected type match the inferred type + # @param allow_reverse_match [Boolean] if true, check if any subtypes + # of the expected type match the inferred type + # @param allow_empty_params [Boolean] if true, allow a general + # inferred type without parameters to allow a more specific + # expcted type + # @param allow_any_match [Boolean] if true, any unique type + # matched in the expected qualifies as a match + # @return [Boolean] + def conforms_to? api_map, expected, + situation, + variance: erased_variance(situation), + allow_subtype_skew: false, + allow_empty_params: false, + allow_reverse_match: false, + allow_any_match: false #, +# allow_undefined_in_expected: false + expected = expected.downcast_to_literal_if_possible + inferred = downcast_to_literal_if_possible + + return duck_types_match?(api_map, expected, inferred) if expected.duck_type? + + if allow_any_match + inferred.any? { |inf| inf.conforms_to?(api_map, expected, situation, + allow_subtype_skew: allow_subtype_skew, + allow_empty_params: allow_empty_params, + allow_reverse_match: allow_reverse_match, + allow_any_match: allow_any_match) } + else + inferred.all? { |inf| inf.conforms_to?(api_map, expected, situation, + allow_subtype_skew: allow_subtype_skew, + allow_empty_params: allow_empty_params, + allow_reverse_match: allow_reverse_match, + allow_any_match: allow_any_match) } + end + end + + # @param api_map [ApiMap] + # @param expected [ComplexType] + # @param inferred [ComplexType] + # @return [Boolean] + def duck_types_match? api_map, expected, inferred + raise ArgumentError, 'Expected type must be duck type' unless expected.duck_type? + expected.each do |exp| + next unless exp.duck_type? + quack = exp.to_s[1..-1] + return false if api_map.get_method_stack(inferred.namespace, quack, scope: inferred.scope).empty? + end + true + end + def rooted_tags map(&:rooted_tag).join(', ') end diff --git a/lib/solargraph/complex_type/type_methods.rb b/lib/solargraph/complex_type/type_methods.rb index e6d596244..4fcaadb7f 100644 --- a/lib/solargraph/complex_type/type_methods.rb +++ b/lib/solargraph/complex_type/type_methods.rb @@ -69,6 +69,15 @@ def undefined? name == 'undefined' end + # Variance of the type ignoring any type parameters + def erased_variance situation = :method_call + if [:method_call, :return_type, :assignment].include?(situation) + :covariant + else + raise "Unknown situation: #{situation.inspect}" + end + end + # @param generics_to_erase [Enumerable] # @return [self] def erase_generics(generics_to_erase) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 0f4ec430d..1023d080e 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -151,10 +151,167 @@ def ==(other) eql?(other) end + # https://www.playfulpython.com/type-hinting-covariance-contra-variance/ + + # "[Expected] type variables that are COVARIANT can be substituted with + # a more specific [inferred] type without causing errors" + # + # "[Expected] type variables that are CONTRAVARIANT can be substituted + # with a more general [inferred] type without causing errors" + # + # "[Expected] types where neither is possible are INVARIANT" + # + # @param situation [:method_call] + # @param default [Symbol] The default variance to return if the type is not one of the special cases + # + # @return [:invariant, :covariant, :contravariant] + def parameter_variance situation, default = :covariant + # @todo RBS can specify variance - maybe we can use that info + # and also let folks specify? + # + # Array/Set: ideally invariant, since we don't know if user is + # going to add new stuff into it or read it. But we don't + # have a way to specify, so we use covariant + # Enumerable: covariant: can't be changed, so we can pass + # in more specific subtypes + # Hash: read-only would be covariant, read-write would be + # invariant if we could distinguish that - should default to + # covariant + # contravariant?: Proc - can be changed, so we can pass + # in less specific super types + if ['Hash', 'Tuple', 'Array', 'Set', 'Enumerable'].include?(name) && fixed_parameters? + :covariant + else + default + end + end + + # @param api_map [ApiMap] + # @param expected [ComplexType, ComplexType::UniqueType] + # @param situation [:method_call, :return_type] + # @param allow_subtype_skew [Boolean] if false, check if any + # subtypes of the expected type match the inferred type + # @param allow_empty_params [Boolean] if true, allow a general + # inferred type without parameters to allow a more specific + # expcted type + # @param allow_reverse_match [Boolean] if true, check if any subtypes + # of the expected type match the inferred type + # @param allow_any_match [Boolean] if true, any unique type + # matched in the expected qualifies as a match + def conforms_to_unique_type?(api_map, expected, situation = :method_call, + variance: erased_variance(situation), + allow_subtype_skew: allow_subtype_skew, + allow_empty_params: allow_empty_params, + allow_reverse_match: allow_reverse_match, + allow_any_match: allow_any_match) + expected = expected.downcast_to_literal_if_possible + inferred = downcast_to_literal_if_possible + + if allow_subtype_skew + # parameters are not considered in this case + expected = expected.erase_parameters + end + + if !expected.parameters? && inferred.parameters? + inferred = inferred.erase_parameters + end + + return true if inferred == expected + + if variance == :invariant + return false unless inferred.name == expected.name + elsif erased_variance == :covariant + # covariant: we can pass in a more specific type + + # we contain the expected mix-in, or we have a more specific type + return false unless api_map.type_include?(inferred.name, expected.name) || + api_map.super_and_sub?(expected.name, inferred.name) || + inferred.name == expected.name + + elsif erased_variance == :contravariant + # contravariant: we can pass in a more general type + + # we contain the expected mix-in, or we have a more general type + return false unless api_map.type_include?(inferred.name, expected.name) || + map.super_and_sub?(inferred.name, expected.name) || + inferred.name == expected.name + else + raise "Unknown erased variance: #{erased_variance.inspect}" + end + + return true if inferred.all_params.empty? && allow_empty_params + + # at this point we know the erased type is fine - time to look at parameters + + # there's an implicit 'any' on the expectation parameters + # if there are none specified + return true if expected.all_params.empty? + + unless expected.key_types.empty? + return false if inferred.key_types.empty? + + return false unless ComplexType.new(inferred.key_types).conforms_to?(api_map, + ComplexType.new(expected.key_types), + situation, + variance: parameter_variance(situation), + allow_subtype_skew: allow_subtype_skew, + allow_empty_params: allow_empty_params, + allow_reverse_match: allow_reverse_match, + allow_any_match: allow_any_match) + end + + return true if expected.subtypes.empty? + + return false if inferred.subtypes.empty? + + ComplexType.new(inferred.subtypes).conforms_to?(api_map, ComplexType.new(expected.subtypes), situation, + variance: parameter_variance(situation), + allow_subtype_skew: allow_subtype_skew, + allow_empty_params: allow_empty_params, + allow_reverse_match: allow_reverse_match, + allow_any_match: allow_any_match) + end + + # @param api_map [ApiMap] + # @param expected [ComplexType::UniqueType] + # @param situation [:method_call, :assignment, :return] + # @param allow_subtype_skew [Boolean] if false, check if any + # subtypes of the expected type match the inferred type + # @param allow_empty_params [Boolean] if true, allow a general + # inferred type without parameters to allow a more specific + # expcted type + # @param allow_reverse_match [Boolean] if true, check if any subtypes + # of the expected type match the inferred type + # @param allow_any_match [Boolean] if true, any unique type + # matched in the expected qualifies as a match + def conforms_to?(api_map, expected, + situation = :method_call, + allow_subtype_skew:, + allow_empty_params:, + allow_reverse_match:, + allow_any_match:) + # @todo teach this to validate duck types as inferred type + return true if duck_type? + + # complex types as expectations are unions - we only need to + # match one of their unique types + expected.any? do |expected_unique_type| + conforms_to_unique_type?(api_map, expected_unique_type, situation, + allow_subtype_skew: allow_subtype_skew, + allow_empty_params: allow_empty_params, + allow_reverse_match: allow_reverse_match, + allow_any_match: allow_any_match) + end + end + def hash [self.class, @name, @key_types, @sub_types, @rooted, @all_params, @parameters_type].hash end + def erase_parameters + UniqueType.new(name, rooted: rooted?, parameters_type: parameters_type) + end + # @return [Array] def items [self] @@ -236,18 +393,6 @@ def generic? name == GENERIC_TAG_NAME || all_params.any?(&:generic?) end - # @param api_map [ApiMap] The ApiMap that performs qualification - # @param atype [ComplexType] type which may be assigned to this type - def can_assign?(api_map, atype) - logger.debug { "UniqueType#can_assign?(self=#{rooted_tags.inspect}, atype=#{atype.rooted_tags.inspect})" } - downcasted_atype = atype.downcast_to_literal_if_possible - out = downcasted_atype.all? do |autype| - autype.name == name || api_map.super_and_sub?(name, autype.name) - end - logger.debug { "UniqueType#can_assign?(self=#{rooted_tags.inspect}, atype=#{atype.rooted_tags.inspect}) => #{out}" } - out - end - # @return [UniqueType] def downcast_to_literal_if_possible SINGLE_SUBTYPE.fetch(rooted_tag, self) @@ -437,6 +582,10 @@ def self_to_type dst end end + def any? &block + block.yield self + end + def all_rooted? return true if name == GENERIC_TAG_NAME rooted? && all_params.all?(&:rooted?) diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index b92cbd6af..f1dd25a9f 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -19,7 +19,8 @@ def self.build_yard_pins(gemspec) YardMap::Mapper.new(yardoc, gemspec).map end - # @param pins [Array] + # @param pins [Array] + # @return [Array] def self.combine_method_pins_by_path(pins) # bad_pins = pins.select { |pin| pin.is_a?(Pin::Method) && pin.path == 'StringIO.open' && pin.source == :rbs }; raise "wtf: #{bad_pins}" if bad_pins.length > 1 method_pins, alias_pins = pins.partition { |pin| pin.class == Pin::Method } diff --git a/lib/solargraph/parser/node_methods.rb b/lib/solargraph/parser/node_methods.rb index 12e974c16..2712f2867 100644 --- a/lib/solargraph/parser/node_methods.rb +++ b/lib/solargraph/parser/node_methods.rb @@ -74,7 +74,7 @@ def process node # @abstract # @param node [Parser::AST::Node] - # @return [Hash{Parser::AST::Node => Source::Chain}] + # @return [Hash{Parser::AST::Node, Symbol => Source::Chain}] def convert_hash node raise NotImplementedError end diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index b716b352d..bc0c37eb6 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -120,7 +120,7 @@ def drill_signature node, signature end # @param node [Parser::AST::Node] - # @return [Hash{Parser::AST::Node => Chain}] + # @return [Hash{Parser::AST::Node, Symbol => Chain}] def convert_hash node return {} unless Parser.is_ast_node?(node) return convert_hash(node.children[0]) if node.type == :kwsplat diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index bc802b748..b4fc3d9b2 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -166,7 +166,17 @@ def compatible_arg?(atype, api_map) # make sure we get types from up the method # inheritance chain if we don't have them on this pin ptype = typify api_map - ptype.undefined? || ptype.can_assign?(api_map, atype) || ptype.generic? + return true if ptype.undefined? + + return true if atype.conforms_to?(api_map, + ptype, + :method_call, + allow_subtype_skew: false, + allow_reverse_match: false, + allow_empty_params: true, + allow_any_match: false) + + ptype.generic? end def documentation diff --git a/lib/solargraph/range.rb b/lib/solargraph/range.rb index 615f180af..c508e48fa 100644 --- a/lib/solargraph/range.rb +++ b/lib/solargraph/range.rb @@ -24,6 +24,7 @@ def initialize start, ending [start, ending] end + # @param other [Object] def <=>(other) return nil unless other.is_a?(Range) if start == other.start @@ -78,7 +79,7 @@ def self.from_to l1, c1, l2, c2 # Get a range from a node. # - # @param node [Parser::AST::Node] + # @param node [AST::Node] # @return [Range, nil] def self.from_node node if node&.loc && node.loc.expression diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 11ab215ed..d4e0c3994 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -187,7 +187,7 @@ def code_for(node) frag.strip.gsub(/,$/, '') end - # @param node [Parser::AST::Node] + # @param node [AST::Node] # @return [String, nil] def comments_for node rng = Range.from_node(node) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index aa215f97b..ab87d5863 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -7,9 +7,7 @@ class TypeChecker autoload :Problem, 'solargraph/type_checker/problem' autoload :ParamDef, 'solargraph/type_checker/param_def' autoload :Rules, 'solargraph/type_checker/rules' - autoload :Checks, 'solargraph/type_checker/checks' - include Checks include Parser::NodeMethods # @return [String] @@ -113,7 +111,11 @@ def method_return_type_problems_for pin result.push Problem.new(pin.location, "#{pin.path} return type could not be inferred", pin: pin) end else - unless (rules.require_all_return_types_match_inferred? ? all_types_match?(api_map, inferred, declared) : any_types_match?(api_map, declared, inferred)) + unless inferred.conforms_to?(api_map, declared, :return_type, + allow_subtype_skew: false, + allow_empty_params: !rules.require_inferred_type_params, + allow_reverse_match: false, + allow_any_match: !rules.require_all_unique_types_match_declared?) result.push Problem.new(pin.location, "Declared return type #{declared.rooted_tags} does not match inferred type #{inferred.rooted_tags} for #{pin.path}", pin: pin) end end @@ -202,7 +204,11 @@ def variable_type_tag_problems result.push Problem.new(pin.location, "Variable type could not be inferred for #{pin.name}", pin: pin) end else - unless any_types_match?(api_map, declared, inferred) + unless inferred.conforms_to?(api_map, declared, :assignment, + allow_subtype_skew: false, + allow_empty_params: !rules.require_inferred_type_params, + allow_reverse_match: false, + allow_any_match: !rules.require_all_unique_types_match_declared?) result.push Problem.new(pin.location, "Declared type #{declared} does not match inferred type #{inferred} for variable #{pin.name}", pin: pin) end end @@ -284,8 +290,8 @@ def call_problems # @param chain [Solargraph::Source::Chain] # @param api_map [Solargraph::ApiMap] - # @param block_pin [Solargraph::Pin::Base] - # @param locals [Array] + # @param closure_pin [Solargraph::Pin::Closure] + # @param locals [Array] # @param location [Solargraph::Location] # @return [Array] def argument_problems_for chain, api_map, block_pin, locals, location @@ -383,7 +389,11 @@ def argument_problems_for chain, api_map, block_pin, locals, location # @todo Some level (strong, I guess) should require the param here else argtype = argchain.infer(api_map, block_pin, locals) - if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype) + if argtype.defined? && ptype.defined? && !argtype.conforms_to?(api_map, ptype, :method_call, + allow_subtype_skew: false, + allow_empty_params: !rules.require_inferred_type_params, + allow_reverse_match: false, + allow_any_match: !rules.require_all_unique_types_match_declared?) errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") next end @@ -433,8 +443,13 @@ def kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, else ptype = data[:qualified] unless ptype.undefined? + argtype = argchain.infer(api_map, block_pin, locals) - if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype) + if argtype.defined? && ptype && !argtype.conforms_to?(api_map, ptype, :method_call, + allow_subtype_skew: false, + allow_empty_params: !rules.require_inferred_type_params, + allow_reverse_match: false, + allow_any_match: !rules.require_all_unique_types_match_declared?) result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") end end @@ -460,7 +475,12 @@ def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kw next unless params.key?(pname.to_s) ptype = params[pname.to_s][:qualified] argtype = argchain.infer(api_map, block_pin, locals) - if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype) + if argtype.defined? && ptype && !argtype.conforms_to?(api_map, ptype, :method_call, + allow_subtype_skew: false, + allow_empty_params: !rules.require_inferred_type_params, + allow_reverse_match: false, + allow_any_match: !rules.require_all_unique_types_match_declared?) + result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}") end end @@ -495,6 +515,8 @@ def param_hash(pin) end # @param pins [Array] + # @param method_pin_stack [Array] + # # @return [Hash{String => Hash{Symbol => String, ComplexType}}] def first_param_hash(pins) return {} if pins.empty? diff --git a/lib/solargraph/type_checker/checks.rb b/lib/solargraph/type_checker/checks.rb deleted file mode 100644 index de402978b..000000000 --- a/lib/solargraph/type_checker/checks.rb +++ /dev/null @@ -1,124 +0,0 @@ -# frozen_string_literal: true - -module Solargraph - class TypeChecker - # Helper methods for performing type checks - # - module Checks - module_function - - # Compare an expected type with an inferred type. Common usage is to - # check if the type declared in a method's @return tag matches the type - # inferred from static analysis of the code. - # - # @param api_map [ApiMap] - # @param expected [ComplexType] - # @param inferred [ComplexType] - # @return [Boolean] - def types_match? api_map, expected, inferred - return true if expected.to_s == inferred.to_s - matches = [] - expected.each do |exp| - found = false - inferred.each do |inf| - # if api_map.super_and_sub?(fuzz(inf), fuzz(exp)) - if either_way?(api_map, inf, exp) - found = true - matches.push inf - break - end - end - return false unless found - end - inferred.each do |inf| - next if matches.include?(inf) - found = false - expected.each do |exp| - # if api_map.super_and_sub?(fuzz(inf), fuzz(exp)) - if either_way?(api_map, inf, exp) - found = true - break - end - end - return false unless found - end - true - end - - # @param api_map [ApiMap] - # @param expected [ComplexType] - # @param inferred [ComplexType] - # @return [Boolean] - def any_types_match? api_map, expected, inferred - expected = expected.downcast_to_literal_if_possible - inferred = inferred.downcast_to_literal_if_possible - return duck_types_match?(api_map, expected, inferred) if expected.duck_type? - # walk through the union expected type and see if any members - # of the union match the inferred type - expected.each do |exp| - next if exp.duck_type? - # @todo: there should be a level of typechecking where all - # unique types in the inferred must match one of the - # expected unique types - inferred.each do |inf| - # return true if exp == inf || api_map.super_and_sub?(fuzz(inf), fuzz(exp)) - return true if exp == inf || either_way?(api_map, inf, exp) - end - end - false - end - - # @param api_map [ApiMap] - # @param inferred [ComplexType] - # @param expected [ComplexType] - # @return [Boolean] - def all_types_match? api_map, inferred, expected - expected = expected.downcast_to_literal_if_possible - inferred = inferred.downcast_to_literal_if_possible - return duck_types_match?(api_map, expected, inferred) if expected.duck_type? - inferred.each do |inf| - next if inf.duck_type? - return false unless expected.any? { |exp| exp == inf || either_way?(api_map, inf, exp) } - end - true - end - - # @param api_map [ApiMap] - # @param expected [ComplexType] - # @param inferred [ComplexType] - # @return [Boolean] - def duck_types_match? api_map, expected, inferred - raise ArgumentError, 'Expected type must be duck type' unless expected.duck_type? - expected.each do |exp| - next unless exp.duck_type? - quack = exp.to_s[1..-1] - return false if api_map.get_method_stack(inferred.namespace, quack, scope: inferred.scope).empty? - end - true - end - - # @param type [ComplexType::UniqueType] - # @return [String] - def fuzz type - if type.parameters? - type.name - else - type.tag - end - end - - # @param api_map [ApiMap] - # @param cls1 [ComplexType::UniqueType] - # @param cls2 [ComplexType::UniqueType] - # @return [Boolean] - def either_way?(api_map, cls1, cls2) - # @todo there should be a level of typechecking which uses the - # full tag with parameters to determine compatibility - f1 = cls1.name - f2 = cls2.name - api_map.type_include?(f1, f2) || api_map.super_and_sub?(f1, f2) || api_map.super_and_sub?(f2, f1) - # api_map.type_include?(f1, f2) || api_map.super_and_sub?(f1, f2) || api_map.super_and_sub?(f2, f1) - end - end - end -end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 0aad5ed8a..8f2027d30 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -54,7 +54,11 @@ def validate_tags? rank > LEVELS[:normal] end - def require_all_return_types_match_inferred? + def require_inferred_type_params + rank >= LEVELS[:alpha] + end + + def require_all_unique_types_match_declared? rank >= LEVELS[:alpha] end end diff --git a/spec/complex_type_spec.rb b/spec/complex_type_spec.rb index f876d642f..2c060ceed 100644 --- a/spec/complex_type_spec.rb +++ b/spec/complex_type_spec.rb @@ -733,5 +733,33 @@ def make_bar expect(type.to_rbs).to eq('[Symbol, String, [Integer, Integer]]') expect(type.to_s).to eq('Array(Symbol, String, Array(Integer, Integer))') end + + it 'recognizes String conforms with itself' do + api_map = Solargraph::ApiMap.new + ptype = Solargraph::ComplexType.parse('String') + atype = Solargraph::ComplexType.parse('String') + expect(ptype.conforms_to?(api_map, atype, :method_call)).to be(true) + end + + it 'recognizes an erased container type conforms with itself' do + api_map = Solargraph::ApiMap.new + ptype = Solargraph::ComplexType.parse('Hash') + atype = Solargraph::ComplexType.parse('Hash') + expect(ptype.conforms_to?(api_map, atype, :method_call)).to be(true) + end + + it 'recognizes an unerased container type conforms with itself' do + api_map = Solargraph::ApiMap.new + ptype = Solargraph::ComplexType.parse('Array') + atype = Solargraph::ComplexType.parse('Array') + expect(ptype.conforms_to?(api_map, atype, :method_call)).to be(true) + end + + it 'recognizes a literal conforms with its type' do + api_map = Solargraph::ApiMap.new + ptype = Solargraph::ComplexType.parse('Symbol') + atype = Solargraph::ComplexType.parse(':foo') + expect(ptype.conforms_to?(api_map, atype, :method_call)).to be(true) + end end end diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 12db1e442..054a09efa 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -14,6 +14,22 @@ def bar; end expect(checker.problems.first.message).to include('Missing @return tag') end + + it 'ignores nilable type issues' do + checker = type_checker(%( + # @param a [String] + # @return [void] + def foo(a); end + + # @param b [String, nil] + # @return [void] + def bar(b) + foo(b) + end + )) + expect(checker.problems.map(&:message)).to eq([]) + end + it 'reports missing param tags' do checker = type_checker(%( class Foo diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index b10bbd42c..659ccee39 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -38,6 +38,19 @@ def bar expect(checker.problems.first.message).to include('does not match') end + it 'reports mismatched key and subtypes ' do + checker = type_checker(%( + # @return [Hash{String => String}] + def foo + # @type h [Hash{Integer => String}] + h = {} + h + end + )) + expect(checker.problems).to be_one + expect(checker.problems.first.message).to include('does not match') + end + it 'reports mismatched inherited return tags' do checker = type_checker(%( class Sup @@ -189,6 +202,42 @@ def foo expect(checker.problems).to be_empty end + it 'validates parameters in function calls' do + checker = type_checker(%( + # @param bar [String] + def foo(bar); end + + def baz + foo(123) + end + )) + expect(checker.problems.map(&:message)).to eq(['123']) + end + + it 'validates default values of parameters' do + checker = type_checker(%( + # @param bar [String] + def foo(bar = 123); end + )) + expect(checker.problems.map(&:message)).to eq(['Declared type String does not match inferred type 123 for variable bar']) + end + + it 'validates string default values of parameters' do + checker = type_checker(%( + # @param bar [String] + def foo(bar = 'foo'); end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'validates symbol default values of parameters' do + checker = type_checker(%( + # @param bar [Symbol] + def foo(bar = :baz); end + )) + expect(checker.problems.map(&:message)).to eq([]) + end + it 'validates subclass arguments of param types' do checker = type_checker(%( class Sup From 8e9cb6f19710bb9bdaefa09dbcb350b4a90685e8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 18 Jul 2025 18:37:35 -0400 Subject: [PATCH 020/460] Add alpha typechecking spec --- spec/type_checker/levels/alpha_spec.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 spec/type_checker/levels/alpha_spec.rb diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb new file mode 100644 index 000000000..d700ea3b7 --- /dev/null +++ b/spec/type_checker/levels/alpha_spec.rb @@ -0,0 +1,22 @@ +describe Solargraph::TypeChecker do + context 'alpha level' do + def type_checker(code) + Solargraph::TypeChecker.load_string(code, 'test.rb', :alpha) + end + + it 'reports nilable type issues' do + checker = type_checker(%( + # @param a [String] + # @return [void] + def foo(a); end + + # @param b [String, nil] + # @return [void] + def bar(b) + foo(b) + end + )) + expect(checker.problems.map(&:message)).to eq(["Wrong argument type for #foo: a expected String, received String, nil"]) + end + end +end From 660edc4a2d862a7f14fa7ed6dc002e79aa77d0b5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 19 Jul 2025 12:13:50 -0400 Subject: [PATCH 021/460] Move to full Bundler fill --- rbs/fills/bundler/bundler.rbs | 4254 +++++++++++++++++++++++++++++++++ rbs/fills/bundler/dsl.rbs | 200 -- 2 files changed, 4254 insertions(+), 200 deletions(-) create mode 100644 rbs/fills/bundler/bundler.rbs delete mode 100644 rbs/fills/bundler/dsl.rbs diff --git a/rbs/fills/bundler/bundler.rbs b/rbs/fills/bundler/bundler.rbs new file mode 100644 index 000000000..f60fe3837 --- /dev/null +++ b/rbs/fills/bundler/bundler.rbs @@ -0,0 +1,4254 @@ +# [`Bundler`](https://docs.ruby-lang.org/en/2.7.0/Bundler.html) provides a +# consistent environment for Ruby projects by tracking and installing the exact +# gems and versions that are needed. +# +# Since Ruby 2.6, [`Bundler`](https://docs.ruby-lang.org/en/2.7.0/Bundler.html) +# is a part of Ruby's standard library. +# +# Bunder is used by creating *gemfiles* listing all the project dependencies and +# (optionally) their versions and then using +# +# ```ruby +# require 'bundler/setup' +# ``` +# +# or +# [`Bundler.setup`](https://docs.ruby-lang.org/en/2.7.0/Bundler.html#method-c-setup) +# to setup environment where only specified gems and their specified versions +# could be used. +# +# See [Bundler website](https://bundler.io/docs.html) for extensive +# documentation on gemfiles creation and +# [`Bundler`](https://docs.ruby-lang.org/en/2.7.0/Bundler.html) usage. +# +# As a standard library inside project, +# [`Bundler`](https://docs.ruby-lang.org/en/2.7.0/Bundler.html) could be used +# for introspection of loaded and required modules. +module Bundler + def self.app_cache: (?untyped custom_path) -> untyped + + def self.app_config_path: () -> untyped + + # Returns absolute location of where binstubs are installed to. + def self.bin_path: () -> untyped + + # Returns absolute path of where gems are installed on the filesystem. + def self.bundle_path: () -> untyped + + def self.bundler_major_version: () -> untyped + + # @deprecated Use `unbundled\_env` instead + def self.clean_env: () -> untyped + + def self.clean_exec: (*untyped args) -> untyped + + def self.clean_system: (*untyped args) -> untyped + + def self.clear_gemspec_cache: () -> untyped + + def self.configure: () -> untyped + + def self.configured_bundle_path: () -> untyped + + # Returns current version of Ruby + # + # @return [CurrentRuby] Current version of Ruby + def self.current_ruby: () -> untyped + + def self.default_bundle_dir: () -> untyped + + def self.default_gemfile: () -> untyped + + def self.default_lockfile: () -> untyped + + def self.definition: (?(::Hash[String, Boolean | nil] | Boolean | nil) unlock) -> Bundler::Definition + + def self.environment: () -> untyped + + def self.feature_flag: () -> untyped + + def self.frozen_bundle?: () -> untyped + + def self.git_present?: () -> untyped + + def self.home: () -> untyped + + def self.install_path: () -> untyped + + def self.load: () -> untyped + + def self.load_gemspec: (untyped file, ?untyped validate) -> untyped + + def self.load_gemspec_uncached: (untyped file, ?untyped validate) -> untyped + + def self.load_marshal: (untyped data) -> untyped + + def self.local_platform: () -> untyped + + def self.locked_gems: () -> untyped + + def self.mkdir_p: (untyped path, ?untyped options) -> untyped + + # @return [Hash] Environment present before + # [`Bundler`](https://docs.ruby-lang.org/en/2.7.0/Bundler.html) was activated + def self.original_env: () -> untyped + + def self.read_file: (untyped file) -> untyped + + def self.require: (*untyped groups) -> untyped + + def self.require_thor_actions: () -> untyped + + def self.requires_sudo?: () -> untyped + + def self.reset!: () -> untyped + + def self.reset_paths!: () -> untyped + + def self.reset_rubygems!: () -> untyped + + def self.rm_rf: (untyped path) -> untyped + + def self.root: () -> untyped + + def self.ruby_scope: () -> untyped + + def self.rubygems: () -> untyped + + def self.settings: () -> untyped + + def self.setup: (*untyped groups) -> untyped + + def self.specs_path: () -> untyped + + def self.sudo: (untyped str) -> untyped + + def self.system_bindir: () -> untyped + + def self.tmp: (?untyped name) -> untyped + + def self.tmp_home_path: (untyped login, untyped warning) -> untyped + + def self.ui: () -> untyped + + def self.ui=: (untyped ui) -> untyped + + def self.use_system_gems?: () -> untyped + + def self.user_bundle_path: (?untyped dir) -> untyped + + def self.user_cache: () -> untyped + + def self.user_home: () -> untyped + + def self.which: (untyped executable) -> untyped + + # @deprecated Use `with\_unbundled\_env` instead + def self.with_clean_env: () { () -> untyped } -> untyped + + def self.with_unbundled_env: () { () -> untyped } -> untyped + + # Run block with environment present before + # [`Bundler`](https://docs.ruby-lang.org/en/2.7.0/Bundler.html) was activated + def self.with_original_env: () { () -> untyped } -> untyped +end + +Bundler::FREEBSD: untyped + +Bundler::NULL: untyped + +Bundler::ORIGINAL_ENV: untyped + +Bundler::SUDO_MUTEX: untyped + +Bundler::VERSION: untyped + +Bundler::WINDOWS: untyped + +class Bundler::APIResponseMismatchError < Bundler::BundlerError + def status_code: () -> untyped +end + +# Represents metadata from when the +# [`Bundler`](https://docs.ruby-lang.org/en/2.7.0/Bundler.html) gem was built. +module Bundler::BuildMetadata + # A string representing the date the bundler gem was built. + def self.built_at: () -> untyped + + # The SHA for the git commit the bundler gem was built from. + def self.git_commit_sha: () -> untyped + + # Whether this is an official release build of + # [`Bundler`](https://docs.ruby-lang.org/en/2.7.0/Bundler.html). + def self.release?: () -> untyped + + # A hash representation of the build metadata. + def self.to_h: () -> untyped +end + +class Bundler::BundlerError < StandardError + def self.all_errors: () -> untyped + + def self.status_code: (untyped code) -> untyped +end + +class Bundler::CurrentRuby + def jruby?: () -> untyped + + def jruby_18?: () -> untyped + + def jruby_19?: () -> untyped + + def jruby_1?: () -> untyped + + def jruby_20?: () -> untyped + + def jruby_21?: () -> untyped + + def jruby_22?: () -> untyped + + def jruby_23?: () -> untyped + + def jruby_24?: () -> untyped + + def jruby_25?: () -> untyped + + def jruby_26?: () -> untyped + + def jruby_27?: () -> untyped + + def jruby_2?: () -> untyped + + def maglev?: () -> untyped + + def maglev_18?: () -> untyped + + def maglev_19?: () -> untyped + + def maglev_1?: () -> untyped + + def maglev_20?: () -> untyped + + def maglev_21?: () -> untyped + + def maglev_22?: () -> untyped + + def maglev_23?: () -> untyped + + def maglev_24?: () -> untyped + + def maglev_25?: () -> untyped + + def maglev_26?: () -> untyped + + def maglev_27?: () -> untyped + + def maglev_2?: () -> untyped + + def mingw?: () -> untyped + + def mingw_18?: () -> untyped + + def mingw_19?: () -> untyped + + def mingw_1?: () -> untyped + + def mingw_20?: () -> untyped + + def mingw_21?: () -> untyped + + def mingw_22?: () -> untyped + + def mingw_23?: () -> untyped + + def mingw_24?: () -> untyped + + def mingw_25?: () -> untyped + + def mingw_26?: () -> untyped + + def mingw_27?: () -> untyped + + def mingw_2?: () -> untyped + + def mri?: () -> untyped + + def mri_18?: () -> untyped + + def mri_19?: () -> untyped + + def mri_1?: () -> untyped + + def mri_20?: () -> untyped + + def mri_21?: () -> untyped + + def mri_22?: () -> untyped + + def mri_23?: () -> untyped + + def mri_24?: () -> untyped + + def mri_25?: () -> untyped + + def mri_26?: () -> untyped + + def mri_27?: () -> untyped + + def mri_2?: () -> untyped + + def mswin64?: () -> untyped + + def mswin64_18?: () -> untyped + + def mswin64_19?: () -> untyped + + def mswin64_1?: () -> untyped + + def mswin64_20?: () -> untyped + + def mswin64_21?: () -> untyped + + def mswin64_22?: () -> untyped + + def mswin64_23?: () -> untyped + + def mswin64_24?: () -> untyped + + def mswin64_25?: () -> untyped + + def mswin64_26?: () -> untyped + + def mswin64_27?: () -> untyped + + def mswin64_2?: () -> untyped + + def mswin?: () -> untyped + + def mswin_18?: () -> untyped + + def mswin_19?: () -> untyped + + def mswin_1?: () -> untyped + + def mswin_20?: () -> untyped + + def mswin_21?: () -> untyped + + def mswin_22?: () -> untyped + + def mswin_23?: () -> untyped + + def mswin_24?: () -> untyped + + def mswin_25?: () -> untyped + + def mswin_26?: () -> untyped + + def mswin_27?: () -> untyped + + def mswin_2?: () -> untyped + + def on_18?: () -> untyped + + def on_19?: () -> untyped + + def on_1?: () -> untyped + + def on_20?: () -> untyped + + def on_21?: () -> untyped + + def on_22?: () -> untyped + + def on_23?: () -> untyped + + def on_24?: () -> untyped + + def on_25?: () -> untyped + + def on_26?: () -> untyped + + def on_27?: () -> untyped + + def on_2?: () -> untyped + + def rbx?: () -> untyped + + def rbx_18?: () -> untyped + + def rbx_19?: () -> untyped + + def rbx_1?: () -> untyped + + def rbx_20?: () -> untyped + + def rbx_21?: () -> untyped + + def rbx_22?: () -> untyped + + def rbx_23?: () -> untyped + + def rbx_24?: () -> untyped + + def rbx_25?: () -> untyped + + def rbx_26?: () -> untyped + + def rbx_27?: () -> untyped + + def rbx_2?: () -> untyped + + def ruby?: () -> untyped + + def ruby_18?: () -> untyped + + def ruby_19?: () -> untyped + + def ruby_1?: () -> untyped + + def ruby_20?: () -> untyped + + def ruby_21?: () -> untyped + + def ruby_22?: () -> untyped + + def ruby_23?: () -> untyped + + def ruby_24?: () -> untyped + + def ruby_25?: () -> untyped + + def ruby_26?: () -> untyped + + def ruby_27?: () -> untyped + + def ruby_2?: () -> untyped + + def truffleruby?: () -> untyped + + def truffleruby_18?: () -> untyped + + def truffleruby_19?: () -> untyped + + def truffleruby_1?: () -> untyped + + def truffleruby_20?: () -> untyped + + def truffleruby_21?: () -> untyped + + def truffleruby_22?: () -> untyped + + def truffleruby_23?: () -> untyped + + def truffleruby_24?: () -> untyped + + def truffleruby_25?: () -> untyped + + def truffleruby_26?: () -> untyped + + def truffleruby_27?: () -> untyped + + def truffleruby_2?: () -> untyped + + def x64_mingw?: () -> untyped + + def x64_mingw_18?: () -> untyped + + def x64_mingw_19?: () -> untyped + + def x64_mingw_1?: () -> untyped + + def x64_mingw_20?: () -> untyped + + def x64_mingw_21?: () -> untyped + + def x64_mingw_22?: () -> untyped + + def x64_mingw_23?: () -> untyped + + def x64_mingw_24?: () -> untyped + + def x64_mingw_25?: () -> untyped + + def x64_mingw_26?: () -> untyped + + def x64_mingw_27?: () -> untyped + + def x64_mingw_2?: () -> untyped +end + +Bundler::CurrentRuby::KNOWN_MAJOR_VERSIONS: untyped + +Bundler::CurrentRuby::KNOWN_MINOR_VERSIONS: untyped + +Bundler::CurrentRuby::KNOWN_PLATFORMS: untyped + +class Bundler::CyclicDependencyError < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::Definition + include ::Bundler::GemHelpers + + def add_current_platform: () -> untyped + + def add_platform: (untyped platform) -> untyped + + def current_dependencies: () -> untyped + + def dependencies: () -> Array[::Bundler::Dependency] + + def ensure_equivalent_gemfile_and_lockfile: (?untyped explicit_flag) -> untyped + + def find_indexed_specs: (untyped current_spec) -> untyped + + def find_resolved_spec: (untyped current_spec) -> untyped + + def gem_version_promoter: () -> untyped + + def gemfiles: () -> untyped + + def groups: () -> untyped + + def has_local_dependencies?: () -> untyped + + def has_rubygems_remotes?: () -> untyped + + def index: () -> untyped + + def initialize: (untyped lockfile, untyped dependencies, untyped sources, untyped unlock, ?untyped ruby_version, ?untyped optional_groups, ?untyped gemfiles) -> void + + def lock: (untyped file, ?untyped preserve_unknown_sections) -> untyped + + def locked_bundler_version: () -> untyped + + def locked_deps: () -> untyped + + def locked_gems: () -> Bundler::LockfileParser + + def locked_ruby_version: () -> untyped + + def locked_ruby_version_object: () -> untyped + + def lockfile: () -> Pathname + + def missing_specs: () -> untyped + + def missing_specs?: () -> untyped + + def new_platform?: () -> untyped + + def new_specs: () -> untyped + + def nothing_changed?: () -> untyped + + def platforms: () -> untyped + + def remove_platform: (untyped platform) -> untyped + + def removed_specs: () -> untyped + + def requested_specs: () -> untyped + + def requires: () -> untyped + + # Resolve all the dependencies specified in Gemfile. It ensures that + # dependencies that have been already resolved via locked file and are fresh + # are reused when resolving dependencies + # + # @return [SpecSet] resolved dependencies + def resolve: () -> untyped + + def resolve_remotely!: () -> untyped + + def resolve_with_cache!: () -> untyped + + def ruby_version: () -> untyped + + def spec_git_paths: () -> untyped + + # For given dependency list returns a SpecSet with Gemspec of all the required + # dependencies. + # + # ``` + # 1. The method first resolves the dependencies specified in Gemfile + # 2. After that it tries and fetches gemspec of resolved dependencies + # ``` + # + # @return [Bundler::SpecSet] + def specs: () -> untyped + + def specs_for: (untyped groups) -> untyped + + def to_lock: () -> untyped + + def unlocking?: () -> untyped + + def validate_platforms!: () -> untyped + + def validate_ruby!: () -> untyped + + def validate_runtime!: () -> untyped + + def self.build: (untyped gemfile, untyped lockfile, untyped unlock) -> untyped +end + +class Bundler::DepProxy + def ==: (untyped other) -> untyped + + def __platform: () -> untyped + + def dep: () -> untyped + + def eql?: (untyped other) -> untyped + + def hash: () -> untyped + + def initialize: (untyped dep, untyped platform) -> void + + def name: () -> untyped + + def requirement: () -> untyped + + def to_s: () -> untyped + + def type: () -> untyped +end + +class Bundler::Dependency < Gem::Dependency + def autorequire: () -> untyped + + def current_env?: () -> untyped + + def current_platform?: () -> untyped + + def gem_platforms: (untyped valid_platforms) -> untyped + + def gemfile: () -> untyped + + def groups: () -> untyped + + def initialize: (untyped name, untyped version, ?untyped options) { () -> untyped } -> void + + def platforms: () -> untyped + + def should_include?: () -> untyped + + def specific?: () -> untyped + + def to_lock: () -> untyped +end + +Bundler::Dependency::PLATFORM_MAP: untyped + +Bundler::Dependency::REVERSE_PLATFORM_MAP: untyped + +class Bundler::DeprecatedError < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::Dsl + @source: untyped + + @sources: untyped + + @git_sources: untyped + + @dependencies: untyped + + @groups: untyped + + @install_conditionals: untyped + + @optional_groups: untyped + + @platforms: untyped + + @env: untyped + + @ruby_version: untyped + + @gemspecs: untyped + + @gemfile: untyped + + @gemfiles: untyped + + @valid_keys: untyped + + include RubyDsl + + def self.evaluate: (untyped gemfile, untyped lockfile, untyped unlock) -> untyped + + VALID_PLATFORMS: untyped + + VALID_KEYS: ::Array["group" | "groups" | "git" | "path" | "glob" | "name" | "branch" | "ref" | "tag" | "require" | "submodules" | "platform" | "platforms" | "source" | "install_if" | "force_ruby_platform"] + + GITHUB_PULL_REQUEST_URL: ::Regexp + + GITLAB_MERGE_REQUEST_URL: ::Regexp + + attr_reader gemspecs: untyped + + attr_reader gemfile: untyped + + attr_accessor dependencies: untyped + + def initialize: () -> void + + def eval_gemfile: (untyped gemfile, ?untyped? contents) -> untyped + + def gemspec: (?path: ::String, ?glob: ::String, ?name: ::String, ?development_group: ::Symbol) -> void + + def gem: (untyped name, *untyped args) -> void + + def source: (::String source, ?type: ::Symbol) ?{ (?) -> untyped } -> void + + def git_source: (untyped name) ?{ (?) -> untyped } -> untyped + + def path: (untyped path, ?::Hash[untyped, untyped] options) ?{ (?) -> untyped } -> untyped + + def git: (untyped uri, ?::Hash[untyped, untyped] options) ?{ (?) -> untyped } -> untyped + + def github: (untyped repo, ?::Hash[untyped, untyped] options) ?{ () -> untyped } -> untyped + + def to_definition: (untyped lockfile, untyped unlock) -> untyped + + def group: (*untyped args) { () -> untyped } -> untyped + + def install_if: (*untyped args) { () -> untyped } -> untyped + + def platforms: (*untyped platforms) { () -> untyped } -> untyped + + alias platform platforms + + def env: (untyped name) { () -> untyped } -> untyped + + def plugin: (*untyped args) -> nil + + def method_missing: (untyped name, *untyped args) -> untyped + + def check_primary_source_safety: () -> untyped + + private + + def add_dependency: (untyped name, ?untyped? version, ?::Hash[untyped, untyped] options) -> (nil | untyped) + + def with_gemfile: (untyped gemfile) { (untyped) -> untyped } -> untyped + + def add_git_sources: () -> untyped + + def with_source: (untyped source) ?{ () -> untyped } -> untyped + + def normalize_hash: (untyped opts) -> untyped + + def valid_keys: () -> untyped + + def normalize_options: (untyped name, untyped version, untyped opts) -> untyped + + def normalize_group_options: (untyped opts, untyped groups) -> untyped + + def validate_keys: (untyped command, untyped opts, untyped valid_keys) -> (true | untyped) + + def normalize_source: (untyped source) -> untyped + + def deprecate_legacy_windows_platforms: (untyped platforms) -> (nil | untyped) + + def check_path_source_safety: () -> (nil | untyped) + + def check_rubygems_source_safety: () -> (untyped | nil) + + def multiple_global_source_warning: () -> untyped + + class DSLError < GemfileError + @status_code: untyped + + @description: untyped + + @dsl_path: untyped + + @backtrace: untyped + + @contents: untyped + + @to_s: untyped + + # @return [::String] the description that should be presented to the user. + # + attr_reader description: ::String + + # @return [::String] the path of the dsl file that raised the exception. + # + attr_reader dsl_path: ::String + + # @return [::Exception] the backtrace of the exception raised by the + # evaluation of the dsl file. + # + attr_reader backtrace: ::Exception + + # @param [::Exception] backtrace @see backtrace + # @param [::String] dsl_path @see dsl_path + # + def initialize: (untyped description, ::String dsl_path, ::Exception backtrace, ?untyped? contents) -> void + + def status_code: () -> untyped + + # @return [::String] the contents of the DSL that cause the exception to + # be raised. + # + def contents: () -> ::String + + # The message of the exception reports the content of podspec for the + # line that generated the original exception. + # + # @example Output + # + # Invalid podspec at `RestKit.podspec` - undefined method + # `exclude_header_search_paths=' for # + # + # from spec-repos/master/RestKit/0.9.3/RestKit.podspec:36 + # ------------------------------------------- + # # because it would break: #import + # > ns.exclude_header_search_paths = 'Code/RestKit.h' + # end + # ------------------------------------------- + # + # @return [::String] the message of the exception. + # + def to_s: () -> ::String + + private + + def parse_line_number_from_description: () -> ::Array[untyped] + end + + def gemfile_root: () -> untyped +end + +# used for Creating Specifications from the Gemcutter Endpoint +class Bundler::EndpointSpecification < Gem::Specification + def __swap__: (untyped spec) -> untyped + + def _local_specification: () -> untyped + + # needed for bundle clean + def bindir: () -> untyped + + def checksum: () -> untyped + + def dependencies: () -> untyped + + def dependencies=: (untyped dependencies) -> untyped + + # needed for binstubs + def executables: () -> untyped + + # needed for "with native extensions" during install + def extensions: () -> untyped + + def fetch_platform: () -> untyped + + def initialize: (untyped name, untyped version, untyped platform, untyped dependencies, ?untyped metadata) -> void + + # needed for inline + def load_paths: () -> untyped + + def name: () -> untyped + + def platform: () -> untyped + + # needed for post\_install\_messages during install + def post_install_message: () -> untyped + + def remote: () -> untyped + + def remote=: (untyped remote) -> untyped + + # needed for standalone, load required\_paths from local gemspec after the gem + # is installed + def require_paths: () -> untyped + + def required_ruby_version: () -> untyped + + def required_rubygems_version: () -> untyped + + def source: () -> untyped + + def source=: (untyped source) -> untyped + + def version: () -> untyped +end + +Bundler::EndpointSpecification::Elem: untyped + +Bundler::EndpointSpecification::ILLFORMED_MESSAGE: untyped + +class Bundler::EnvironmentPreserver + # @return [Hash] + def backup: () -> untyped + + def initialize: (untyped env, untyped keys) -> void + + # @return [Hash] + def restore: () -> untyped +end + +Bundler::EnvironmentPreserver::BUNDLER_KEYS: untyped + +Bundler::EnvironmentPreserver::BUNDLER_PREFIX: untyped + +Bundler::EnvironmentPreserver::INTENTIONALLY_NIL: untyped + +class Bundler::FeatureFlag + def allow_bundler_dependency_conflicts?: () -> untyped + + def allow_offline_install?: () -> untyped + + def auto_clean_without_path?: () -> untyped + + def auto_config_jobs?: () -> untyped + + def bundler_10_mode?: () -> untyped + + def bundler_1_mode?: () -> untyped + + def bundler_2_mode?: () -> untyped + + def bundler_3_mode?: () -> untyped + + def bundler_4_mode?: () -> untyped + + def bundler_5_mode?: () -> untyped + + def bundler_6_mode?: () -> untyped + + def bundler_7_mode?: () -> untyped + + def bundler_8_mode?: () -> untyped + + def bundler_9_mode?: () -> untyped + + def cache_all?: () -> untyped + + def cache_command_is_package?: () -> untyped + + def console_command?: () -> untyped + + def default_cli_command: () -> untyped + + def default_install_uses_path?: () -> untyped + + def deployment_means_frozen?: () -> untyped + + def disable_multisource?: () -> untyped + + def error_on_stderr?: () -> untyped + + def forget_cli_options?: () -> untyped + + def global_gem_cache?: () -> untyped + + def init_gems_rb?: () -> untyped + + def initialize: (untyped bundler_version) -> void + + def list_command?: () -> untyped + + def lockfile_uses_separate_rubygems_sources?: () -> untyped + + def only_update_to_newer_versions?: () -> untyped + + def path_relative_to_cwd?: () -> untyped + + def plugins?: () -> untyped + + def prefer_gems_rb?: () -> untyped + + def print_only_version_number?: () -> untyped + + def setup_makes_kernel_gem_public?: () -> untyped + + def skip_default_git_sources?: () -> untyped + + def specific_platform?: () -> untyped + + def suppress_install_using_messages?: () -> untyped + + def unlock_source_unlocks_spec?: () -> untyped + + def update_requires_all_flag?: () -> untyped + + def use_gem_version_promoter_for_major_updates?: () -> untyped + + def viz_command?: () -> untyped +end + +# # fileutils.rb +# +# Copyright (c) 2000-2007 Minero Aoki +# +# This program is free software. You can distribute/modify this program under +# the same terms of ruby. +# +# ## module [`Bundler::FileUtils`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils.html) +# +# Namespace for several file utility methods for copying, moving, removing, etc. +# +# ### [`Module`](https://docs.ruby-lang.org/en/2.7.0/Module.html) Functions +# +# ```ruby +# require 'bundler/vendor/fileutils/lib/fileutils' +# +# Bundler::FileUtils.cd(dir, **options) +# Bundler::FileUtils.cd(dir, **options) {|dir| block } +# Bundler::FileUtils.pwd() +# Bundler::FileUtils.mkdir(dir, **options) +# Bundler::FileUtils.mkdir(list, **options) +# Bundler::FileUtils.mkdir_p(dir, **options) +# Bundler::FileUtils.mkdir_p(list, **options) +# Bundler::FileUtils.rmdir(dir, **options) +# Bundler::FileUtils.rmdir(list, **options) +# Bundler::FileUtils.ln(target, link, **options) +# Bundler::FileUtils.ln(targets, dir, **options) +# Bundler::FileUtils.ln_s(target, link, **options) +# Bundler::FileUtils.ln_s(targets, dir, **options) +# Bundler::FileUtils.ln_sf(target, link, **options) +# Bundler::FileUtils.cp(src, dest, **options) +# Bundler::FileUtils.cp(list, dir, **options) +# Bundler::FileUtils.cp_r(src, dest, **options) +# Bundler::FileUtils.cp_r(list, dir, **options) +# Bundler::FileUtils.mv(src, dest, **options) +# Bundler::FileUtils.mv(list, dir, **options) +# Bundler::FileUtils.rm(list, **options) +# Bundler::FileUtils.rm_r(list, **options) +# Bundler::FileUtils.rm_rf(list, **options) +# Bundler::FileUtils.install(src, dest, **options) +# Bundler::FileUtils.chmod(mode, list, **options) +# Bundler::FileUtils.chmod_R(mode, list, **options) +# Bundler::FileUtils.chown(user, group, list, **options) +# Bundler::FileUtils.chown_R(user, group, list, **options) +# Bundler::FileUtils.touch(list, **options) +# ``` +# +# Possible `options` are: +# +# `:force` +# : forced operation (rewrite files if exist, remove directories if not empty, +# etc.); +# `:verbose` +# : print command to be run, in bash syntax, before performing it; +# `:preserve` +# : preserve object's group, user and modification time on copying; +# `:noop` +# : no changes are made (usable in combination with `:verbose` which will +# print the command to run) +# +# +# Each method documents the options that it honours. See also +# [`::commands`](https://docs.ruby-lang.org/en/2.7.0/FileUtils.html#method-c-commands), +# [`::options`](https://docs.ruby-lang.org/en/2.7.0/FileUtils.html#method-c-options) +# and +# [`::options_of`](https://docs.ruby-lang.org/en/2.7.0/FileUtils.html#method-c-options_of) +# methods to introspect which command have which options. +# +# All methods that have the concept of a "source" file or directory can take +# either one file or a list of files in that argument. See the method +# documentation for examples. +# +# There are some 'low level' methods, which do not accept keyword arguments: +# +# ```ruby +# Bundler::FileUtils.copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) +# Bundler::FileUtils.copy_file(src, dest, preserve = false, dereference = true) +# Bundler::FileUtils.copy_stream(srcstream, deststream) +# Bundler::FileUtils.remove_entry(path, force = false) +# Bundler::FileUtils.remove_entry_secure(path, force = false) +# Bundler::FileUtils.remove_file(path, force = false) +# Bundler::FileUtils.compare_file(path_a, path_b) +# Bundler::FileUtils.compare_stream(stream_a, stream_b) +# Bundler::FileUtils.uptodate?(file, cmp_list) +# ``` +# +# ## module [`Bundler::FileUtils::Verbose`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils/Verbose.html) +# +# This module has all methods of +# [`Bundler::FileUtils`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils.html) +# module, but it outputs messages before acting. This equates to passing the +# `:verbose` flag to methods in +# [`Bundler::FileUtils`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils.html). +# +# ## module [`Bundler::FileUtils::NoWrite`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils/NoWrite.html) +# +# This module has all methods of +# [`Bundler::FileUtils`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils.html) +# module, but never changes files/directories. This equates to passing the +# `:noop` flag to methods in +# [`Bundler::FileUtils`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils.html). +# +# ## module [`Bundler::FileUtils::DryRun`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils/DryRun.html) +# +# This module has all methods of +# [`Bundler::FileUtils`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils.html) +# module, but never changes files/directories. This equates to passing the +# `:noop` and `:verbose` flags to methods in +# [`Bundler::FileUtils`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils.html). +module Bundler::FileUtils + include ::Bundler::FileUtils::StreamUtils_ + + extend ::Bundler::FileUtils::StreamUtils_ + + def self.cd: (untyped dir, ?verbose: untyped verbose) { () -> untyped } -> untyped + + def self.chdir: (untyped dir, ?verbose: untyped verbose) { () -> untyped } -> untyped + + def self.chmod: (untyped mode, untyped list, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.chmod_R: (untyped mode, untyped list, ?noop: untyped noop, ?verbose: untyped verbose, ?force: untyped force) -> untyped + + def self.chown: (untyped user, untyped group, untyped list, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.chown_R: (untyped user, untyped group, untyped list, ?noop: untyped noop, ?verbose: untyped verbose, ?force: untyped force) -> untyped + + def self.cmp: (untyped a, untyped b) -> untyped + + def self.collect_method: (untyped opt) -> untyped + + # Returns an [`Array`](https://docs.ruby-lang.org/en/2.7.0/Array.html) of + # names of high-level methods that accept any keyword arguments. + # + # ```ruby + # p Bundler::FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...] + # ``` + def self.commands: () -> untyped + + def self.compare_file: (untyped a, untyped b) -> untyped + + def self.compare_stream: (untyped a, untyped b) -> untyped + + def self.copy: (untyped src, untyped dest, ?preserve: untyped preserve, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.copy_entry: (untyped src, untyped dest, ?untyped preserve, ?untyped dereference_root, ?untyped remove_destination) -> untyped + + def self.copy_file: (untyped src, untyped dest, ?untyped preserve, ?untyped dereference) -> untyped + + def self.copy_stream: (untyped src, untyped dest) -> untyped + + def self.cp: (untyped src, untyped dest, ?preserve: untyped preserve, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.cp_r: (untyped src, untyped dest, ?preserve: untyped preserve, ?noop: untyped noop, ?verbose: untyped verbose, ?dereference_root: untyped dereference_root, ?remove_destination: untyped remove_destination) -> untyped + + # Alias for: + # [`pwd`](https://docs.ruby-lang.org/en/2.7.0/FileUtils.html#method-i-pwd) + def self.getwd: () -> untyped + + def self.have_option?: (untyped mid, untyped opt) -> untyped + + def self.identical?: (untyped a, untyped b) -> untyped + + def self.install: (untyped src, untyped dest, ?mode: untyped mode, ?owner: untyped owner, ?group: untyped group, ?preserve: untyped preserve, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.link: (untyped src, untyped dest, ?force: untyped force, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.ln: (untyped src, untyped dest, ?force: untyped force, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.ln_s: (untyped src, untyped dest, ?force: untyped force, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.ln_sf: (untyped src, untyped dest, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.makedirs: (untyped list, ?mode: untyped mode, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.mkdir: (untyped list, ?mode: untyped mode, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.mkdir_p: (untyped list, ?mode: untyped mode, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.mkpath: (untyped list, ?mode: untyped mode, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.move: (untyped src, untyped dest, ?force: untyped force, ?noop: untyped noop, ?verbose: untyped verbose, ?secure: untyped secure) -> untyped + + def self.mv: (untyped src, untyped dest, ?force: untyped force, ?noop: untyped noop, ?verbose: untyped verbose, ?secure: untyped secure) -> untyped + + # Returns an [`Array`](https://docs.ruby-lang.org/en/2.7.0/Array.html) of + # option names. + # + # ```ruby + # p Bundler::FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"] + # ``` + def self.options: () -> untyped + + def self.options_of: (untyped mid) -> untyped + + def self.private_module_function: (untyped name) -> untyped + + # Returns the name of the current directory. + # + # Also aliased as: + # [`getwd`](https://docs.ruby-lang.org/en/2.7.0/FileUtils.html#method-c-getwd) + def self.pwd: () -> untyped + + def self.remove: (untyped list, ?force: untyped force, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.remove_dir: (untyped path, ?untyped force) -> untyped + + def self.remove_entry: (untyped path, ?untyped force) -> untyped + + def self.remove_entry_secure: (untyped path, ?untyped force) -> untyped + + def self.remove_file: (untyped path, ?untyped force) -> untyped + + def self.rm: (untyped list, ?force: untyped force, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.rm_f: (untyped list, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.rm_r: (untyped list, ?force: untyped force, ?noop: untyped noop, ?verbose: untyped verbose, ?secure: untyped secure) -> untyped + + def self.rm_rf: (untyped list, ?noop: untyped noop, ?verbose: untyped verbose, ?secure: untyped secure) -> untyped + + def self.rmdir: (untyped list, ?parents: untyped parents, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.rmtree: (untyped list, ?noop: untyped noop, ?verbose: untyped verbose, ?secure: untyped secure) -> untyped + + def self.safe_unlink: (untyped list, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.symlink: (untyped src, untyped dest, ?force: untyped force, ?noop: untyped noop, ?verbose: untyped verbose) -> untyped + + def self.touch: (untyped list, ?noop: untyped noop, ?verbose: untyped verbose, ?mtime: untyped mtime, ?nocreate: untyped nocreate) -> untyped + + def self.uptodate?: (untyped new, untyped old_list) -> untyped +end + +Bundler::FileUtils::LOW_METHODS: untyped + +Bundler::FileUtils::METHODS: untyped + +Bundler::FileUtils::OPT_TABLE: untyped + +# This module has all methods of +# [`Bundler::FileUtils`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils.html) +# module, but never changes files/directories, with printing message before +# acting. This equates to passing the `:noop` and `:verbose` flag to methods in +# [`Bundler::FileUtils`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils.html). +module Bundler::FileUtils::DryRun + include ::Bundler::FileUtils::LowMethods + + include ::Bundler::FileUtils + + include ::Bundler::FileUtils::StreamUtils_ + + extend ::Bundler::FileUtils::DryRun + + extend ::Bundler::FileUtils::LowMethods + + extend ::Bundler::FileUtils + + extend ::Bundler::FileUtils::StreamUtils_ + + def self.cd: (*untyped _) -> untyped + + def self.chdir: (*untyped _) -> untyped + + def self.chmod: (*untyped args, **untyped options) -> untyped + + def self.chmod_R: (*untyped args, **untyped options) -> untyped + + def self.chown: (*untyped args, **untyped options) -> untyped + + def self.chown_R: (*untyped args, **untyped options) -> untyped + + def self.cmp: (*untyped _) -> untyped + + def self.compare_file: (*untyped _) -> untyped + + def self.compare_stream: (*untyped _) -> untyped + + def self.copy: (*untyped args, **untyped options) -> untyped + + def self.copy_entry: (*untyped _) -> untyped + + def self.copy_file: (*untyped _) -> untyped + + def self.copy_stream: (*untyped _) -> untyped + + def self.cp: (*untyped args, **untyped options) -> untyped + + def self.cp_r: (*untyped args, **untyped options) -> untyped + + def self.getwd: (*untyped _) -> untyped + + def self.identical?: (*untyped _) -> untyped + + def self.install: (*untyped args, **untyped options) -> untyped + + def self.link: (*untyped args, **untyped options) -> untyped + + def self.ln: (*untyped args, **untyped options) -> untyped + + def self.ln_s: (*untyped args, **untyped options) -> untyped + + def self.ln_sf: (*untyped args, **untyped options) -> untyped + + def self.makedirs: (*untyped args, **untyped options) -> untyped + + def self.mkdir: (*untyped args, **untyped options) -> untyped + + def self.mkdir_p: (*untyped args, **untyped options) -> untyped + + def self.mkpath: (*untyped args, **untyped options) -> untyped + + def self.move: (*untyped args, **untyped options) -> untyped + + def self.mv: (*untyped args, **untyped options) -> untyped + + def self.pwd: (*untyped _) -> untyped + + def self.remove: (*untyped args, **untyped options) -> untyped + + def self.remove_dir: (*untyped _) -> untyped + + def self.remove_entry: (*untyped _) -> untyped + + def self.remove_entry_secure: (*untyped _) -> untyped + + def self.remove_file: (*untyped _) -> untyped + + def self.rm: (*untyped args, **untyped options) -> untyped + + def self.rm_f: (*untyped args, **untyped options) -> untyped + + def self.rm_r: (*untyped args, **untyped options) -> untyped + + def self.rm_rf: (*untyped args, **untyped options) -> untyped + + def self.rmdir: (*untyped args, **untyped options) -> untyped + + def self.rmtree: (*untyped args, **untyped options) -> untyped + + def self.safe_unlink: (*untyped args, **untyped options) -> untyped + + def self.symlink: (*untyped args, **untyped options) -> untyped + + def self.touch: (*untyped args, **untyped options) -> untyped + + def self.uptodate?: (*untyped _) -> untyped +end + +class Bundler::FileUtils::Entry_ + include ::Bundler::FileUtils::StreamUtils_ + + def blockdev?: () -> untyped + + def chardev?: () -> untyped + + def chmod: (untyped mode) -> untyped + + def chown: (untyped uid, untyped gid) -> untyped + + def copy: (untyped dest) -> untyped + + def copy_file: (untyped dest) -> untyped + + def copy_metadata: (untyped path) -> untyped + + def dereference?: () -> untyped + + def directory?: () -> untyped + + def door?: () -> untyped + + def entries: () -> untyped + + def exist?: () -> untyped + + def file?: () -> untyped + + def initialize: (untyped a, ?untyped b, ?untyped deref) -> void + + def inspect: () -> untyped + + def lstat: () -> untyped + + def lstat!: () -> untyped + + def path: () -> untyped + + def pipe?: () -> untyped + + def platform_support: () -> untyped + + def postorder_traverse: () -> untyped + + def prefix: () -> untyped + + def preorder_traverse: () -> untyped + + def rel: () -> untyped + + def remove: () -> untyped + + def remove_dir1: () -> untyped + + def remove_file: () -> untyped + + def socket?: () -> untyped + + def stat: () -> untyped + + def stat!: () -> untyped + + def symlink?: () -> untyped + + def traverse: () -> untyped + + def wrap_traverse: (untyped pre, untyped post) -> untyped +end + +Bundler::FileUtils::Entry_::DIRECTORY_TERM: untyped + +Bundler::FileUtils::Entry_::SYSCASE: untyped + +Bundler::FileUtils::Entry_::S_IF_DOOR: untyped + +module Bundler::FileUtils::LowMethods +end + +# This module has all methods of +# [`Bundler::FileUtils`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils.html) +# module, but never changes files/directories. This equates to passing the +# `:noop` flag to methods in +# [`Bundler::FileUtils`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils.html). +module Bundler::FileUtils::NoWrite + include ::Bundler::FileUtils::LowMethods + + include ::Bundler::FileUtils + + include ::Bundler::FileUtils::StreamUtils_ + + extend ::Bundler::FileUtils::NoWrite + + extend ::Bundler::FileUtils::LowMethods + + extend ::Bundler::FileUtils + + extend ::Bundler::FileUtils::StreamUtils_ + + def self.cd: (*untyped _) -> untyped + + def self.chdir: (*untyped _) -> untyped + + def self.chmod: (*untyped args, **untyped options) -> untyped + + def self.chmod_R: (*untyped args, **untyped options) -> untyped + + def self.chown: (*untyped args, **untyped options) -> untyped + + def self.chown_R: (*untyped args, **untyped options) -> untyped + + def self.cmp: (*untyped _) -> untyped + + def self.compare_file: (*untyped _) -> untyped + + def self.compare_stream: (*untyped _) -> untyped + + def self.copy: (*untyped args, **untyped options) -> untyped + + def self.copy_entry: (*untyped _) -> untyped + + def self.copy_file: (*untyped _) -> untyped + + def self.copy_stream: (*untyped _) -> untyped + + def self.cp: (*untyped args, **untyped options) -> untyped + + def self.cp_r: (*untyped args, **untyped options) -> untyped + + def self.getwd: (*untyped _) -> untyped + + def self.identical?: (*untyped _) -> untyped + + def self.install: (*untyped args, **untyped options) -> untyped + + def self.link: (*untyped args, **untyped options) -> untyped + + def self.ln: (*untyped args, **untyped options) -> untyped + + def self.ln_s: (*untyped args, **untyped options) -> untyped + + def self.ln_sf: (*untyped args, **untyped options) -> untyped + + def self.makedirs: (*untyped args, **untyped options) -> untyped + + def self.mkdir: (*untyped args, **untyped options) -> untyped + + def self.mkdir_p: (*untyped args, **untyped options) -> untyped + + def self.mkpath: (*untyped args, **untyped options) -> untyped + + def self.move: (*untyped args, **untyped options) -> untyped + + def self.mv: (*untyped args, **untyped options) -> untyped + + def self.pwd: (*untyped _) -> untyped + + def self.remove: (*untyped args, **untyped options) -> untyped + + def self.remove_dir: (*untyped _) -> untyped + + def self.remove_entry: (*untyped _) -> untyped + + def self.remove_entry_secure: (*untyped _) -> untyped + + def self.remove_file: (*untyped _) -> untyped + + def self.rm: (*untyped args, **untyped options) -> untyped + + def self.rm_f: (*untyped args, **untyped options) -> untyped + + def self.rm_r: (*untyped args, **untyped options) -> untyped + + def self.rm_rf: (*untyped args, **untyped options) -> untyped + + def self.rmdir: (*untyped args, **untyped options) -> untyped + + def self.rmtree: (*untyped args, **untyped options) -> untyped + + def self.safe_unlink: (*untyped args, **untyped options) -> untyped + + def self.symlink: (*untyped args, **untyped options) -> untyped + + def self.touch: (*untyped args, **untyped options) -> untyped + + def self.uptodate?: (*untyped _) -> untyped +end + +module Bundler::FileUtils::StreamUtils_ +end + +# This module has all methods of +# [`Bundler::FileUtils`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils.html) +# module, but it outputs messages before acting. This equates to passing the +# `:verbose` flag to methods in +# [`Bundler::FileUtils`](https://docs.ruby-lang.org/en/2.7.0/Bundler/FileUtils.html). +module Bundler::FileUtils::Verbose + include ::Bundler::FileUtils + + include ::Bundler::FileUtils::StreamUtils_ + + extend ::Bundler::FileUtils::Verbose + + extend ::Bundler::FileUtils + + extend ::Bundler::FileUtils::StreamUtils_ + + def self.cd: (*untyped args, **untyped options) -> untyped + + def self.chdir: (*untyped args, **untyped options) -> untyped + + def self.chmod: (*untyped args, **untyped options) -> untyped + + def self.chmod_R: (*untyped args, **untyped options) -> untyped + + def self.chown: (*untyped args, **untyped options) -> untyped + + def self.chown_R: (*untyped args, **untyped options) -> untyped + + def self.cmp: (untyped a, untyped b) -> untyped + + def self.compare_file: (untyped a, untyped b) -> untyped + + def self.compare_stream: (untyped a, untyped b) -> untyped + + def self.copy: (*untyped args, **untyped options) -> untyped + + def self.copy_entry: (untyped src, untyped dest, ?untyped preserve, ?untyped dereference_root, ?untyped remove_destination) -> untyped + + def self.copy_file: (untyped src, untyped dest, ?untyped preserve, ?untyped dereference) -> untyped + + def self.copy_stream: (untyped src, untyped dest) -> untyped + + def self.cp: (*untyped args, **untyped options) -> untyped + + def self.cp_r: (*untyped args, **untyped options) -> untyped + + def self.getwd: () -> untyped + + def self.identical?: (untyped a, untyped b) -> untyped + + def self.install: (*untyped args, **untyped options) -> untyped + + def self.link: (*untyped args, **untyped options) -> untyped + + def self.ln: (*untyped args, **untyped options) -> untyped + + def self.ln_s: (*untyped args, **untyped options) -> untyped + + def self.ln_sf: (*untyped args, **untyped options) -> untyped + + def self.makedirs: (*untyped args, **untyped options) -> untyped + + def self.mkdir: (*untyped args, **untyped options) -> untyped + + def self.mkdir_p: (*untyped args, **untyped options) -> untyped + + def self.mkpath: (*untyped args, **untyped options) -> untyped + + def self.move: (*untyped args, **untyped options) -> untyped + + def self.mv: (*untyped args, **untyped options) -> untyped + + def self.pwd: () -> untyped + + def self.remove: (*untyped args, **untyped options) -> untyped + + def self.remove_dir: (untyped path, ?untyped force) -> untyped + + def self.remove_entry: (untyped path, ?untyped force) -> untyped + + def self.remove_entry_secure: (untyped path, ?untyped force) -> untyped + + def self.remove_file: (untyped path, ?untyped force) -> untyped + + def self.rm: (*untyped args, **untyped options) -> untyped + + def self.rm_f: (*untyped args, **untyped options) -> untyped + + def self.rm_r: (*untyped args, **untyped options) -> untyped + + def self.rm_rf: (*untyped args, **untyped options) -> untyped + + def self.rmdir: (*untyped args, **untyped options) -> untyped + + def self.rmtree: (*untyped args, **untyped options) -> untyped + + def self.safe_unlink: (*untyped args, **untyped options) -> untyped + + def self.symlink: (*untyped args, **untyped options) -> untyped + + def self.touch: (*untyped args, **untyped options) -> untyped + + def self.uptodate?: (untyped new, untyped old_list) -> untyped +end + +class Bundler::GemHelper + def allowed_push_host: () -> untyped + + def already_tagged?: () -> untyped + + def base: () -> untyped + + def build_checksum: (?untyped built_gem_path) -> untyped + + def build_gem: () -> untyped + + def built_gem_path: () -> untyped + + def clean?: () -> untyped + + def committed?: () -> untyped + + def current_branch: () -> untyped + + def default_remote: () -> untyped + + def gem_command: () -> untyped + + def gem_key: () -> untyped + + def gem_push?: () -> untyped + + def gem_push_host: () -> untyped + + def gemspec: () -> untyped + + def git_push: (?untyped remote) -> untyped + + def guard_clean: () -> untyped + + def initialize: (?untyped base, ?untyped name) -> void + + def install: () -> untyped + + def install_gem: (?untyped built_gem_path, ?untyped local) -> untyped + + def name: () -> untyped + + def rubygem_push: (untyped path) -> untyped + + def sh: (untyped cmd) { () -> untyped } -> untyped + + def sh_with_input: (untyped cmd) -> untyped + + def sh_with_status: (untyped cmd) { () -> untyped } -> untyped + + def spec_path: () -> untyped + + def tag_prefix=: (untyped tag_prefix) -> untyped + + def tag_version: () -> untyped + + def version: () -> untyped + + def version_tag: () -> untyped + + def self.instance: () -> untyped + + def self.instance=: (untyped instance) -> untyped + + def self.install_tasks: (?untyped opts) -> untyped + + def self.tag_prefix=: (untyped tag_prefix) -> untyped + + def self.gemspec: () { () -> untyped } -> untyped +end + +module Bundler::GemHelpers + def self.generic: (untyped p) -> untyped + + def self.generic_local_platform: () -> untyped + + def self.platform_specificity_match: (untyped spec_platform, untyped user_platform) -> untyped + + def self.select_best_platform_match: (untyped specs, untyped platform) -> untyped +end + +Bundler::GemHelpers::GENERICS: untyped + +Bundler::GemHelpers::GENERIC_CACHE: untyped + +class Bundler::GemHelpers::PlatformMatch < Struct + def <=>: (untyped other) -> untyped + + def cpu_match: () -> untyped + + def cpu_match=: (untyped _) -> untyped + + def os_match: () -> untyped + + def os_match=: (untyped _) -> untyped + + def platform_version_match: () -> untyped + + def platform_version_match=: (untyped _) -> untyped + + def self.[]: (*untyped _) -> untyped + + def self.cpu_match: (untyped spec_platform, untyped user_platform) -> untyped + + def self.members: () -> untyped + + def self.new: (*untyped _) -> untyped + + def self.os_match: (untyped spec_platform, untyped user_platform) -> untyped + + def self.platform_version_match: (untyped spec_platform, untyped user_platform) -> untyped +end + +Bundler::GemHelpers::PlatformMatch::Elem: untyped + +Bundler::GemHelpers::PlatformMatch::EXACT_MATCH: untyped + +Bundler::GemHelpers::PlatformMatch::WORST_MATCH: untyped + +class Bundler::GemNotFound < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::GemRequireError < Bundler::BundlerError + def initialize: (untyped orig_exception, untyped msg) -> void + + def orig_exception: () -> untyped + + def status_code: () -> untyped +end + +class Bundler::GemfileError < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::GemfileEvalError < Bundler::GemfileError +end + +class Bundler::GemfileLockNotFound < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::GemfileNotFound < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::GemspecError < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::GenericSystemCallError < Bundler::BundlerError + def initialize: (untyped underlying_error, untyped message) -> void + + def status_code: () -> untyped + + def underlying_error: () -> untyped +end + +class Bundler::GitError < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::HTTPError < Bundler::BundlerError + def filter_uri: (untyped uri) -> untyped + + def status_code: () -> untyped +end + +# Handles all the fetching with the rubygems server +class Bundler::Fetcher +end + +# This error is raised if HTTP authentication is required, but not provided. +class Bundler::Fetcher::AuthenticationRequiredError < Bundler::HTTPError +end + +# This error is raised if HTTP authentication is provided, but incorrect. +class Bundler::Fetcher::BadAuthenticationError < Bundler::HTTPError +end + +# This is the error raised if +# [`OpenSSL`](https://docs.ruby-lang.org/en/2.7.0/OpenSSL.html) fails the cert +# verification +class Bundler::Fetcher::CertificateFailureError < Bundler::HTTPError +end + +# This error is raised if the API returns a 413 (only printed in verbose) +class Bundler::Fetcher::FallbackError < Bundler::HTTPError +end + +# This error is raised when it looks like the network is down +class Bundler::Fetcher::NetworkDownError < Bundler::HTTPError +end + +# This is the error raised when a source is HTTPS and +# [`OpenSSL`](https://docs.ruby-lang.org/en/2.7.0/OpenSSL.html) didn't load +class Bundler::Fetcher::SSLError < Bundler::HTTPError +end + +class Bundler::Index[out Elem] + include ::Enumerable + + def <<: (untyped spec) -> untyped + + def ==: (untyped other) -> untyped + + def []: (untyped query, ?untyped base) -> untyped + + def add_source: (untyped index) -> untyped + + def all_specs: () -> untyped + + def dependencies_eql?: (untyped spec, untyped other_spec) -> untyped + + def dependency_names: () -> untyped + + def each: () { () -> untyped } -> untyped + + def empty?: () -> untyped + + def initialize: () -> void + + def inspect: () -> untyped + + def local_search: (untyped query, ?untyped base) -> untyped + + def search: (untyped query, ?untyped base) -> untyped + + def search_all: (untyped name) -> untyped + + def size: () -> untyped + + def sort_specs: (untyped specs) -> untyped + + def sources: () -> untyped + + def spec_names: () -> untyped + + def specs: () -> untyped + + # returns a list of the dependencies + def unmet_dependency_names: () -> untyped + + def unsorted_search: (untyped query, untyped base) -> untyped + + def use: (untyped other, ?untyped override_dupes) -> untyped + + def self.build: () -> untyped + + def self.sort_specs: (untyped specs) -> untyped +end + +Bundler::Index::EMPTY_SEARCH: untyped + +Bundler::Index::NULL: untyped + +Bundler::Index::RUBY: untyped + +class Bundler::InstallError < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::InstallHookError < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::InvalidOption < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::LazySpecification + include ::Bundler::MatchPlatform + + include ::Bundler::GemHelpers + + def ==: (untyped other) -> untyped + + def __materialize__: () -> untyped + + def dependencies: () -> untyped + + def full_name: () -> untyped + + def git_version: () -> untyped + + def identifier: () -> untyped + + def initialize: (untyped name, untyped version, untyped platform, ?untyped source) -> void + + def name: () -> String + + def platform: () -> untyped + + def remote: () -> untyped + + def remote=: (untyped remote) -> untyped + + def respond_to?: (*untyped args) -> untyped + + def satisfies?: (untyped dependency) -> untyped + + def source: () -> untyped + + def source=: (untyped source) -> untyped + + def to_lock: () -> untyped + + def to_s: () -> untyped + + def version: () -> String +end + +class Bundler::LazySpecification::Identifier < Struct + include ::Comparable + + extend ::T::Generic + + def <=>: (untyped other) -> untyped + + def dependencies: () -> untyped + + def dependencies=: (untyped _) -> untyped + + def name: () -> untyped + + def name=: (untyped _) -> untyped + + def platform: () -> untyped + + def platform=: (untyped _) -> untyped + + def platform_string: () -> untyped + + def source: () -> untyped + + def source=: (untyped _) -> untyped + + def version: () -> untyped + + def version=: (untyped _) -> untyped + + def self.[]: (*untyped _) -> untyped + + def self.members: () -> untyped + + def self.new: (*untyped _) -> untyped +end + +Bundler::LazySpecification::Identifier::Elem: untyped + +class Bundler::LockfileError < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::LockfileParser + def bundler_version: () -> untyped + + def dependencies: () -> Hash[String, Bundler::Dependency] + + def initialize: (untyped lockfile) -> void + + def platforms: () -> untyped + + def ruby_version: () -> untyped + + def sources: () -> untyped + + def specs: () -> ::Array[::Bundler::LazySpecification] + + def warn_for_outdated_bundler_version: () -> untyped + + def self.sections_in_lockfile: (untyped lockfile_contents) -> untyped + + def self.sections_to_ignore: (?untyped base_version) -> untyped + + def self.unknown_sections_in_lockfile: (untyped lockfile_contents) -> untyped +end + +Bundler::LockfileParser::BUNDLED: untyped + +Bundler::LockfileParser::DEPENDENCIES: untyped + +Bundler::LockfileParser::ENVIRONMENT_VERSION_SECTIONS: untyped + +Bundler::LockfileParser::GEM: untyped + +Bundler::LockfileParser::GIT: untyped + +Bundler::LockfileParser::KNOWN_SECTIONS: untyped + +Bundler::LockfileParser::NAME_VERSION: untyped + +Bundler::LockfileParser::OPTIONS: untyped + +Bundler::LockfileParser::PATH: untyped + +Bundler::LockfileParser::PLATFORMS: untyped + +Bundler::LockfileParser::PLUGIN: untyped + +Bundler::LockfileParser::RUBY: untyped + +Bundler::LockfileParser::SECTIONS_BY_VERSION_INTRODUCED: untyped + +Bundler::LockfileParser::SOURCE: untyped + +Bundler::LockfileParser::SPECS: untyped + +Bundler::LockfileParser::TYPES: untyped + +class Bundler::MarshalError < StandardError +end + +module Bundler::MatchPlatform + include ::Bundler::GemHelpers + + def match_platform: (untyped p) -> untyped + + def self.platforms_match?: (untyped gemspec_platform, untyped local_platform) -> untyped +end + +# [`Bundler::Molinillo`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Molinillo.html) +# is a generic dependency resolution algorithm. +module Bundler::Molinillo +end + +Bundler::Molinillo::VERSION: untyped + +# An error caused by attempting to fulfil a dependency that was circular +# +# @note This exception will be thrown iff a {Vertex} is added to a +# +# ``` +# {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an +# existing {DependencyGraph::Vertex} +# ``` +class Bundler::Molinillo::CircularDependencyError < Bundler::Molinillo::ResolverError + # [`Set`](https://docs.ruby-lang.org/en/2.7.0/Set.html) + # : the dependencies responsible for causing the error + def dependencies: () -> untyped + + def initialize: (untyped vertices) -> void +end + +# Hacks needed for old Ruby versions. +module Bundler::Molinillo::Compatibility + def self.flat_map: (untyped enum) { () -> untyped } -> untyped +end + +# @!visibility private +module Bundler::Molinillo::Delegates +end + +# [`Delegates`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Molinillo/Delegates.html) +# all {Bundler::Molinillo::ResolutionState} methods to a `#state` property. +module Bundler::Molinillo::Delegates::ResolutionState + # (see Bundler::Molinillo::ResolutionState#activated) + def activated: () -> untyped + + # (see Bundler::Molinillo::ResolutionState#conflicts) + def conflicts: () -> untyped + + # (see Bundler::Molinillo::ResolutionState#depth) + def depth: () -> untyped + + # (see Bundler::Molinillo::ResolutionState#name) + def name: () -> untyped + + # (see Bundler::Molinillo::ResolutionState#possibilities) + def possibilities: () -> untyped + + # (see Bundler::Molinillo::ResolutionState#requirement) + def requirement: () -> untyped + + # (see Bundler::Molinillo::ResolutionState#requirements) + def requirements: () -> untyped + + # (see Bundler::Molinillo::ResolutionState#unused\_unwind\_options) + def unused_unwind_options: () -> untyped +end + +# [`Delegates`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Molinillo/Delegates.html) +# all {Bundler::Molinillo::SpecificationProvider} methods to a +# `#specification\_provider` property. +module Bundler::Molinillo::Delegates::SpecificationProvider + def allow_missing?: (untyped dependency) -> untyped + + def dependencies_for: (untyped specification) -> untyped + + def name_for: (untyped dependency) -> untyped + + # (see + # [`Bundler::Molinillo::SpecificationProvider#name_for_explicit_dependency_source`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Molinillo/SpecificationProvider.html#method-i-name_for_explicit_dependency_source)) + def name_for_explicit_dependency_source: () -> untyped + + # (see + # [`Bundler::Molinillo::SpecificationProvider#name_for_locking_dependency_source`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Molinillo/SpecificationProvider.html#method-i-name_for_locking_dependency_source)) + def name_for_locking_dependency_source: () -> untyped + + def requirement_satisfied_by?: (untyped requirement, untyped activated, untyped spec) -> untyped + + def search_for: (untyped dependency) -> untyped + + def sort_dependencies: (untyped dependencies, untyped activated, untyped conflicts) -> untyped +end + +# A directed acyclic graph that is tuned to hold named dependencies +class Bundler::Molinillo::DependencyGraph[out Elem] + include ::TSort + + include ::Enumerable + + def ==: (untyped other) -> untyped + + def add_child_vertex: (untyped name, untyped payload, untyped parent_names, untyped requirement) -> untyped + + def add_edge: (untyped origin, untyped destination, untyped requirement) -> untyped + + def add_vertex: (untyped name, untyped payload, ?untyped root) -> untyped + + def delete_edge: (untyped edge) -> untyped + + def detach_vertex_named: (untyped name) -> untyped + + def each: () { () -> untyped } -> untyped + + def initialize: () -> void + + # @return [String] a string suitable for debugging + def inspect: () -> untyped + + # @return [Log] the op log for this graph + def log: () -> untyped + + def rewind_to: (untyped tag) -> untyped + + def root_vertex_named: (untyped name) -> untyped + + def set_payload: (untyped name, untyped payload) -> untyped + + def tag: (untyped tag) -> untyped + + def to_dot: (?untyped options) -> untyped + + def tsort_each_child: (untyped vertex) { () -> untyped } -> untyped + + # @!visibility private + # + # Alias for: + # [`each`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Molinillo/DependencyGraph.html#method-i-each) + def tsort_each_node: () -> untyped + + def vertex_named: (untyped name) -> untyped + + # @return [{String => Vertex}] the vertices of the dependency graph, keyed + # + # ``` + # by {Vertex#name} + # ``` + def vertices: () -> untyped + + def self.tsort: (untyped vertices) -> untyped +end + +# An action that modifies a {DependencyGraph} that is reversible. @abstract +class Bundler::Molinillo::DependencyGraph::Action + def down: (untyped graph) -> untyped + + # @return [Action,Nil] The next action + def next: () -> untyped + + def next=: (untyped _) -> untyped + + # @return [Action,Nil] The previous action + def previous: () -> untyped + + def previous=: (untyped previous) -> untyped + + def up: (untyped graph) -> untyped + + # @return [Symbol] The name of the action. + def self.action_name: () -> untyped +end + +# @!visibility private (see +# [`DependencyGraph#add_edge_no_circular`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Molinillo/DependencyGraph.html#method-i-add_edge_no_circular)) +class Bundler::Molinillo::DependencyGraph::AddEdgeNoCircular < Bundler::Molinillo::DependencyGraph::Action + # @return [String] the name of the destination of the edge + def destination: () -> untyped + + def down: (untyped graph) -> untyped + + def initialize: (untyped origin, untyped destination, untyped requirement) -> void + + def make_edge: (untyped graph) -> untyped + + # @return [String] the name of the origin of the edge + def origin: () -> untyped + + # @return [Object] the requirement that the edge represents + def requirement: () -> untyped + + def up: (untyped graph) -> untyped + + # (see Action.action\_name) + def self.action_name: () -> untyped +end + +class Bundler::Molinillo::DependencyGraph::AddVertex < Bundler::Molinillo::DependencyGraph::Action + def down: (untyped graph) -> untyped + + def initialize: (untyped name, untyped payload, untyped root) -> void + + def name: () -> untyped + + def payload: () -> untyped + + def root: () -> untyped + + def up: (untyped graph) -> untyped + + def self.action_name: () -> untyped +end + +# @!visibility private (see +# [`DependencyGraph#delete_edge`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Molinillo/DependencyGraph.html#method-i-delete_edge)) +class Bundler::Molinillo::DependencyGraph::DeleteEdge < Bundler::Molinillo::DependencyGraph::Action + # @return [String] the name of the destination of the edge + def destination_name: () -> untyped + + def down: (untyped graph) -> untyped + + def initialize: (untyped origin_name, untyped destination_name, untyped requirement) -> void + + def make_edge: (untyped graph) -> untyped + + # @return [String] the name of the origin of the edge + def origin_name: () -> untyped + + # @return [Object] the requirement that the edge represents + def requirement: () -> untyped + + def up: (untyped graph) -> untyped + + # (see Action.action\_name) + def self.action_name: () -> untyped +end + +# @!visibility private @see +# [`DependencyGraph#detach_vertex_named`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Molinillo/DependencyGraph.html#method-i-detach_vertex_named) +class Bundler::Molinillo::DependencyGraph::DetachVertexNamed < Bundler::Molinillo::DependencyGraph::Action + def down: (untyped graph) -> untyped + + def initialize: (untyped name) -> void + + # @return [String] the name of the vertex to detach + def name: () -> untyped + + def up: (untyped graph) -> untyped + + # (see Action#name) + def self.action_name: () -> untyped +end + +# A directed edge of a {DependencyGraph} @attr [Vertex] origin The origin of the +# directed edge @attr [Vertex] destination The destination of the directed edge +# @attr [Object] requirement The requirement the directed edge represents +class Bundler::Molinillo::DependencyGraph::Edge < Struct + def destination: () -> untyped + + def destination=: (untyped _) -> untyped + + def origin: () -> untyped + + def origin=: (untyped _) -> untyped + + def requirement: () -> untyped + + def requirement=: (untyped _) -> untyped + + def self.[]: (*untyped _) -> untyped + + def self.members: () -> untyped + + def self.new: (*untyped _) -> untyped +end + +Bundler::Molinillo::DependencyGraph::Edge::Elem: untyped + +# A log for dependency graph actions +class Bundler::Molinillo::DependencyGraph::Log + extend ::Enumerable + + def add_edge_no_circular: (untyped graph, untyped origin, untyped destination, untyped requirement) -> untyped + + def add_vertex: (untyped graph, untyped name, untyped payload, untyped root) -> untyped + + def delete_edge: (untyped graph, untyped origin_name, untyped destination_name, untyped requirement) -> untyped + + def detach_vertex_named: (untyped graph, untyped name) -> untyped + + def each: () { () -> untyped } -> untyped + + def initialize: () -> void + + def pop!: (untyped graph) -> untyped + + # @!visibility private Enumerates each action in the log in reverse order + # @yield [Action] + def reverse_each: () -> untyped + + def rewind_to: (untyped graph, untyped tag) -> untyped + + def set_payload: (untyped graph, untyped name, untyped payload) -> untyped + + def tag: (untyped graph, untyped tag) -> untyped +end + +Bundler::Molinillo::DependencyGraph::Log::Elem: untyped + +class Bundler::Molinillo::DependencyGraph::SetPayload < Bundler::Molinillo::DependencyGraph::Action + def down: (untyped graph) -> untyped + + def initialize: (untyped name, untyped payload) -> void + + def name: () -> untyped + + def payload: () -> untyped + + def up: (untyped graph) -> untyped + + def self.action_name: () -> untyped +end + +# @!visibility private @see +# [`DependencyGraph#tag`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Molinillo/DependencyGraph.html#method-i-tag) +class Bundler::Molinillo::DependencyGraph::Tag < Bundler::Molinillo::DependencyGraph::Action + def down: (untyped _graph) -> untyped + + def initialize: (untyped tag) -> void + + # @return [Object] An opaque tag + def tag: () -> untyped + + def up: (untyped _graph) -> untyped + + # (see Action.action\_name) + def self.action_name: () -> untyped +end + +# A vertex in a {DependencyGraph} that encapsulates a {#name} and a {#payload} +class Bundler::Molinillo::DependencyGraph::Vertex + def ==: (untyped other) -> untyped + + def _path_to?: (untyped other, ?untyped visited) -> untyped + + def ancestor?: (untyped other) -> untyped + + def descendent?: (untyped other) -> untyped + + def eql?: (untyped other) -> untyped + + # @return [Array] the explicit requirements that required + # + # ```ruby + # this vertex + # ``` + def explicit_requirements: () -> untyped + + # @return [Fixnum] a hash for the vertex based upon its {#name} + def hash: () -> untyped + + # @return [Array] the edges of {#graph} that have `self` as their + # + # ``` + # {Edge#destination} + # ``` + def incoming_edges: () -> untyped + + def incoming_edges=: (untyped incoming_edges) -> untyped + + def initialize: (untyped name, untyped payload) -> void + + # @return [String] a string suitable for debugging + def inspect: () -> untyped + + def is_reachable_from?: (untyped other) -> untyped + + # @return [String] the name of the vertex + def name: () -> untyped + + def name=: (untyped name) -> untyped + + # @return [Array] the edges of {#graph} that have `self` as their + # + # ``` + # {Edge#origin} + # ``` + def outgoing_edges: () -> untyped + + def outgoing_edges=: (untyped outgoing_edges) -> untyped + + def path_to?: (untyped other) -> untyped + + # @return [Object] the payload the vertex holds + def payload: () -> untyped + + def payload=: (untyped payload) -> untyped + + # @return [Array] the vertices of {#graph} that have an edge with + # + # ``` + # `self` as their {Edge#destination} + # ``` + def predecessors: () -> untyped + + # @return [Set] the vertices of {#graph} where `self` is a + # + # ``` + # {#descendent?} + # ``` + def recursive_predecessors: () -> untyped + + # @return [Set] the vertices of {#graph} where `self` is an + # + # ``` + # {#ancestor?} + # ``` + def recursive_successors: () -> untyped + + # @return [Array] all of the requirements that required + # + # ```ruby + # this vertex + # ``` + def requirements: () -> untyped + + # @return [Boolean] whether the vertex is considered a root vertex + def root: () -> untyped + + def root=: (untyped root) -> untyped + + # @return [Boolean] whether the vertex is considered a root vertex + def root?: () -> untyped + + def shallow_eql?: (untyped other) -> untyped + + # @return [Array] the vertices of {#graph} that have an edge with + # + # ``` + # `self` as their {Edge#origin} + # ``` + def successors: () -> untyped +end + +# A state that encapsulates a set of {#requirements} with an {Array} of +# possibilities +class Bundler::Molinillo::DependencyState < Bundler::Molinillo::ResolutionState + # Removes a possibility from `self` @return [PossibilityState] a state with a + # single possibility, + # + # ```ruby + # the possibility that was removed from `self` + # ``` + def pop_possibility_state: () -> untyped +end + +Bundler::Molinillo::DependencyState::Elem: untyped + +# An error caused by searching for a dependency that is completely unknown, i.e. +# has no versions available whatsoever. +class Bundler::Molinillo::NoSuchDependencyError < Bundler::Molinillo::ResolverError + # @return [Object] the dependency that could not be found + def dependency: () -> untyped + + def dependency=: (untyped dependency) -> untyped + + def initialize: (untyped dependency, ?untyped required_by) -> void + + # The error message for the missing dependency, including the specifications + # that had this dependency. + def message: () -> untyped + + # @return [Array] the specifications that depended upon {#dependency} + def required_by: () -> untyped + + def required_by=: (untyped required_by) -> untyped +end + +# A state that encapsulates a single possibility to fulfill the given +# {#requirement} +class Bundler::Molinillo::PossibilityState < Bundler::Molinillo::ResolutionState +end + +Bundler::Molinillo::PossibilityState::Elem: untyped + +class Bundler::Molinillo::ResolutionState < Struct + def activated: () -> untyped + + def activated=: (untyped _) -> untyped + + def conflicts: () -> untyped + + def conflicts=: (untyped _) -> untyped + + def depth: () -> untyped + + def depth=: (untyped _) -> untyped + + def name: () -> untyped + + def name=: (untyped _) -> untyped + + def possibilities: () -> untyped + + def possibilities=: (untyped _) -> untyped + + def requirement: () -> untyped + + def requirement=: (untyped _) -> untyped + + def requirements: () -> untyped + + def requirements=: (untyped _) -> untyped + + def unused_unwind_options: () -> untyped + + def unused_unwind_options=: (untyped _) -> untyped + + def self.[]: (*untyped _) -> untyped + + # Returns an empty resolution state @return [ResolutionState] an empty state + def self.empty: () -> untyped + + def self.members: () -> untyped + + def self.new: (*untyped _) -> untyped +end + +Bundler::Molinillo::ResolutionState::Elem: untyped + +# This class encapsulates a dependency resolver. The resolver is responsible for +# determining which set of dependencies to activate, with feedback from the +# {#specification\_provider} +class Bundler::Molinillo::Resolver + def initialize: (untyped specification_provider, untyped resolver_ui) -> void + + def resolve: (untyped requested, ?untyped base) -> untyped + + # @return [UI] the UI module used to communicate back to the user + # + # ```ruby + # during the resolution process + # ``` + def resolver_ui: () -> untyped + + # @return [SpecificationProvider] the specification provider used + # + # ``` + # in the resolution process + # ``` + def specification_provider: () -> untyped +end + +# A specific resolution from a given {Resolver} +class Bundler::Molinillo::Resolver::Resolution + include ::Bundler::Molinillo::Delegates::SpecificationProvider + + include ::Bundler::Molinillo::Delegates::ResolutionState + + # @return [DependencyGraph] the base dependency graph to which + # + # ```ruby + # dependencies should be 'locked' + # ``` + def base: () -> untyped + + def initialize: (untyped specification_provider, untyped resolver_ui, untyped requested, untyped base) -> void + + def iteration_rate=: (untyped iteration_rate) -> untyped + + # @return [Array] the dependencies that were explicitly required + def original_requested: () -> untyped + + # Resolves the {#original\_requested} dependencies into a full dependency + # + # ```ruby + # graph + # ``` + # + # @raise [ResolverError] if successful resolution is impossible @return + # [DependencyGraph] the dependency graph of successfully resolved + # + # ```ruby + # dependencies + # ``` + def resolve: () -> untyped + + # @return [UI] the UI that knows how to communicate feedback about the + # + # ```ruby + # resolution process back to the user + # ``` + def resolver_ui: () -> untyped + + # @return [SpecificationProvider] the provider that knows about + # + # ``` + # dependencies, requirements, specifications, versions, etc. + # ``` + def specification_provider: () -> untyped + + def started_at=: (untyped started_at) -> untyped + + def states=: (untyped states) -> untyped +end + +class Bundler::Molinillo::Resolver::Resolution::Conflict < Struct + def activated_by_name: () -> untyped + + def activated_by_name=: (untyped _) -> untyped + + def existing: () -> untyped + + def existing=: (untyped _) -> untyped + + def locked_requirement: () -> untyped + + def locked_requirement=: (untyped _) -> untyped + + # @return [Object] a spec that was unable to be activated due to a conflict + def possibility: () -> untyped + + def possibility_set: () -> untyped + + def possibility_set=: (untyped _) -> untyped + + def requirement: () -> untyped + + def requirement=: (untyped _) -> untyped + + def requirement_trees: () -> untyped + + def requirement_trees=: (untyped _) -> untyped + + def requirements: () -> untyped + + def requirements=: (untyped _) -> untyped + + def underlying_error: () -> untyped + + def underlying_error=: (untyped _) -> untyped + + def self.[]: (*untyped _) -> untyped + + def self.members: () -> untyped + + def self.new: (*untyped _) -> untyped +end + +Bundler::Molinillo::Resolver::Resolution::Conflict::Elem: untyped + +class Bundler::Molinillo::Resolver::Resolution::PossibilitySet < Struct + def dependencies: () -> untyped + + def dependencies=: (untyped _) -> untyped + + # @return [Object] most up-to-date dependency in the possibility set + def latest_version: () -> untyped + + def possibilities: () -> untyped + + def possibilities=: (untyped _) -> untyped + + # [`String`](https://docs.ruby-lang.org/en/2.7.0/String.html) representation + # of the possibility set, for debugging + def to_s: () -> untyped + + def self.[]: (*untyped _) -> untyped + + def self.members: () -> untyped + + def self.new: (*untyped _) -> untyped +end + +Bundler::Molinillo::Resolver::Resolution::PossibilitySet::Elem: untyped + +class Bundler::Molinillo::Resolver::Resolution::UnwindDetails < Struct + include ::Comparable + + def <=>: (untyped other) -> untyped + + # @return [Array] array of all the requirements that led to the need for + # + # ```ruby + # this unwind + # ``` + def all_requirements: () -> untyped + + def conflicting_requirements: () -> untyped + + def conflicting_requirements=: (untyped _) -> untyped + + def requirement_tree: () -> untyped + + def requirement_tree=: (untyped _) -> untyped + + def requirement_trees: () -> untyped + + def requirement_trees=: (untyped _) -> untyped + + def requirements_unwound_to_instead: () -> untyped + + def requirements_unwound_to_instead=: (untyped _) -> untyped + + # @return [Integer] index of state requirement in reversed requirement tree + # + # ```ruby + # (the conflicting requirement itself will be at position 0) + # ``` + def reversed_requirement_tree_index: () -> untyped + + def state_index: () -> untyped + + def state_index=: (untyped _) -> untyped + + def state_requirement: () -> untyped + + def state_requirement=: (untyped _) -> untyped + + # @return [Array] array of sub-dependencies to avoid when choosing a + # + # ``` + # new possibility for the state we've unwound to. Only relevant for + # non-primary unwinds + # ``` + def sub_dependencies_to_avoid: () -> untyped + + # @return [Boolean] where the requirement of the state we're unwinding + # + # ``` + # to directly caused the conflict. Note: in this case, it is + # impossible for the state we're unwinding to to be a parent of + # any of the other conflicting requirements (or we would have + # circularity) + # ``` + def unwinding_to_primary_requirement?: () -> untyped + + def self.[]: (*untyped _) -> untyped + + def self.members: () -> untyped + + def self.new: (*untyped _) -> untyped +end + +Bundler::Molinillo::Resolver::Resolution::UnwindDetails::Elem: untyped + +# An error that occurred during the resolution process +class Bundler::Molinillo::ResolverError < StandardError +end + +# Provides information about specifications and dependencies to the resolver, +# allowing the {Resolver} class to remain generic while still providing power +# and flexibility. +# +# This module contains the methods that users of +# [`Bundler::Molinillo`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Molinillo.html) +# must to implement, using knowledge of their own model classes. +module Bundler::Molinillo::SpecificationProvider + def allow_missing?: (untyped dependency) -> untyped + + def dependencies_for: (untyped specification) -> untyped + + def name_for: (untyped dependency) -> untyped + + # @return [String] the name of the source of explicit dependencies, i.e. + # + # ``` + # those passed to {Resolver#resolve} directly. + # ``` + def name_for_explicit_dependency_source: () -> untyped + + # @return [String] the name of the source of 'locked' dependencies, i.e. + # + # ``` + # those passed to {Resolver#resolve} directly as the `base` + # ``` + def name_for_locking_dependency_source: () -> untyped + + def requirement_satisfied_by?: (untyped requirement, untyped activated, untyped spec) -> untyped + + def search_for: (untyped dependency) -> untyped + + def sort_dependencies: (untyped dependencies, untyped activated, untyped conflicts) -> untyped +end + +# Conveys information about the resolution process to a user. +module Bundler::Molinillo::UI + # Called after resolution ends (either successfully or with an error). By + # default, prints a newline. + # + # @return [void] + def after_resolution: () -> untyped + + # Called before resolution begins. + # + # @return [void] + def before_resolution: () -> untyped + + def debug: (?untyped depth) -> untyped + + # Whether or not debug messages should be printed. By default, whether or not + # the `MOLINILLO\_DEBUG` environment variable is set. + # + # @return [Boolean] + def debug?: () -> untyped + + # Called roughly every {#progress\_rate}, this method should convey progress + # to the user. + # + # @return [void] + def indicate_progress: () -> untyped + + # The {IO} object that should be used to print output. `STDOUT`, by default. + # + # @return [IO] + def output: () -> untyped + + # How often progress should be conveyed to the user via {#indicate\_progress}, + # in seconds. A third of a second, by default. + # + # @return [Float] + def progress_rate: () -> untyped +end + +# An error caused by conflicts in version +class Bundler::Molinillo::VersionConflict < Bundler::Molinillo::ResolverError + include ::Bundler::Molinillo::Delegates::SpecificationProvider + + # @return [{String => Resolution::Conflict}] the conflicts that caused + # + # ```ruby + # resolution to fail + # ``` + def conflicts: () -> untyped + + def initialize: (untyped conflicts, untyped specification_provider) -> void + + def message_with_trees: (?untyped opts) -> untyped + + # @return [SpecificationProvider] the specification provider used during + # + # ```ruby + # resolution + # ``` + def specification_provider: () -> untyped +end + +class Bundler::NoSpaceOnDeviceError < Bundler::PermissionError + def message: () -> untyped + + def status_code: () -> untyped +end + +class Bundler::OperationNotSupportedError < Bundler::PermissionError + def message: () -> untyped + + def status_code: () -> untyped +end + +class Bundler::PathError < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::PermissionError < Bundler::BundlerError + def action: () -> untyped + + def initialize: (untyped path, ?untyped permission_type) -> void + + def message: () -> untyped + + def status_code: () -> untyped +end + +# This is the interfacing class represents the API that we intend to provide the +# plugins to use. +# +# For plugins to be independent of the +# [`Bundler`](https://docs.ruby-lang.org/en/2.7.0/Bundler.html) internals they +# shall limit their interactions to methods of this class only. This will save +# them from breaking when some internal change. +# +# Currently we are delegating the methods defined in +# [`Bundler`](https://docs.ruby-lang.org/en/2.7.0/Bundler.html) class to itself. +# So, this class acts as a buffer. +# +# If there is some change in the +# [`Bundler`](https://docs.ruby-lang.org/en/2.7.0/Bundler.html) class that is +# incompatible to its previous behavior or if otherwise desired, we can +# reimplement(or implement) the method to preserve compatibility. +# +# To use this, either the class can inherit this class or use it directly. For +# example of both types of use, refer the file `spec/plugins/command.rb` +# +# To use it without inheriting, you will have to create an object of this to use +# the functions (except for declaration functions like command, source, and +# hooks). +# Manages which plugins are installed and their sources. This also is supposed +# to map which plugin does what (currently the features are not implemented so +# this class is now a stub class). +# Handles the installation of plugin in appropriate directories. +# +# This class is supposed to be wrapper over the existing gem installation infra +# but currently it itself handles everything as the Source's subclasses (e.g. +# Source::RubyGems) are heavily dependent on the Gemfile. +# SourceList object to be used while parsing the Gemfile, setting the +# approptiate options to be used with Source classes for plugin installation +module Bundler::Plugin + def self.add_command: (untyped command, untyped cls) -> untyped + + def self.add_hook: (untyped event) { () -> untyped } -> untyped + + def self.add_source: (untyped source, untyped cls) -> untyped + + def self.cache: () -> untyped + + def self.command?: (untyped command) -> untyped + + def self.exec_command: (untyped command, untyped args) -> untyped + + def self.gemfile_install: (?untyped gemfile) { () -> untyped } -> untyped + + def self.global_root: () -> untyped + + def self.hook: (untyped event, *untyped args) { () -> untyped } -> untyped + + def self.index: () -> untyped + + def self.install: (untyped names, untyped options) -> untyped + + def self.installed?: (untyped plugin) -> untyped + + def self.local_root: () -> untyped + + def self.reset!: () -> untyped + + def self.root: () -> untyped + + def self.source: (untyped name) -> untyped + + def self.source?: (untyped name) -> untyped + + def self.source_from_lock: (untyped locked_opts) -> untyped +end + +Bundler::Plugin::PLUGIN_FILE_NAME: untyped + +class Bundler::Plugin::API + # The cache dir to be used by the plugins for storage + # + # @return [Pathname] path of the cache dir + def cache_dir: () -> untyped + + def method_missing: (untyped name, *untyped args) { () -> untyped } -> untyped + + def tmp: (*untyped names) -> untyped + + def self.command: (untyped command, ?untyped cls) -> untyped + + def self.hook: (untyped event) { () -> untyped } -> untyped + + def self.source: (untyped source, ?untyped cls) -> untyped +end + +# Dsl to parse the Gemfile looking for plugins to install +class Bundler::Plugin::DSL < Bundler::Dsl + def _gem: (untyped name, *untyped args) -> untyped + + # This lists the plugins that was added automatically and not specified by the + # user. + # + # When we encounter :type attribute with a source block, we add a plugin by + # name bundler-source- to list of plugins to be installed. + # + # These plugins are optional and are not installed when there is conflict with + # any other plugin. + def inferred_plugins: () -> untyped + + def plugin: (untyped name, *untyped args) -> untyped +end + +module Bundler::Plugin::Events +end + +class Bundler::Plugin::Index +end + +class Bundler::Plugin::MalformattedPlugin < Bundler::PluginError +end + +class Bundler::Plugin::UndefinedCommandError < Bundler::PluginError +end + +class Bundler::Plugin::UnknownSourceError < Bundler::PluginError +end + +class Bundler::Plugin::DSL::PluginGemfileError < Bundler::PluginError +end + +class Bundler::PluginError < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::ProductionError < Bundler::BundlerError + def status_code: () -> untyped +end + +# Represents a lazily loaded gem specification, where the full specification is +# on the source server in rubygems' "quick" index. The proxy object is to be +# seeded with what we're given from the source's abbreviated index - the full +# specification will only be fetched when necessary. +class Bundler::RemoteSpecification + include ::Comparable + + include ::Bundler::MatchPlatform + + include ::Bundler::GemHelpers + + def <=>: (untyped other) -> untyped + + def __swap__: (untyped spec) -> untyped + + def dependencies: () -> untyped + + def dependencies=: (untyped dependencies) -> untyped + + # Needed before installs, since the arch matters then and quick specs don't + # bother to include the arch in the platform string + def fetch_platform: () -> untyped + + def full_name: () -> untyped + + def git_version: () -> untyped + + def initialize: (untyped name, untyped version, untyped platform, untyped spec_fetcher) -> void + + def name: () -> untyped + + def platform: () -> untyped + + def remote: () -> untyped + + def remote=: (untyped remote) -> untyped + + def respond_to?: (untyped method, ?untyped include_all) -> untyped + + # Create a delegate used for sorting. This strategy is copied from RubyGems + # 2.23 and ensures that Bundler's specifications can be compared and sorted + # with RubyGems' own specifications. + # + # @see #<=> @see + # [`Gem::Specification#sort_obj`](https://docs.ruby-lang.org/en/2.7.0/Gem/Specification.html#method-i-sort_obj) + # + # @return [Array] an object you can use to compare and sort this + # + # ```ruby + # specification against other specifications + # ``` + def sort_obj: () -> untyped + + def source: () -> untyped + + def source=: (untyped source) -> untyped + + def to_s: () -> untyped + + def version: () -> untyped +end + +class Bundler::Resolver + include ::Bundler::Molinillo::SpecificationProvider + + include ::Bundler::Molinillo::UI + + def after_resolution: () -> untyped + + def before_resolution: () -> untyped + + def debug: (?untyped depth) -> untyped + + def debug?: () -> untyped + + def dependencies_for: (untyped specification) -> untyped + + def index_for: (untyped dependency) -> untyped + + def indicate_progress: () -> untyped + + def initialize: (untyped index, untyped source_requirements, untyped base, untyped gem_version_promoter, untyped additional_base_requirements, untyped platforms) -> void + + def name_for: (untyped dependency) -> untyped + + def name_for_explicit_dependency_source: () -> untyped + + def name_for_locking_dependency_source: () -> untyped + + def relevant_sources_for_vertex: (untyped vertex) -> untyped + + def requirement_satisfied_by?: (untyped requirement, untyped activated, untyped spec) -> untyped + + def search_for: (untyped dependency) -> untyped + + def sort_dependencies: (untyped dependencies, untyped activated, untyped conflicts) -> untyped + + def start: (untyped requirements) -> untyped + + def self.platform_sort_key: (untyped platform) -> untyped + + def self.resolve: (untyped requirements, untyped index, ?untyped source_requirements, ?untyped base, ?untyped gem_version_promoter, ?untyped additional_base_requirements, ?untyped platforms) -> untyped + + def self.sort_platforms: (untyped platforms) -> untyped +end + +class Bundler::Resolver::SpecGroup + include ::Bundler::GemHelpers + + def ==: (untyped other) -> untyped + + def activate_platform!: (untyped platform) -> untyped + + def dependencies_for_activated_platforms: () -> untyped + + def eql?: (untyped other) -> untyped + + def for?: (untyped platform) -> untyped + + def hash: () -> untyped + + def ignores_bundler_dependencies: () -> untyped + + def ignores_bundler_dependencies=: (untyped ignores_bundler_dependencies) -> untyped + + def initialize: (untyped all_specs) -> void + + def name: () -> untyped + + def name=: (untyped name) -> untyped + + def source: () -> untyped + + def source=: (untyped source) -> untyped + + def to_s: () -> untyped + + def to_specs: () -> untyped + + def version: () -> untyped + + def version=: (untyped version) -> untyped +end + +module Bundler::RubyDsl + def ruby: (*untyped ruby_version) -> untyped +end + +class Bundler::RubyVersion + def ==: (untyped other) -> untyped + + def diff: (untyped other) -> untyped + + def engine: () -> untyped + + def engine_gem_version: () -> untyped + + def engine_versions: () -> untyped + + def exact?: () -> untyped + + def gem_version: () -> untyped + + def host: () -> untyped + + def initialize: (untyped versions, untyped patchlevel, untyped engine, untyped engine_version) -> void + + def patchlevel: () -> untyped + + def single_version_string: () -> untyped + + def to_gem_version_with_patchlevel: () -> untyped + + def to_s: (?untyped versions) -> untyped + + def versions: () -> untyped + + def versions_string: (untyped versions) -> untyped + + def self.from_string: (untyped string) -> untyped + + def self.system: () -> untyped +end + +Bundler::RubyVersion::PATTERN: untyped + +class Bundler::RubyVersionMismatch < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::RubygemsIntegration + # This backports base\_dir which replaces installation path RubyGems 1.8+ + def backport_base_dir: () -> untyped + + def backport_cache_file: () -> untyped + + # This backports the correct segment generation code from RubyGems 1.4+ by + # monkeypatching it into the method in RubyGems 1.3.6 and 1.3.7. + def backport_segment_generation: () -> untyped + + def backport_spec_file: () -> untyped + + # This backport fixes the marshaling of @segments. + def backport_yaml_initialize: () -> untyped + + def bin_path: (untyped gem, untyped bin, untyped ver) -> untyped + + def binstubs_call_gem?: () -> untyped + + def build: (untyped spec, ?untyped skip_validation) -> untyped + + def build_args: () -> untyped + + def build_args=: (untyped args) -> untyped + + def build_gem: (untyped gem_dir, untyped spec) -> untyped + + def clear_paths: () -> untyped + + def config_map: () -> untyped + + def configuration: () -> untyped + + def download_gem: (untyped spec, untyped uri, untyped path) -> untyped + + def ext_lock: () -> untyped + + def fetch_all_remote_specs: (untyped remote) -> untyped + + def fetch_prerelease_specs: () -> untyped + + def fetch_specs: (untyped all, untyped pre) { () -> untyped } -> untyped + + def gem_bindir: () -> untyped + + def gem_cache: () -> untyped + + def gem_dir: () -> untyped + + def gem_from_path: (untyped path, ?untyped policy) -> untyped + + def gem_path: () -> untyped + + def inflate: (untyped obj) -> untyped + + def initialize: () -> void + + def install_with_build_args: (untyped args) -> untyped + + def load_path_insert_index: () -> untyped + + def load_plugin_files: (untyped files) -> untyped + + def load_plugins: () -> untyped + + def loaded_gem_paths: () -> untyped + + def loaded_specs: (untyped name) -> untyped + + def mark_loaded: (untyped spec) -> untyped + + def marshal_spec_dir: () -> untyped + + def method_visibility: (untyped klass, untyped method) -> untyped + + def path: (untyped obj) -> untyped + + def path_separator: () -> untyped + + def platforms: () -> untyped + + def post_reset_hooks: () -> untyped + + def preserve_paths: () -> untyped + + def provides?: (untyped req_str) -> untyped + + def read_binary: (untyped path) -> untyped + + def redefine_method: (untyped klass, untyped method, ?untyped unbound_method) { () -> untyped } -> untyped + + def replace_bin_path: (untyped specs, untyped specs_by_name) -> untyped + + def replace_entrypoints: (untyped specs) -> untyped + + def replace_gem: (untyped specs, untyped specs_by_name) -> untyped + + # Because [`Bundler`](https://docs.ruby-lang.org/en/2.6.0/Bundler.html) has a + # static view of what specs are available, we don't refresh, so stub it out. + def replace_refresh: () -> untyped + + def repository_subdirectories: () -> untyped + + def reset: () -> untyped + + def reverse_rubygems_kernel_mixin: () -> untyped + + def ruby_engine: () -> untyped + + def security_policies: () -> untyped + + def security_policy_keys: () -> untyped + + def set_installed_by_version: (untyped spec, ?untyped installed_by_version) -> untyped + + def sources: () -> untyped + + def sources=: (untyped val) -> untyped + + def spec_cache_dirs: () -> untyped + + def spec_default_gem?: (untyped spec) -> untyped + + def spec_extension_dir: (untyped spec) -> untyped + + def spec_from_gem: (untyped path, ?untyped policy) -> untyped + + def spec_matches_for_glob: (untyped spec, untyped glob) -> untyped + + def spec_missing_extensions?: (untyped spec, ?untyped default) -> untyped + + def stub_set_spec: (untyped stub, untyped spec) -> untyped + + def stub_source_index: (untyped specs) -> untyped + + def stubs_provide_full_functionality?: () -> untyped + + def suffix_pattern: () -> untyped + + def ui=: (untyped obj) -> untyped + + def undo_replacements: () -> untyped + + def user_home: () -> untyped + + def validate: (untyped spec) -> untyped + + def version: () -> untyped + + def with_build_args: (untyped args) -> untyped + + def self.provides?: (untyped req_str) -> untyped + + def self.version: () -> untyped +end + +Bundler::RubygemsIntegration::EXT_LOCK: untyped + +# RubyGems 1.8.0 to 1.8.4 +class Bundler::RubygemsIntegration::AlmostModern < Bundler::RubygemsIntegration::Modern + # RubyGems [>= 1.8.0, < 1.8.5] has a bug that changes + # [`Gem.dir`](https://docs.ruby-lang.org/en/2.6.0/Gem.html#method-c-dir) + # whenever you call + # [`Gem::Installer#install`](https://docs.ruby-lang.org/en/2.6.0/Installer.html#method-i-install) + # with an :install\_dir set. We have to change it back for our sudo mode to + # work. + def preserve_paths: () -> untyped +end + +# RubyGems versions 1.3.6 and 1.3.7 +class Bundler::RubygemsIntegration::Ancient < Bundler::RubygemsIntegration::Legacy + def initialize: () -> void +end + +# RubyGems 2.0 +class Bundler::RubygemsIntegration::Future < Bundler::RubygemsIntegration + def all_specs: () -> untyped + + def build: (untyped spec, ?untyped skip_validation) -> untyped + + def download_gem: (untyped spec, untyped uri, untyped path) -> untyped + + def fetch_all_remote_specs: (untyped remote) -> untyped + + def fetch_specs: (untyped source, untyped remote, untyped name) -> untyped + + def find_name: (untyped name) -> untyped + + def gem_from_path: (untyped path, ?untyped policy) -> untyped + + def gem_remote_fetcher: () -> untyped + + def install_with_build_args: (untyped args) -> untyped + + def path_separator: () -> untyped + + def repository_subdirectories: () -> untyped + + def stub_rubygems: (untyped specs) -> untyped +end + +# RubyGems 1.4 through 1.6 +class Bundler::RubygemsIntegration::Legacy < Bundler::RubygemsIntegration + def all_specs: () -> untyped + + def find_name: (untyped name) -> untyped + + def initialize: () -> void + + def post_reset_hooks: () -> untyped + + def reset: () -> untyped + + def stub_rubygems: (untyped specs) -> untyped + + def validate: (untyped spec) -> untyped +end + +# RubyGems 1.8.5-1.8.19 +class Bundler::RubygemsIntegration::Modern < Bundler::RubygemsIntegration + def all_specs: () -> untyped + + def find_name: (untyped name) -> untyped + + def stub_rubygems: (untyped specs) -> untyped +end + +# RubyGems 2.1.0 +class Bundler::RubygemsIntegration::MoreFuture < Bundler::RubygemsIntegration::Future + def all_specs: () -> untyped + + # RubyGems-generated binstubs call + # [`Kernel#gem`](https://docs.ruby-lang.org/en/2.6.0/Kernel.html#method-i-gem) + def binstubs_call_gem?: () -> untyped + + def find_name: (untyped name) -> untyped + + def initialize: () -> void + + # only 2.5.2+ has all of the stub methods we want to use, and since this is a + # performance optimization *only*, we'll restrict ourselves to the most recent + # RG versions instead of all versions that have stubs + def stubs_provide_full_functionality?: () -> untyped + + def use_gemdeps: (untyped gemfile) -> untyped +end + +# RubyGems 1.8.20+ +class Bundler::RubygemsIntegration::MoreModern < Bundler::RubygemsIntegration::Modern + def build: (untyped spec, ?untyped skip_validation) -> untyped +end + +# RubyGems 1.7 +class Bundler::RubygemsIntegration::Transitional < Bundler::RubygemsIntegration::Legacy + def stub_rubygems: (untyped specs) -> untyped + + def validate: (untyped spec) -> untyped +end + +class Bundler::Runtime + include ::Bundler::SharedHelpers + + def cache: (?untyped custom_path) -> untyped + + def clean: (?untyped dry_run) -> untyped + + def current_dependencies: () -> untyped + + def dependencies: () -> untyped + + def gems: () -> untyped + + def initialize: (untyped root, untyped definition) -> void + + def lock: (?untyped opts) -> untyped + + def prune_cache: (untyped cache_path) -> untyped + + def requested_specs: () -> untyped + + def require: (*untyped groups) -> untyped + + def requires: () -> untyped + + def setup: (*untyped groups) -> untyped + + def specs: () -> untyped +end + +Bundler::Runtime::REQUIRE_ERRORS: untyped + +class Bundler::SecurityError < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::Settings + def []: (untyped name) -> untyped + + def all: () -> untyped + + def allow_sudo?: () -> untyped + + def app_cache_path: () -> untyped + + def credentials_for: (untyped uri) -> untyped + + def gem_mirrors: () -> untyped + + def ignore_config?: () -> untyped + + def initialize: (?untyped root) -> void + + def key_for: (untyped key) -> untyped + + def local_overrides: () -> untyped + + def locations: (untyped key) -> untyped + + def mirror_for: (untyped uri) -> untyped + + # for legacy reasons, in + # [`Bundler`](https://docs.ruby-lang.org/en/2.7.0/Bundler.html) 2, we do not + # respect :disable\_shared\_gems + def path: () -> untyped + + def pretty_values_for: (untyped exposed_key) -> untyped + + def set_command_option: (untyped key, untyped value) -> untyped + + def set_command_option_if_given: (untyped key, untyped value) -> untyped + + def set_global: (untyped key, untyped value) -> untyped + + def set_local: (untyped key, untyped value) -> untyped + + def temporary: (untyped update) -> untyped + + def validate!: () -> untyped + + def self.normalize_uri: (untyped uri) -> untyped +end + +Bundler::Settings::ARRAY_KEYS: untyped + +Bundler::Settings::BOOL_KEYS: untyped + +Bundler::Settings::CONFIG_REGEX: untyped + +Bundler::Settings::DEFAULT_CONFIG: untyped + +Bundler::Settings::NORMALIZE_URI_OPTIONS_PATTERN: untyped + +Bundler::Settings::NUMBER_KEYS: untyped + +Bundler::Settings::PER_URI_OPTIONS: untyped + +class Bundler::Settings::Path < Struct + def append_ruby_scope: () -> untyped + + def append_ruby_scope=: (untyped _) -> untyped + + def base_path: () -> untyped + + def base_path_relative_to_pwd: () -> untyped + + def default_install_uses_path: () -> untyped + + def default_install_uses_path=: (untyped _) -> untyped + + def explicit_path: () -> untyped + + def explicit_path=: (untyped _) -> untyped + + def path: () -> untyped + + def system_path: () -> untyped + + def system_path=: (untyped _) -> untyped + + def use_system_gems?: () -> untyped + + def validate!: () -> untyped + + def self.[]: (*untyped _) -> untyped + + def self.members: () -> untyped + + def self.new: (*untyped _) -> untyped +end + +Bundler::Settings::Path::Elem: untyped + +module Bundler::SharedHelpers + extend ::Bundler::SharedHelpers + + def chdir: (untyped dir) { () -> untyped } -> untyped + + def const_get_safely: (untyped constant_name, untyped namespace) -> untyped + + def default_bundle_dir: () -> untyped + + def default_gemfile: () -> untyped + + def default_lockfile: () -> untyped + + def digest: (untyped name) -> untyped + + def ensure_same_dependencies: (untyped spec, untyped old_deps, untyped new_deps) -> untyped + + def filesystem_access: (untyped path, ?untyped action) { () -> untyped } -> untyped + + def in_bundle?: () -> untyped + + def major_deprecation: (untyped major_version, untyped message) -> untyped + + def md5_available?: () -> untyped + + def pretty_dependency: (untyped dep, ?untyped print_source) -> untyped + + def print_major_deprecations!: () -> untyped + + def pwd: () -> untyped + + def root: () -> untyped + + def set_bundle_environment: () -> untyped + + def set_env: (untyped key, untyped value) -> untyped + + def trap: (untyped signal, ?untyped override) { () -> untyped } -> untyped + + def with_clean_git_env: () { () -> untyped } -> untyped + + def write_to_gemfile: (untyped gemfile_path, untyped contents) -> untyped +end + +class Bundler::Source + def can_lock?: (untyped spec) -> untyped + + def dependency_names: () -> untyped + + def dependency_names=: (untyped dependency_names) -> untyped + + def dependency_names_to_double_check: () -> untyped + + def double_check_for: (*untyped _) -> untyped + + def extension_cache_path: (untyped spec) -> untyped + + def include?: (untyped other) -> untyped + + def inspect: () -> untyped + + def path?: () -> untyped + + def unmet_deps: () -> untyped + + def version_message: (untyped spec) -> untyped +end + +class Bundler::Source::Gemspec < Bundler::Source::Path + def as_path_source: () -> untyped + + def gemspec: () -> untyped + + def initialize: (untyped options) -> void +end + +class Bundler::Source::Git < Bundler::Source::Path + def ==: (untyped other) -> untyped + + def allow_git_ops?: () -> untyped + + def app_cache_dirname: () -> untyped + + def branch: () -> untyped + + def cache: (untyped spec, ?untyped custom_path) -> untyped + + # This is the path which is going to contain a cache of the git repository. + # When using the same git repository across different projects, this cache + # will be shared. When using local git repos, this is set to the local repo. + def cache_path: () -> untyped + + def eql?: (untyped other) -> untyped + + def extension_dir_name: () -> untyped + + def hash: () -> untyped + + def initialize: (untyped options) -> void + + def install: (untyped spec, ?untyped options) -> untyped + + # This is the path which is going to contain a specific checkout of the git + # repository. When using local git repos, this is set to the local repo. + # + # Also aliased as: + # [`path`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Source/Git.html#method-i-path) + def install_path: () -> untyped + + def load_spec_files: () -> untyped + + def local_override!: (untyped path) -> untyped + + def name: () -> untyped + + def options: () -> untyped + + # Alias for: + # [`install_path`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Source/Git.html#method-i-install_path) + def path: () -> untyped + + def ref: () -> untyped + + def revision: () -> untyped + + def specs: (*untyped _) -> untyped + + def submodules: () -> untyped + + def to_lock: () -> untyped + + def to_s: () -> untyped + + def unlock!: () -> untyped + + def uri: () -> untyped + + def self.from_lock: (untyped options) -> untyped +end + +class Bundler::Source::Git::GitCommandError < Bundler::GitError + def initialize: (untyped command, ?untyped path, ?untyped extra_info) -> void +end + +class Bundler::Source::Git::GitNotAllowedError < Bundler::GitError + def initialize: (untyped command) -> void +end + +class Bundler::Source::Git::GitNotInstalledError < Bundler::GitError + def initialize: () -> void +end + +# The +# [`GitProxy`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Source/Git/GitProxy.html) +# is responsible to interact with git repositories. All actions required by the +# [`Git`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Source/Git.html) source is +# encapsulated in this object. +class Bundler::Source::Git::GitProxy + def branch: () -> untyped + + def checkout: () -> untyped + + def contains?: (untyped commit) -> untyped + + def copy_to: (untyped destination, ?untyped submodules) -> untyped + + def full_version: () -> untyped + + def initialize: (untyped path, untyped uri, untyped ref, ?untyped revision, ?untyped git) -> void + + def path: () -> untyped + + def path=: (untyped path) -> untyped + + def ref: () -> untyped + + def ref=: (untyped ref) -> untyped + + def revision: () -> untyped + + def revision=: (untyped revision) -> untyped + + def uri: () -> untyped + + def uri=: (untyped uri) -> untyped + + def version: () -> untyped +end + +class Bundler::Source::Git::MissingGitRevisionError < Bundler::GitError + def initialize: (untyped ref, untyped repo) -> void +end + +class Bundler::Source::Metadata < Bundler::Source + def ==: (untyped other) -> untyped + + def cached!: () -> untyped + + def eql?: (untyped other) -> untyped + + def hash: () -> untyped + + def install: (untyped spec, ?untyped _opts) -> untyped + + def options: () -> untyped + + def remote!: () -> untyped + + def specs: () -> untyped + + def to_s: () -> untyped + + def version_message: (untyped spec) -> untyped +end + +class Bundler::Source::Path < Bundler::Source + def ==: (untyped other) -> untyped + + def app_cache_dirname: () -> untyped + + def cache: (untyped spec, ?untyped custom_path) -> untyped + + def cached!: () -> untyped + + def eql?: (untyped other) -> untyped + + def expanded_original_path: () -> untyped + + def hash: () -> untyped + + def initialize: (untyped options) -> void + + def install: (untyped spec, ?untyped options) -> untyped + + def local_specs: (*untyped _) -> untyped + + def name: () -> untyped + + def name=: (untyped name) -> untyped + + def options: () -> untyped + + def original_path: () -> untyped + + def path: () -> untyped + + def remote!: () -> untyped + + def root: () -> untyped + + def root_path: () -> untyped + + def specs: () -> untyped + + def to_lock: () -> untyped + + def to_s: () -> untyped + + def version: () -> untyped + + def version=: (untyped version) -> untyped + + def self.from_lock: (untyped options) -> untyped +end + +Bundler::Source::Path::DEFAULT_GLOB: untyped + +class Bundler::Source::Rubygems < Bundler::Source + def ==: (untyped other) -> untyped + + def add_remote: (untyped source) -> untyped + + def api_fetchers: () -> untyped + + def builtin_gem?: (untyped spec) -> untyped + + def cache: (untyped spec, ?untyped custom_path) -> untyped + + def cache_path: () -> untyped + + def cached!: () -> untyped + + def cached_built_in_gem: (untyped spec) -> untyped + + def cached_gem: (untyped spec) -> untyped + + def cached_path: (untyped spec) -> untyped + + def cached_specs: () -> untyped + + def caches: () -> untyped + + def can_lock?: (untyped spec) -> untyped + + def credless_remotes: () -> untyped + + def dependency_names_to_double_check: () -> untyped + + def double_check_for: (untyped unmet_dependency_names) -> untyped + + def eql?: (untyped other) -> untyped + + def equivalent_remotes?: (untyped other_remotes) -> untyped + + def fetch_gem: (untyped spec) -> untyped + + def fetch_names: (untyped fetchers, untyped dependency_names, untyped index, untyped override_dupes) -> untyped + + def fetchers: () -> untyped + + def hash: () -> untyped + + def include?: (untyped o) -> untyped + + def initialize: (?untyped options) -> void + + def install: (untyped spec, ?untyped opts) -> untyped + + def installed?: (untyped spec) -> untyped + + def installed_specs: () -> untyped + + def loaded_from: (untyped spec) -> untyped + + # Alias for: + # [`to_s`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Source/Rubygems.html#method-i-to_s) + def name: () -> untyped + + def normalize_uri: (untyped uri) -> untyped + + def options: () -> untyped + + def remote!: () -> untyped + + def remote_specs: () -> untyped + + def remotes: () -> untyped + + def remotes_for_spec: (untyped spec) -> untyped + + def remove_auth: (untyped remote) -> untyped + + def replace_remotes: (untyped other_remotes, ?untyped allow_equivalent) -> untyped + + def requires_sudo?: () -> untyped + + def rubygems_dir: () -> untyped + + def specs: () -> untyped + + def suppress_configured_credentials: (untyped remote) -> untyped + + def to_lock: () -> untyped + + # Also aliased as: + # [`name`](https://docs.ruby-lang.org/en/2.7.0/Bundler/Source/Rubygems.html#method-i-name) + def to_s: () -> untyped + + def unmet_deps: () -> untyped + + def self.from_lock: (untyped options) -> untyped +end + +Bundler::Source::Rubygems::API_REQUEST_LIMIT: untyped + +Bundler::Source::Rubygems::API_REQUEST_SIZE: untyped + +class Bundler::SourceList + def add_git_source: (?untyped options) -> untyped + + def add_path_source: (?untyped options) -> untyped + + def add_plugin_source: (untyped source, ?untyped options) -> untyped + + def add_rubygems_remote: (untyped uri) -> untyped + + def add_rubygems_source: (?untyped options) -> untyped + + def all_sources: () -> untyped + + def cached!: () -> untyped + + def default_source: () -> untyped + + def get: (untyped source) -> untyped + + def git_sources: () -> untyped + + def global_rubygems_source: () -> untyped + + def global_rubygems_source=: (untyped uri) -> untyped + + def initialize: () -> void + + def lock_sources: () -> untyped + + def metadata_source: () -> untyped + + def path_sources: () -> untyped + + def plugin_sources: () -> untyped + + def remote!: () -> untyped + + def replace_sources!: (untyped replacement_sources) -> untyped + + def rubygems_primary_remotes: () -> untyped + + def rubygems_remotes: () -> untyped + + def rubygems_sources: () -> untyped +end + +class Bundler::SpecSet[out Elem] + include ::TSort + + include ::Enumerable + + def <<: (*untyped args) { () -> untyped } -> untyped + + def []: (untyped key) -> untyped + + def []=: (untyped key, untyped value) -> untyped + + def add: (*untyped args) { () -> untyped } -> untyped + + def each: (*untyped args) { () -> untyped } -> untyped + + def empty?: (*untyped args) { () -> untyped } -> untyped + + def find_by_name_and_platform: (untyped name, untyped platform) -> untyped + + def for: (untyped dependencies, ?untyped skip, ?untyped check, ?untyped match_current_platform, ?untyped raise_on_missing) -> untyped + + def initialize: (untyped specs) -> void + + def length: (*untyped args) { () -> untyped } -> untyped + + def materialize: (untyped deps, ?untyped missing_specs) -> untyped + + # Materialize for all the specs in the spec set, regardless of what platform + # they're for This is in contrast to how for does platform filtering (and + # specifically different from how `materialize` calls `for` only for the + # current platform) @return [Array] + def materialized_for_all_platforms: () -> untyped + + def merge: (untyped set) -> untyped + + def remove: (*untyped args) { () -> untyped } -> untyped + + def size: (*untyped args) { () -> untyped } -> untyped + + def sort!: () -> untyped + + def to_a: () -> untyped + + def to_hash: () -> untyped + + def valid_for?: (untyped deps) -> untyped + + def what_required: (untyped spec) -> untyped +end + +class Bundler::StubSpecification < Bundler::RemoteSpecification + def activated: () -> untyped + + def activated=: (untyped activated) -> untyped + + def default_gem: () -> untyped + + def default_gem?: () -> bool + + def full_gem_path: () -> untyped + + def full_require_paths: () -> untyped + + def ignored: () -> untyped + + def ignored=: (untyped ignored) -> untyped + + # This is what we do in bundler/rubygems\_ext + # [`full_require_paths`](https://docs.ruby-lang.org/en/2.6.0/Bundler/StubSpecification.html#method-i-full_require_paths) + # is always implemented in >= 2.2.0 + def load_paths: () -> untyped + + def loaded_from: () -> untyped + + def matches_for_glob: (untyped glob) -> untyped + + # This is defined directly to avoid having to load every installed spec + def missing_extensions?: () -> untyped + + def raw_require_paths: () -> untyped + + def source=: (untyped source) -> untyped + + def stub: () -> untyped + + def stub=: (untyped stub) -> untyped + + def to_yaml: () -> untyped + + def self.from_stub: (untyped stub) -> untyped +end + +class Bundler::SudoNotPermittedError < Bundler::BundlerError + def status_code: () -> untyped +end + +class Bundler::TemporaryResourceError < Bundler::PermissionError + def message: () -> untyped + + def status_code: () -> untyped +end + +class Bundler::ThreadCreationError < Bundler::BundlerError + def status_code: () -> untyped +end + +module Bundler::UI +end + +class Bundler::UI::RGProxy < Gem::SilentUI + def initialize: (untyped ui) -> void + + def say: (untyped message) -> untyped +end + +class Bundler::UI::Silent + def add_color: (untyped string, untyped color) -> untyped + + def ask: (untyped message) -> untyped + + def confirm: (untyped message, ?untyped newline) -> untyped + + def debug: (untyped message, ?untyped newline) -> untyped + + def debug?: () -> untyped + + def error: (untyped message, ?untyped newline) -> untyped + + def info: (untyped message, ?untyped newline) -> untyped + + def initialize: () -> void + + def level: (?untyped name) -> untyped + + def level=: (untyped name) -> untyped + + def no?: () -> untyped + + def quiet?: () -> untyped + + def shell=: (untyped shell) -> untyped + + def silence: () -> untyped + + def trace: (untyped message, ?untyped newline, ?untyped force) -> untyped + + def unprinted_warnings: () -> untyped + + def warn: (untyped message, ?untyped newline) -> untyped + + def yes?: (untyped msg) -> untyped +end + +module Bundler::URICredentialsFilter + def self.credential_filtered_string: (untyped str_to_filter, untyped uri) -> untyped + + def self.credential_filtered_uri: (untyped uri_to_anonymize) -> untyped +end + +# Internal error, should be rescued +class Bundler::VersionConflict < Bundler::BundlerError + def conflicts: () -> untyped + + def initialize: (untyped conflicts, ?untyped msg) -> void + + def status_code: () -> untyped +end + +class Bundler::VirtualProtocolError < Bundler::BundlerError + def message: () -> untyped + + def status_code: () -> untyped +end + +# A stub yaml serializer that can handle only hashes and strings (as of now). +module Bundler::YAMLSerializer + def self.dump: (untyped hash) -> untyped + + def self.load: (untyped str) -> untyped +end + +Bundler::YAMLSerializer::ARRAY_REGEX: untyped + +Bundler::YAMLSerializer::HASH_REGEX: untyped + +class Bundler::YamlSyntaxError < Bundler::BundlerError + def initialize: (untyped orig_exception, untyped msg) -> void + + def orig_exception: () -> untyped + + def status_code: () -> untyped +end + +class Bundler::Installer + def self.ambiguous_gems=: (untyped ambiguous_gems) -> untyped + + def self.ambiguous_gems: () -> untyped + + def post_install_messages: () -> untyped + + def self.install: (untyped root, untyped definition, ?untyped options) -> untyped + + def initialize: (untyped root, untyped definition) -> void + + def run: (untyped options) -> void + + def generate_bundler_executable_stubs: (untyped spec, ?untyped options) -> void + + def generate_standalone_bundler_executable_stubs: (untyped spec, ?untyped options) -> void +end diff --git a/rbs/fills/bundler/dsl.rbs b/rbs/fills/bundler/dsl.rbs deleted file mode 100644 index b63cd736c..000000000 --- a/rbs/fills/bundler/dsl.rbs +++ /dev/null @@ -1,200 +0,0 @@ -# -# Bundler provides a consistent environment for Ruby projects by tracking and -# installing the exact gems and versions that are needed. -# -# Bundler is a part of Ruby's standard library. -# -# Bundler is used by creating *gemfiles* listing all the project dependencies -# and (optionally) their versions and then using -# -# require 'bundler/setup' -# -# or Bundler.setup to setup environment where only specified gems and their -# specified versions could be used. -# -# See [Bundler website](https://bundler.io/docs.html) for extensive -# documentation on gemfiles creation and Bundler usage. -# -# As a standard library inside project, Bundler could be used for introspection -# of loaded and required modules. -# -module Bundler - class Dsl - @source: untyped - - @sources: untyped - - @git_sources: untyped - - @dependencies: untyped - - @groups: untyped - - @install_conditionals: untyped - - @optional_groups: untyped - - @platforms: untyped - - @env: untyped - - @ruby_version: untyped - - @gemspecs: untyped - - @gemfile: untyped - - @gemfiles: untyped - - @valid_keys: untyped - - include RubyDsl - - def self.evaluate: (untyped gemfile, untyped lockfile, untyped unlock) -> untyped - - VALID_PLATFORMS: untyped - - VALID_KEYS: ::Array["group" | "groups" | "git" | "path" | "glob" | "name" | "branch" | "ref" | "tag" | "require" | "submodules" | "platform" | "platforms" | "source" | "install_if" | "force_ruby_platform"] - - GITHUB_PULL_REQUEST_URL: ::Regexp - - GITLAB_MERGE_REQUEST_URL: ::Regexp - - attr_reader gemspecs: untyped - - attr_reader gemfile: untyped - - attr_accessor dependencies: untyped - - def initialize: () -> void - - def eval_gemfile: (untyped gemfile, ?untyped? contents) -> untyped - - def gemspec: (?path: ::String, ?glob: ::String, ?name: ::String, ?development_group: ::Symbol) -> void - - def gem: (untyped name, *untyped args) -> void - - def source: (::String source, ?type: ::Symbol) ?{ (?) -> untyped } -> void - - def git_source: (untyped name) ?{ (?) -> untyped } -> untyped - - def path: (untyped path, ?::Hash[untyped, untyped] options) ?{ (?) -> untyped } -> untyped - - def git: (untyped uri, ?::Hash[untyped, untyped] options) ?{ (?) -> untyped } -> untyped - - def github: (untyped repo, ?::Hash[untyped, untyped] options) ?{ () -> untyped } -> untyped - - def to_definition: (untyped lockfile, untyped unlock) -> untyped - - def group: (*untyped args) { () -> untyped } -> untyped - - def install_if: (*untyped args) { () -> untyped } -> untyped - - def platforms: (*untyped platforms) { () -> untyped } -> untyped - - alias platform platforms - - def env: (untyped name) { () -> untyped } -> untyped - - def plugin: (*untyped args) -> nil - - def method_missing: (untyped name, *untyped args) -> untyped - - def check_primary_source_safety: () -> untyped - - private - - def add_dependency: (untyped name, ?untyped? version, ?::Hash[untyped, untyped] options) -> (nil | untyped) - - def with_gemfile: (untyped gemfile) { (untyped) -> untyped } -> untyped - - def add_git_sources: () -> untyped - - def with_source: (untyped source) ?{ () -> untyped } -> untyped - - def normalize_hash: (untyped opts) -> untyped - - def valid_keys: () -> untyped - - def normalize_options: (untyped name, untyped version, untyped opts) -> untyped - - def normalize_group_options: (untyped opts, untyped groups) -> untyped - - def validate_keys: (untyped command, untyped opts, untyped valid_keys) -> (true | untyped) - - def normalize_source: (untyped source) -> untyped - - def deprecate_legacy_windows_platforms: (untyped platforms) -> (nil | untyped) - - def check_path_source_safety: () -> (nil | untyped) - - def check_rubygems_source_safety: () -> (untyped | nil) - - def multiple_global_source_warning: () -> untyped - - class DSLError < GemfileError - @status_code: untyped - - @description: untyped - - @dsl_path: untyped - - @backtrace: untyped - - @contents: untyped - - @to_s: untyped - - # @return [::String] the description that should be presented to the user. - # - attr_reader description: ::String - - # @return [::String] the path of the dsl file that raised the exception. - # - attr_reader dsl_path: ::String - - # @return [::Exception] the backtrace of the exception raised by the - # evaluation of the dsl file. - # - attr_reader backtrace: ::Exception - - # @param [::Exception] backtrace @see backtrace - # @param [::String] dsl_path @see dsl_path - # - def initialize: (untyped description, ::String dsl_path, ::Exception backtrace, ?untyped? contents) -> void - - def status_code: () -> untyped - - # @return [::String] the contents of the DSL that cause the exception to - # be raised. - # - def contents: () -> ::String - - # The message of the exception reports the content of podspec for the - # line that generated the original exception. - # - # @example Output - # - # Invalid podspec at `RestKit.podspec` - undefined method - # `exclude_header_search_paths=' for # - # - # from spec-repos/master/RestKit/0.9.3/RestKit.podspec:36 - # ------------------------------------------- - # # because it would break: #import - # > ns.exclude_header_search_paths = 'Code/RestKit.h' - # end - # ------------------------------------------- - # - # @return [::String] the message of the exception. - # - def to_s: () -> ::String - - private - - def parse_line_number_from_description: () -> ::Array[untyped] - end - - def gemfile_root: () -> untyped - end -end From 06fdd7631086fdfdae40f1174a5d6a9af6369903 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 19 Jul 2025 12:18:05 -0400 Subject: [PATCH 022/460] Merge in ruby_dsl.rbs --- rbs/fills/bundler/{ => 0}/bundler.rbs | 19 +++++++++++- rbs/fills/bundler/ruby_dsl.rbs | 42 --------------------------- 2 files changed, 18 insertions(+), 43 deletions(-) rename rbs/fills/bundler/{ => 0}/bundler.rbs (99%) delete mode 100644 rbs/fills/bundler/ruby_dsl.rbs diff --git a/rbs/fills/bundler/bundler.rbs b/rbs/fills/bundler/0/bundler.rbs similarity index 99% rename from rbs/fills/bundler/bundler.rbs rename to rbs/fills/bundler/0/bundler.rbs index f60fe3837..4af422af1 100644 --- a/rbs/fills/bundler/bundler.rbs +++ b/rbs/fills/bundler/0/bundler.rbs @@ -3192,7 +3192,24 @@ class Bundler::Resolver::SpecGroup end module Bundler::RubyDsl - def ruby: (*untyped ruby_version) -> untyped + @ruby_version: untyped + + def ruby: (*::String ruby_version) -> void + + # Support the various file formats found in .ruby-version files. + # + # 3.2.2 + # ruby-3.2.2 + # + # Also supports .tool-versions files for asdf. Lines not starting with "ruby" are ignored. + # + # ruby 2.5.1 # comment is ignored + # ruby 2.5.1# close comment and extra spaces doesn't confuse + # + # Intentionally does not support `3.2.1@gemset` since rvm recommends using .ruby-gemset instead + # + # Loads the file relative to the dirname of the Gemfile itself. + def normalize_ruby_file: (::String filename) -> ::String end class Bundler::RubyVersion diff --git a/rbs/fills/bundler/ruby_dsl.rbs b/rbs/fills/bundler/ruby_dsl.rbs deleted file mode 100644 index 35b30c681..000000000 --- a/rbs/fills/bundler/ruby_dsl.rbs +++ /dev/null @@ -1,42 +0,0 @@ -# -# Bundler provides a consistent environment for Ruby projects by tracking and -# installing the exact gems and versions that are needed. -# -# Bundler is a part of Ruby's standard library. -# -# Bundler is used by creating *gemfiles* listing all the project dependencies -# and (optionally) their versions and then using -# -# require 'bundler/setup' -# -# or Bundler.setup to setup environment where only specified gems and their -# specified versions could be used. -# -# See [Bundler website](https://bundler.io/docs.html) for extensive -# documentation on gemfiles creation and Bundler usage. -# -# As a standard library inside project, Bundler could be used for introspection -# of loaded and required modules. -# -module Bundler - module RubyDsl - @ruby_version: untyped - - def ruby: (*::String ruby_version) -> void - - # Support the various file formats found in .ruby-version files. - # - # 3.2.2 - # ruby-3.2.2 - # - # Also supports .tool-versions files for asdf. Lines not starting with "ruby" are ignored. - # - # ruby 2.5.1 # comment is ignored - # ruby 2.5.1# close comment and extra spaces doesn't confuse - # - # Intentionally does not support `3.2.1@gemset` since rvm recommends using .ruby-gemset instead - # - # Loads the file relative to the dirname of the Gemfile itself. - def normalize_ruby_file: (::String filename) -> ::String - end -end From b9afcf7197159b5bcb30b0bd83cab8bebf7f4138 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 20 Jul 2025 17:21:56 -0400 Subject: [PATCH 023/460] Fix function defaults --- lib/solargraph/complex_type/unique_type.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 1023d080e..972bb0dbb 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -200,10 +200,10 @@ def parameter_variance situation, default = :covariant # matched in the expected qualifies as a match def conforms_to_unique_type?(api_map, expected, situation = :method_call, variance: erased_variance(situation), - allow_subtype_skew: allow_subtype_skew, - allow_empty_params: allow_empty_params, - allow_reverse_match: allow_reverse_match, - allow_any_match: allow_any_match) + allow_subtype_skew:, + allow_empty_params:, + allow_reverse_match:, + allow_any_match:) expected = expected.downcast_to_literal_if_possible inferred = downcast_to_literal_if_possible From c2f4d73dc435b7fe591f8c92a5a40dc2866b821a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 20 Jul 2025 17:34:59 -0400 Subject: [PATCH 024/460] Adapt specs --- lib/solargraph/complex_type/unique_type.rb | 7 ++++ .../conforms_to_spec.rb} | 42 +++++++++++-------- 2 files changed, 32 insertions(+), 17 deletions(-) rename spec/{type_checker/checks_spec.rb => complex_type/conforms_to_spec.rb} (75%) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 972bb0dbb..6bbe9a1bd 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -204,6 +204,13 @@ def conforms_to_unique_type?(api_map, expected, situation = :method_call, allow_empty_params:, allow_reverse_match:, allow_any_match:) + if allow_reverse_match + reversed_match = expected.conforms_to_unique_type? api_map, self, situation, allow_subtype_skew: allow_subtype_skew, + allow_empty_params: allow_empty_params, + allow_reverse_match: false, + allow_any_match: allow_any_match + return true if reversed_match + end expected = expected.downcast_to_literal_if_possible inferred = downcast_to_literal_if_possible diff --git a/spec/type_checker/checks_spec.rb b/spec/complex_type/conforms_to_spec.rb similarity index 75% rename from spec/type_checker/checks_spec.rb rename to spec/complex_type/conforms_to_spec.rb index 41119cefd..847da8563 100644 --- a/spec/type_checker/checks_spec.rb +++ b/spec/complex_type/conforms_to_spec.rb @@ -1,9 +1,9 @@ -describe Solargraph::TypeChecker::Checks do +describe Solargraph::ComplexType do it 'validates simple core types' do api_map = Solargraph::ApiMap.new exp = Solargraph::ComplexType.parse('String') inf = Solargraph::ComplexType.parse('String') - match = Solargraph::TypeChecker::Checks.types_match?(api_map, exp, inf) + match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end @@ -11,7 +11,7 @@ api_map = Solargraph::ApiMap.new exp = Solargraph::ComplexType.parse('String') inf = Solargraph::ComplexType.parse('Integer') - match = Solargraph::TypeChecker::Checks.types_match?(api_map, exp, inf) + match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(false) end @@ -24,7 +24,7 @@ class Sub < Sup; end api_map.map source sup = Solargraph::ComplexType.parse('Sup') sub = Solargraph::ComplexType.parse('Sub') - match = Solargraph::TypeChecker::Checks.types_match?(api_map, sup, sub) + match = sub.conforms_to?(api_map, sup, :method_call) expect(match).to be(true) end @@ -48,7 +48,7 @@ class Sub < Sup; end api_map = Solargraph::ApiMap.new exp = Solargraph::ComplexType.parse('Array') inf = Solargraph::ComplexType.parse('Array') - match = Solargraph::TypeChecker::Checks.types_match?(api_map, exp, inf) + match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end @@ -59,7 +59,7 @@ class Sub < Sup; end api_map.catalog Solargraph::Bench.new(source_maps: [source_map], external_requires: ['set']) exp = Solargraph::ComplexType.parse('Set') inf = Solargraph::ComplexType.parse('Set') - match = Solargraph::TypeChecker::Checks.types_match?(api_map, exp, inf) + match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end @@ -67,7 +67,7 @@ class Sub < Sup; end api_map = Solargraph::ApiMap.new exp = Solargraph::ComplexType.parse('Hash{ Symbol => String}') inf = Solargraph::ComplexType.parse('Hash') - match = Solargraph::TypeChecker::Checks.types_match?(api_map, exp, inf) + match = inf.conforms_to?(api_map, exp, :method_call, allow_empty_params: true) expect(match).to be(true) end @@ -75,7 +75,7 @@ class Sub < Sup; end api_map = Solargraph::ApiMap.new exp = Solargraph::ComplexType.parse('String, Integer') inf = Solargraph::ComplexType.parse('String, Integer') - match = Solargraph::TypeChecker::Checks.types_match?(api_map, exp, inf) + match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end @@ -83,7 +83,7 @@ class Sub < Sup; end api_map = Solargraph::ApiMap.new exp = Solargraph::ComplexType.parse('String, Integer') inf = Solargraph::ComplexType.parse('Integer, String') - match = Solargraph::TypeChecker::Checks.types_match?(api_map, exp, inf) + match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end @@ -91,7 +91,7 @@ class Sub < Sup; end api_map = Solargraph::ApiMap.new exp = Solargraph::ComplexType.parse('String') inf = Solargraph::ComplexType.parse('String, Integer') - match = Solargraph::TypeChecker::Checks.types_match?(api_map, exp, inf) + match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(false) end @@ -99,7 +99,7 @@ class Sub < Sup; end api_map = Solargraph::ApiMap.new exp = Solargraph::ComplexType.parse('nil') inf = Solargraph::ComplexType.parse('nil') - match = Solargraph::TypeChecker::Checks.types_match?(api_map, exp, inf) + match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end @@ -107,7 +107,7 @@ class Sub < Sup; end api_map = Solargraph::ApiMap.new exp = Solargraph::ComplexType.parse('Class') inf = Solargraph::ComplexType.parse('Class') - match = Solargraph::TypeChecker::Checks.types_match?(api_map, exp, inf) + match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end @@ -115,7 +115,15 @@ class Sub < Sup; end api_map = Solargraph::ApiMap.new exp = Solargraph::ComplexType.parse('Class') inf = Solargraph::ComplexType.parse('Class') - match = Solargraph::TypeChecker::Checks.types_match?(api_map, exp, inf) + match = inf.conforms_to?(api_map, exp, :method_call, allow_empty_params: true) + expect(match).to be(true) + end + + it 'validates generic classes with expected Class' do + api_map = Solargraph::ApiMap.new + inf = Solargraph::ComplexType.parse('Class') + exp = Solargraph::ComplexType.parse('Class') + match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end @@ -128,9 +136,9 @@ class Sub < Sup; end api_map.map source sup = Solargraph::ComplexType.parse('Sup') sub = Solargraph::ComplexType.parse('Sub') - match = Solargraph::TypeChecker::Checks.either_way?(api_map, sup, sub) + match = sub.conforms_to?(api_map, sup, :method_call, allow_reverse_match: true) expect(match).to be(true) - match = Solargraph::TypeChecker::Checks.either_way?(api_map, sub, sup) + match = sup.conforms_to?(api_map, sub, :method_call, allow_reverse_match: true) expect(match).to be(true) end @@ -138,9 +146,9 @@ class Sub < Sup; end api_map = Solargraph::ApiMap.new sup = Solargraph::ComplexType.parse('String') sub = Solargraph::ComplexType.parse('Array') - match = Solargraph::TypeChecker::Checks.either_way?(api_map, sup, sub) + match = sub.conforms_to?(api_map, sup, :method_call, allow_reverse_match: true) expect(match).to be(false) - match = Solargraph::TypeChecker::Checks.either_way?(api_map, sub, sup) + match = sup.conforms_to?(api_map, sub, :method_call, allow_reverse_match: true) expect(match).to be(false) end end From e0cead948bde8952ebd9241dfd41ff96a0a92887 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 20 Jul 2025 18:41:10 -0400 Subject: [PATCH 025/460] Fix some annotations --- lib/solargraph/api_map/store.rb | 2 +- lib/solargraph/pin/method.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 47f92194c..15fb00827 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -116,7 +116,7 @@ def get_instance_variables(fqns, scope = :instance) end # @param fqns [String] - # @return [Enumerable] + # @return [Enumerable] def get_class_variables(fqns) namespace_children(fqns).select { |pin| pin.is_a?(Pin::ClassVariable)} end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 2f807f444..749868246 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -385,7 +385,7 @@ def probe api_map attribute? ? infer_from_iv(api_map) : infer_from_return_nodes(api_map) end - # @return [::Array] + # @return [::Array] def overloads # Ignore overload tags with nil parameters. If it's not an array, the # tag's source is likely malformed. From 5e34afadb99b93ca76b1f05eeefa403f94d040e7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 21 Jul 2025 07:17:22 -0400 Subject: [PATCH 026/460] Other shims from typechecking solargraph --- rbs/fills/rubygems/0/basic_specification.rbs | 326 ++++ rbs/fills/rubygems/0/errors.rbs | 364 ++++ rbs/fills/rubygems/0/specification.rbs | 1753 ++++++++++++++++++ sig/shims/ast/2.4/.rbs_meta.yaml | 9 + sig/shims/ast/2.4/ast.rbs | 73 + 5 files changed, 2525 insertions(+) create mode 100644 rbs/fills/rubygems/0/basic_specification.rbs create mode 100644 rbs/fills/rubygems/0/errors.rbs create mode 100644 rbs/fills/rubygems/0/specification.rbs create mode 100644 sig/shims/ast/2.4/.rbs_meta.yaml create mode 100644 sig/shims/ast/2.4/ast.rbs diff --git a/rbs/fills/rubygems/0/basic_specification.rbs b/rbs/fills/rubygems/0/basic_specification.rbs new file mode 100644 index 000000000..361027e67 --- /dev/null +++ b/rbs/fills/rubygems/0/basic_specification.rbs @@ -0,0 +1,326 @@ +# +# BasicSpecification is an abstract class which implements some common code used +# by both Specification and StubSpecification. +# +class Gem::BasicSpecification + @ignored: untyped + + @extension_dir: untyped + + @full_gem_path: untyped + + @full_require_paths: untyped + + @paths_map: untyped + + @gem_dir: untyped + + attr_writer base_dir: untyped + + attr_writer extension_dir: untyped + + attr_writer ignored: untyped + + # + # The path this gemspec was loaded from. This attribute is not persisted. + # + attr_accessor loaded_from: untyped + + attr_writer full_gem_path: untyped + + # + # + def initialize: () -> void + + # + # + def self.default_specifications_dir: () -> untyped + + extend Gem::Deprecate + + def gem_build_complete_path: () -> untyped + + # + # True when the gem has been activated + # + def activated?: () -> untyped + + # + # Returns the full path to the base gem directory. + # + # eg: /usr/local/lib/ruby/gems/1.8 + # + def base_dir: () -> untyped + + # + # Return true if this spec can require `file`. + # + def contains_requirable_file?: (untyped file) -> (false | untyped) + + # + # Return true if this spec should be ignored because it's missing extensions. + # + def ignored?: () -> untyped + + # + # + def default_gem?: () -> untyped + + # + # Regular gems take precedence over default gems + # + def default_gem_priority: () -> (1 | -1) + + # + # Gems higher up in `gem_path` take precedence + # + def base_dir_priority: (untyped gem_path) -> untyped + + # + # Returns full path to the directory where gem's extensions are installed. + # + def extension_dir: () -> untyped + + # + # Returns path to the extensions directory. + # + def extensions_dir: () -> untyped + + private + + def find_full_gem_path: () -> untyped + + public + + # + # The full path to the gem (install path + full name). + # + # TODO: This is duplicated with #gem_dir. Eventually either of them should be + # deprecated. + # + def full_gem_path: () -> untyped + + # + # Returns the full name (name-version) of this Gem. Platform information is + # included (name-version-platform) if it is specified and not the default Ruby + # platform. + # + def full_name: () -> ::String + + # + # Returns the full name of this Gem (see `Gem::BasicSpecification#full_name`). + # Information about where the gem is installed is also included if not installed + # in the default GEM_HOME. + # + def full_name_with_location: () -> (::String | untyped) + + # + # Full paths in the gem to add to `$LOAD_PATH` when this gem is activated. + # + def full_require_paths: () -> untyped + + # + # The path to the data directory for this gem. + # + def datadir: () -> untyped + + # + # Full path of the target library file. If the file is not in this gem, return + # nil. + # + def to_fullpath: (untyped path) -> (untyped | nil) + + # + # Returns the full path to this spec's gem directory. eg: + # /usr/local/lib/ruby/1.8/gems/mygem-1.0 + # + # TODO: This is duplicated with #full_gem_path. Eventually either of them should + # be deprecated. + # + def gem_dir: () -> untyped + + # + # Returns the full path to the gems directory containing this spec's gem + # directory. eg: /usr/local/lib/ruby/1.8/gems + # + def gems_dir: () -> untyped + + def internal_init: () -> untyped + + # + # Name of the gem + # + def name: () -> untyped + + # + # Platform of the gem + # + def platform: () -> untyped + + def raw_require_paths: () -> untyped + + # + # Paths in the gem to add to `$LOAD_PATH` when this gem is activated. + # + # See also #require_paths= + # + # If you have an extension you do not need to add `"ext"` to the require path, + # the extension build process will copy the extension files into "lib" for you. + # + # The default value is `"lib"` + # + # Usage: + # + # # If all library files are in the root directory... + # spec.require_path = '.' + # + def require_paths: () -> untyped + + # + # Returns the paths to the source files for use with analysis and documentation + # tools. These paths are relative to full_gem_path. + # + def source_paths: () -> untyped + + # + # Return all files in this gem that match for `glob`. + # + def matches_for_glob: (untyped glob) -> untyped + + # + # Returns the list of plugins in this spec. + # + def plugins: () -> untyped + + # + # Returns a string usable in Dir.glob to match all requirable paths for this + # spec. + # + def lib_dirs_glob: () -> ::String + + # + # Return a Gem::Specification from this gem + # + def to_spec: () -> untyped + + # + # Version of the gem + # + def version: () -> untyped + + # + # Whether this specification is stubbed - i.e. we have information about the gem + # from a stub line, without having to evaluate the entire gemspec file. + # + def stubbed?: () -> untyped + + # + # + def this: () -> self + + private + + # + # + def have_extensions?: () -> untyped + + # + # + def have_file?: (untyped file, untyped suffixes) -> (true | untyped) +end diff --git a/rbs/fills/rubygems/0/errors.rbs b/rbs/fills/rubygems/0/errors.rbs new file mode 100644 index 000000000..34b97fcf1 --- /dev/null +++ b/rbs/fills/rubygems/0/errors.rbs @@ -0,0 +1,364 @@ +# +# RubyGems is the Ruby standard for publishing and managing third party +# libraries. +# +# For user documentation, see: +# +# * `gem help` and `gem help [command]` +# * [RubyGems User Guide](https://guides.rubygems.org/) +# * [Frequently Asked Questions](https://guides.rubygems.org/faqs) +# +# For gem developer documentation see: +# +# * [Creating Gems](https://guides.rubygems.org/make-your-own-gem) +# * Gem::Specification +# * Gem::Version for version dependency notes +# +# Further RubyGems documentation can be found at: +# +# * [RubyGems Guides](https://guides.rubygems.org) +# * [RubyGems API](https://www.rubydoc.info/github/rubygems/rubygems) (also +# available from `gem server`) +# +# ## RubyGems Plugins +# +# RubyGems will load plugins in the latest version of each installed gem or +# $LOAD_PATH. Plugins must be named 'rubygems_plugin' (.rb, .so, etc) and +# placed at the root of your gem's #require_path. Plugins are installed at a +# special location and loaded on boot. +# +# For an example plugin, see the [Graph gem](https://github.com/seattlerb/graph) +# which adds a `gem graph` command. +# +# ## RubyGems Defaults, Packaging +# +# RubyGems defaults are stored in lib/rubygems/defaults.rb. If you're packaging +# RubyGems or implementing Ruby you can change RubyGems' defaults. +# +# For RubyGems packagers, provide lib/rubygems/defaults/operating_system.rb and +# override any defaults from lib/rubygems/defaults.rb. +# +# For Ruby implementers, provide lib/rubygems/defaults/#{RUBY_ENGINE}.rb and +# override any defaults from lib/rubygems/defaults.rb. +# +# If you need RubyGems to perform extra work on install or uninstall, your +# defaults override file can set pre/post install and uninstall hooks. See +# Gem::pre_install, Gem::pre_uninstall, Gem::post_install, Gem::post_uninstall. +# +# ## Bugs +# +# You can submit bugs to the [RubyGems bug +# tracker](https://github.com/rubygems/rubygems/issues) on GitHub +# +# ## Credits +# +# RubyGems is currently maintained by Eric Hodel. +# +# RubyGems was originally developed at RubyConf 2003 by: +# +# * Rich Kilmer -- rich(at)infoether.com +# * Chad Fowler -- chad(at)chadfowler.com +# * David Black -- dblack(at)wobblini.net +# * Paul Brannan -- paul(at)atdesk.com +# * Jim Weirich -- jim(at)weirichhouse.org +# +# Contributors: +# +# * Gavin Sinclair -- gsinclair(at)soyabean.com.au +# * George Marrows -- george.marrows(at)ntlworld.com +# * Dick Davies -- rasputnik(at)hellooperator.net +# * Mauricio Fernandez -- batsman.geo(at)yahoo.com +# * Simon Strandgaard -- neoneye(at)adslhome.dk +# * Dave Glasser -- glasser(at)mit.edu +# * Paul Duncan -- pabs(at)pablotron.org +# * Ville Aine -- vaine(at)cs.helsinki.fi +# * Eric Hodel -- drbrain(at)segment7.net +# * Daniel Berger -- djberg96(at)gmail.com +# * Phil Hagelberg -- technomancy(at)gmail.com +# * Ryan Davis -- ryand-ruby(at)zenspider.com +# * Evan Phoenix -- evan(at)fallingsnow.net +# * Steve Klabnik -- steve(at)steveklabnik.com +# +# (If your name is missing, PLEASE let us know!) +# +# ## License +# +# See +# [LICENSE.txt](https://github.com/rubygems/rubygems/blob/master/LICENSE.txt) +# for permissions. +# +# Thanks! +# +# -The RubyGems Team +# +# +# Provides 3 methods for declaring when something is going away. +# +# +deprecate(name, repl, year, month)+: +# Indicate something may be removed on/after a certain date. +# +# +rubygems_deprecate(name, replacement=:none)+: +# Indicate something will be removed in the next major RubyGems version, +# and (optionally) a replacement for it. +# +# `rubygems_deprecate_command`: +# Indicate a RubyGems command (in +lib/rubygems/commands/*.rb+) will be +# removed in the next RubyGems version. +# +# Also provides `skip_during` for temporarily turning off deprecation warnings. +# This is intended to be used in the test suite, so deprecation warnings don't +# cause test failures if you need to make sure stderr is otherwise empty. +# +# Example usage of `deprecate` and `rubygems_deprecate`: +# +# class Legacy +# def self.some_class_method +# # ... +# end +# +# def some_instance_method +# # ... +# end +# +# def some_old_method +# # ... +# end +# +# extend Gem::Deprecate +# deprecate :some_instance_method, "X.z", 2011, 4 +# rubygems_deprecate :some_old_method, "Modern#some_new_method" +# +# class << self +# extend Gem::Deprecate +# deprecate :some_class_method, :none, 2011, 4 +# end +# end +# +# Example usage of `rubygems_deprecate_command`: +# +# class Gem::Commands::QueryCommand < Gem::Command +# extend Gem::Deprecate +# rubygems_deprecate_command +# +# # ... +# end +# +# Example usage of `skip_during`: +# +# class TestSomething < Gem::Testcase +# def test_some_thing_with_deprecations +# Gem::Deprecate.skip_during do +# actual_stdout, actual_stderr = capture_output do +# Gem.something_deprecated +# end +# assert_empty actual_stdout +# assert_equal(expected, actual_stderr) +# end +# end +# end +# +module Gem + # + # Raised when RubyGems is unable to load or activate a gem. Contains the name + # and version requirements of the gem that either conflicts with already + # activated gems or that RubyGems is otherwise unable to activate. + # + class LoadError < ::LoadError + # + # Name of gem + # + attr_accessor name: untyped + + # + # Version requirement of gem + # + attr_accessor requirement: untyped + end + + # + # Raised when trying to activate a gem, and that gem does not exist on the + # system. Instead of rescuing from this class, make sure to rescue from the + # superclass Gem::LoadError to catch all types of load errors. + # + class MissingSpecError < Gem::LoadError + @name: untyped + + @requirement: untyped + + @extra_message: untyped + + # + # + def initialize: (untyped name, untyped requirement, ?untyped? extra_message) -> void + + def message: () -> untyped + + private + + # + # + def build_message: () -> ::String + end + + # + # Raised when trying to activate a gem, and the gem exists on the system, but + # not the requested version. Instead of rescuing from this class, make sure to + # rescue from the superclass Gem::LoadError to catch all types of load errors. + # + class MissingSpecVersionError < MissingSpecError + @specs: untyped + + attr_reader specs: untyped + + # + # + def initialize: (untyped name, untyped requirement, untyped specs) -> void + + private + + # + # + def build_message: () -> ::String + end + + # + # Raised when there are conflicting gem specs loaded + # + class ConflictError < LoadError + @target: untyped + + @conflicts: untyped + + @name: untyped + + # + # A Hash mapping conflicting specifications to the dependencies that caused the + # conflict + # + attr_reader conflicts: untyped + + # + # The specification that had the conflict + # + attr_reader target: untyped + + # + # + def initialize: (untyped target, untyped conflicts) -> void + end + + class ErrorReason + end + + # + # Generated when trying to lookup a gem to indicate that the gem was found, but + # that it isn't usable on the current platform. + # + # fetch and install read these and report them to the user to aid in figuring + # out why a gem couldn't be installed. + # + class PlatformMismatch < ErrorReason + @name: untyped + + @version: untyped + + @platforms: untyped + + # + # the name of the gem + # + attr_reader name: untyped + + # + # the version + # + attr_reader version: untyped + + # + # The platforms that are mismatched + # + attr_reader platforms: untyped + + # + # + def initialize: (untyped name, untyped version) -> void + + # + # append a platform to the list of mismatched platforms. + # + # Platforms are added via this instead of injected via the constructor so that + # we can loop over a list of mismatches and just add them rather than perform + # some kind of calculation mismatch summary before creation. + # + def add_platform: (untyped platform) -> untyped + + # + # A wordy description of the error. + # + def wordy: () -> untyped + end + + # + # An error that indicates we weren't able to fetch some data from a source + # + class SourceFetchProblem < ErrorReason + @source: untyped + + @error: untyped + + # + # Creates a new SourceFetchProblem for the given `source` and `error`. + # + def initialize: (untyped source, untyped error) -> void + + # + # The source that had the fetch problem. + # + attr_reader source: untyped + + # + # The fetch error which is an Exception subclass. + # + attr_reader error: untyped + + # + # An English description of the error. + # + def wordy: () -> ::String + + # + # The fetch error which is an Exception subclass. + # + alias exception error + end +end diff --git a/rbs/fills/rubygems/0/specification.rbs b/rbs/fills/rubygems/0/specification.rbs new file mode 100644 index 000000000..ff51ef0b1 --- /dev/null +++ b/rbs/fills/rubygems/0/specification.rbs @@ -0,0 +1,1753 @@ +# +# The Specification class contains the information for a gem. Typically defined +# in a .gemspec file or a Rakefile, and looks like this: +# +# Gem::Specification.new do |s| +# s.name = 'example' +# s.version = '0.1.0' +# s.licenses = ['MIT'] +# s.summary = "This is an example!" +# s.description = "Much longer explanation of the example!" +# s.authors = ["Ruby Coder"] +# s.email = 'rubycoder@example.com' +# s.files = ["lib/example.rb"] +# s.homepage = 'https://rubygems.org/gems/example' +# s.metadata = { "source_code_uri" => "https://github.com/example/example" } +# end +# +# Starting in RubyGems 2.0, a Specification can hold arbitrary metadata. See +# #metadata for restrictions on the format and size of metadata items you may +# add to a specification. +# +class Gem::Specification < Gem::BasicSpecification + @@required_attributes: untyped + + @@default_value: untyped + + @@attributes: untyped + + @@array_attributes: untyped + + @@nil_attributes: untyped + + @@non_nil_attributes: untyped + + @@dirs: untyped + + self.@load_cache: untyped + + self.@load_cache_mutex: untyped + + self.@specification_record: untyped + + self.@unresolved_deps: untyped + + @removed_method_calls: untyped + + # DO NOT CHANGE TO ||= ! This is not a normal accessor. (yes, it sucks) + # DOC: Why isn't it normal? Why does it suck? How can we fix this? + @files: untyped + + @authors: untyped + + @licenses: untyped + + @original_platform: untyped + + @new_platform: untyped + + @platform: untyped + + @require_paths: untyped + + @executables: untyped + + @extensions: untyped + + @extra_rdoc_files: untyped + + @installed_by_version: untyped + + @rdoc_options: untyped + + @required_ruby_version: untyped + + @required_rubygems_version: untyped + + @requirements: untyped + + @test_files: untyped + + @extensions_dir: untyped + + @activated: untyped + + @loaded: untyped + + @bin_dir: untyped + + @cache_dir: untyped + + @cache_file: untyped + + @date: untyped + + @dependencies: untyped + + @description: untyped + + @doc_dir: untyped + + @full_name: untyped + + @gems_dir: untyped + + @has_rdoc: untyped + + @base_dir: untyped + + @loaded_from: untyped + + @ri_dir: untyped + + @spec_dir: untyped + + @spec_file: untyped + + @summary: untyped + + @test_suite_file: untyped + + @version: untyped + + extend Gem::Deprecate + + # + # The version number of a specification that does not specify one (i.e. RubyGems + # 0.7 or earlier). + # + NONEXISTENT_SPECIFICATION_VERSION: -1 + + CURRENT_SPECIFICATION_VERSION: 4 + + SPECIFICATION_VERSION_HISTORY: { -1 => ::Array["(RubyGems versions up to and including 0.7 did not have versioned specifications)"], 1 => ::Array["Deprecated \"test_suite_file\" in favor of the new, but equivalent, \"test_files\"" | "\"test_file=x\" is a shortcut for \"test_files=[x]\""], 2 => ::Array["Added \"required_rubygems_version\"" | "Now forward-compatible with future versions"], 3 => ::Array["Added Fixnum validation to the specification_version"], 4 => ::Array["Added sandboxed freeform metadata to the specification version."] } + + MARSHAL_FIELDS: { -1 => 16, 1 => 16, 2 => 16, 3 => 17, 4 => 18 } + + TODAY: untyped + + VALID_NAME_PATTERN: ::Regexp + + # rubocop:disable Style/MutableConstant + INITIALIZE_CODE_FOR_DEFAULTS: ::Hash[untyped, untyped] + + # Sentinel object to represent "not found" stubs + NOT_FOUND: untyped + + # Tracking removed method calls to warn users during build time. + REMOVED_METHODS: ::Array[:rubyforge_project= | :mark_version] + + # + # + def removed_method_calls: () -> untyped + + # + # This gem's name. + # + # Usage: + # + # spec.name = 'rake' + # + attr_accessor name: String + + # + # This gem's version. + # + # The version string can contain numbers and periods, such as `1.0.0`. A gem is + # a 'prerelease' gem if the version has a letter in it, such as `1.0.0.pre`. + # + # Usage: + # + # spec.version = '0.4.1' + # + attr_reader version: String + + # + # A short summary of this gem's description. Displayed in `gem list -d`. + # + # The #description should be more detailed than the summary. + # + # Usage: + # + # spec.summary = "This is a small summary of my gem" + # + attr_reader summary: untyped + + # + # Files included in this gem. You cannot append to this accessor, you must + # assign to it. + # + # Only add files you can require to this list, not directories, etc. + # + # Directories are automatically stripped from this list when building a gem, + # other non-files cause an error. + # + # Usage: + # + # require 'rake' + # spec.files = FileList['lib/**/*.rb', + # 'bin/*', + # '[A-Z]*'].to_a + # + # # or without Rake... + # spec.files = Dir['lib/**/*.rb'] + Dir['bin/*'] + # spec.files += Dir['[A-Z]*'] + # spec.files.reject! { |fn| fn.include? "CVS" } + # + def files: () -> Enumerable[String] + + # + # A list of authors for this gem. + # + # Alternatively, a single author can be specified by assigning a string to + # `spec.author` + # + # Usage: + # + # spec.authors = ['John Jones', 'Mary Smith'] + # + def authors=: (untyped value) -> untyped + + # + # The version of Ruby required by this gem + # + # Usage: + # + # spec.required_ruby_version = '>= 2.7.0' + # + attr_reader required_ruby_version: untyped + + # + # A long description of this gem + # + # The description should be more detailed than the summary but not excessively + # long. A few paragraphs is a recommended length with no examples or + # formatting. + # + # Usage: + # + # spec.description = <<-EOF + # Rake is a Make-like program implemented in Ruby. Tasks and + # dependencies are specified in standard Ruby syntax. + # EOF + # + attr_reader description: untyped + + # + # A contact email address (or addresses) for this gem + # + # Usage: + # + # spec.email = 'john.jones@example.com' + # spec.email = ['jack@example.com', 'jill@example.com'] + # + attr_accessor email: untyped + + # + # The URL of this gem's home page + # + # Usage: + # + # spec.homepage = 'https://github.com/ruby/rake' + # + attr_accessor homepage: untyped + + # + # The license for this gem. + # + # The license must be no more than 64 characters. + # + # This should just be the name of your license. The full text of the license + # should be inside of the gem (at the top level) when you build it. + # + # The simplest way is to specify the standard SPDX ID https://spdx.org/licenses/ + # for the license. Ideally, you should pick one that is OSI (Open Source + # Initiative) http://opensource.org/licenses/alphabetical approved. + # + # The most commonly used OSI-approved licenses are MIT and Apache-2.0. GitHub + # also provides a license picker at http://choosealicense.com/. + # + # You can also use a custom license file along with your gemspec and specify a + # LicenseRef-, where idstring is the name of the file containing the + # license text. + # + # You should specify a license for your gem so that people know how they are + # permitted to use it and any restrictions you're placing on it. Not specifying + # a license means all rights are reserved; others have no right to use the code + # for any purpose. + # + # You can set multiple licenses with #licenses= + # + # Usage: + # spec.license = 'MIT' + # + def license=: (untyped o) -> untyped + + # + # The license(s) for the library. + # + # Each license must be a short name, no more than 64 characters. + # + # This should just be the name of your license. The full text of the license + # should be inside of the gem when you build it. + # + # See #license= for more discussion + # + # Usage: + # spec.licenses = ['MIT', 'GPL-2.0'] + # + def licenses=: (untyped licenses) -> untyped + + # + # The metadata holds extra data for this gem that may be useful to other + # consumers and is settable by gem authors. + # + # Metadata items have the following restrictions: + # + # * The metadata must be a Hash object + # * All keys and values must be Strings + # * Keys can be a maximum of 128 bytes and values can be a maximum of 1024 + # bytes + # * All strings must be UTF-8, no binary data is allowed + # + # You can use metadata to specify links to your gem's homepage, codebase, + # documentation, wiki, mailing list, issue tracker and changelog. + # + # s.metadata = { + # "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", + # "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", + # "documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1", + # "homepage_uri" => "https://bestgemever.example.io", + # "mailing_list_uri" => "https://groups.example.com/bestgemever", + # "source_code_uri" => "https://example.com/user/bestgemever", + # "wiki_uri" => "https://example.com/user/bestgemever/wiki" + # "funding_uri" => "https://example.com/donate" + # } + # + # These links will be used on your gem's page on rubygems.org and must pass + # validation against following regex. + # + # %r{\Ahttps?:\/\/([^\s:@]+:[^\s:@]*@)?[A-Za-z\d\-]+(\.[A-Za-z\d\-]+)+\.?(:\d{1,5})?([\/?]\S*)?\z} + # + attr_accessor metadata: untyped + + # + # Singular (alternative) writer for #authors + # + # Usage: + # + # spec.author = 'John Jones' + # + def author=: (untyped o) -> untyped + + # + # The path in the gem for executable scripts. Usually 'bin' + # + # Usage: + # + # spec.bindir = 'bin' + # + attr_accessor bindir: untyped + + # + # The certificate chain used to sign this gem. See Gem::Security for details. + # + attr_accessor cert_chain: untyped + + # + # A message that gets displayed after the gem is installed. + # + # Usage: + # + # spec.post_install_message = "Thanks for installing!" + # + attr_accessor post_install_message: untyped + + # + # The platform this gem runs on. + # + # This is usually Gem::Platform::RUBY or Gem::Platform::CURRENT. + # + # Most gems contain pure Ruby code; they should simply leave the default value + # in place. Some gems contain C (or other) code to be compiled into a Ruby + # "extension". The gem should leave the default value in place unless the code + # will only compile on a certain type of system. Some gems consist of + # pre-compiled code ("binary gems"). It's especially important that they set + # the platform attribute appropriately. A shortcut is to set the platform to + # Gem::Platform::CURRENT, which will cause the gem builder to set the platform + # to the appropriate value for the system on which the build is being performed. + # + # If this attribute is set to a non-default value, it will be included in the + # filename of the gem when it is built such as: nokogiri-1.6.0-x86-mingw32.gem + # + # Usage: + # + # spec.platform = Gem::Platform.local + # + def platform=: (untyped platform) -> untyped + + # + # Paths in the gem to add to `$LOAD_PATH` when this gem is activated. If you + # have an extension you do not need to add `"ext"` to the require path, the + # extension build process will copy the extension files into "lib" for you. + # + # The default value is `"lib"` + # + # Usage: + # + # # If all library files are in the root directory... + # spec.require_paths = ['.'] + # + def require_paths=: (untyped val) -> untyped + + # + # The RubyGems version required by this gem + # + attr_reader required_rubygems_version: untyped + + # + # The key used to sign this gem. See Gem::Security for details. + # + attr_accessor signing_key: untyped + + # + # Adds a development dependency named `gem` with `requirements` to this gem. + # + # Usage: + # + # spec.add_development_dependency 'example', '~> 1.1', '>= 1.1.4' + # + # Development dependencies aren't installed by default and aren't activated when + # a gem is required. + # + def add_development_dependency: (untyped gem, *untyped requirements) -> untyped + + # + # + def add_dependency: (untyped gem, *untyped requirements) -> untyped + + # + # Executables included in the gem. + # + # For example, the rake gem has rake as an executable. You don’t specify the + # full path (as in bin/rake); all application-style files are expected to be + # found in bindir. These files must be executable Ruby files. Files that use + # bash or other interpreters will not work. + # + # Executables included may only be ruby scripts, not scripts for other languages + # or compiled binaries. + # + # Usage: + # + # spec.executables << 'rake' + # + def executables: () -> untyped + + # + # Extensions to build when installing the gem, specifically the paths to + # extconf.rb-style files used to compile extensions. + # + # These files will be run when the gem is installed, causing the C (or whatever) + # code to be compiled on the user’s machine. + # + # Usage: + # + # spec.extensions << 'ext/rmagic/extconf.rb' + # + # See Gem::Ext::Builder for information about writing extensions for gems. + # + def extensions: () -> untyped + + # + # Extra files to add to RDoc such as README or doc/examples.txt + # + # When the user elects to generate the RDoc documentation for a gem (typically + # at install time), all the library files are sent to RDoc for processing. This + # option allows you to have some non-code files included for a more complete set + # of documentation. + # + # Usage: + # + # spec.extra_rdoc_files = ['README', 'doc/user-guide.txt'] + # + def extra_rdoc_files: () -> untyped + + def installed_by_version: () -> untyped + + def installed_by_version=: (untyped version) -> untyped + + # + # Specifies the rdoc options to be used when generating API documentation. + # + # Usage: + # + # spec.rdoc_options << '--title' << 'Rake -- Ruby Make' << + # '--main' << 'README' << + # '--line-numbers' + # + def rdoc_options: () -> untyped + + LATEST_RUBY_WITHOUT_PATCH_VERSIONS: untyped + + # + # The version of Ruby required by this gem. The ruby version can be specified + # to the patch-level: + # + # $ ruby -v -e 'p Gem.ruby_version' + # ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-darwin12.4.0] + # # + # + # Prereleases can also be specified. + # + # Usage: + # + # # This gem will work with 1.8.6 or greater... + # spec.required_ruby_version = '>= 1.8.6' + # + # # Only with final releases of major version 2 where minor version is at least 3 + # spec.required_ruby_version = '~> 2.3' + # + # # Only prereleases or final releases after 2.6.0.preview2 + # spec.required_ruby_version = '> 2.6.0.preview2' + # + # # This gem will work with 2.3.0 or greater, including major version 3, but lesser than 4.0.0 + # spec.required_ruby_version = '>= 2.3', '< 4' + # + def required_ruby_version=: (untyped req) -> untyped + + # + # The RubyGems version required by this gem + # + def required_rubygems_version=: (untyped req) -> untyped + + # + # Lists the external (to RubyGems) requirements that must be met for this gem to + # work. It's simply information for the user. + # + # Usage: + # + # spec.requirements << 'libmagick, v6.0' + # spec.requirements << 'A good graphics card' + # + def requirements: () -> untyped + + def test_files=: (untyped files) -> untyped + + # + # The version of RubyGems used to create this gem. + # + # Do not set this, it is set automatically when the gem is packaged. + # + attr_accessor rubygems_version: untyped + + def extensions_dir: () -> untyped + + # + # True when this gemspec has been activated. This attribute is not persisted. + # + attr_accessor activated: untyped + + # + # True when this gemspec has been activated. This attribute is not persisted. + # + alias activated? activated + + attr_accessor autorequire: untyped + + attr_writer default_executable: untyped + + attr_writer original_platform: untyped + + # + # The Gem::Specification version of this gemspec. + # + # Do not set this, it is set automatically when the gem is packaged. + # + attr_accessor specification_version: untyped + + def self._all: () -> untyped + + def self.clear_load_cache: () -> untyped + + def self.gem_path: () -> untyped + + def self.each_gemspec: (untyped dirs) { (untyped) -> untyped } -> untyped + + # + # + def self.gemspec_stubs_in: (untyped dir, untyped pattern) { (untyped) -> untyped } -> untyped + + def self.each_spec: (untyped dirs) { (untyped) -> untyped } -> untyped + + # + # Returns a Gem::StubSpecification for every installed gem + # + def self.stubs: () -> untyped + + # + # Returns a Gem::StubSpecification for default gems + # + def self.default_stubs: (?::String pattern) -> untyped + + # + # Returns a Gem::StubSpecification for installed gem named `name` only returns + # stubs that match Gem.platforms + # + def self.stubs_for: (untyped name) -> untyped + + # + # Finds stub specifications matching a pattern from the standard locations, + # optionally filtering out specs not matching the current platform + # + def self.stubs_for_pattern: (untyped pattern, ?bool match_platform) -> untyped + + def self._resort!: (untyped specs) -> untyped + + # + # Loads the default specifications. It should be called only once. + # + def self.load_defaults: () -> untyped + + # + # Adds `spec` to the known specifications, keeping the collection properly + # sorted. + # + def self.add_spec: (untyped spec) -> untyped + + # + # Removes `spec` from the known specs. + # + def self.remove_spec: (untyped spec) -> untyped + + # + # Returns all specifications. This method is discouraged from use. You probably + # want to use one of the Enumerable methods instead. + # + def self.all: () -> untyped + + # + # Sets the known specs to `specs`. Not guaranteed to work for you in the future. + # Use at your own risk. Caveat emptor. Doomy doom doom. Etc etc. + # + def self.all=: (untyped specs) -> untyped + + # + # Return full names of all specs in sorted order. + # + def self.all_names: () -> untyped + + # + # Return the list of all array-oriented instance variables. + # + def self.array_attributes: () -> untyped + + # + # Return the list of all instance variables. + # + def self.attribute_names: () -> untyped + + # + # Return the directories that Specification uses to find specs. + # + def self.dirs: () -> untyped + + # + # Set the directories that Specification uses to find specs. Setting this resets + # the list of known specs. + # + def self.dirs=: (untyped dirs) -> untyped + + extend Enumerable[Gem::Specification] + + # + # Enumerate every known spec. See ::dirs= and ::add_spec to set the list of + # specs. + # + def self.each: () { (Gem::Specification) -> untyped } -> untyped + + # + # Returns every spec that matches `name` and optional `requirements`. + # + def self.find_all_by_name: (untyped name, *untyped requirements) -> untyped + + # + # Returns every spec that has the given `full_name` + # + def self.find_all_by_full_name: (untyped full_name) -> untyped + + # + # Find the best specification matching a `name` and `requirements`. Raises if + # the dependency doesn't resolve to a valid specification. + # + def self.find_by_name: (String name, *untyped requirements) -> instance + + # + # Find the best specification matching a +full_name+. + def self.find_by_full_name: (untyped full_name) -> instance + + # + # Return the best specification that contains the file matching `path`. + # + def self.find_by_path: (String path) -> instance + + # + # Return the best specification that contains the file matching `path` amongst + # the specs that are not activated. + # + def self.find_inactive_by_path: (untyped path) -> untyped + + # + # + def self.find_active_stub_by_path: (untyped path) -> untyped + + # + # Return currently unresolved specs that contain the file matching `path`. + # + def self.find_in_unresolved: (untyped path) -> untyped + + # + # Search through all unresolved deps and sub-dependencies and return specs that + # contain the file matching `path`. + # + def self.find_in_unresolved_tree: (untyped path) -> (untyped | ::Array[untyped]) + + # + # + def self.unresolved_specs: () -> untyped + + # + # Special loader for YAML files. When a Specification object is loaded from a + # YAML file, it bypasses the normal Ruby object initialization routine + # (#initialize). This method makes up for that and deals with gems of different + # ages. + # + # `input` can be anything that YAML.load() accepts: String or IO. + # + def self.from_yaml: (untyped input) -> untyped + + # + # Return the latest specs, optionally including prerelease specs if `prerelease` + # is true. + # + def self.latest_specs: (?bool prerelease) -> untyped + + # + # Return the latest installed spec for gem `name`. + # + def self.latest_spec_for: (untyped name) -> untyped + + def self._latest_specs: (untyped specs, ?bool prerelease) -> untyped + + # + # Loads Ruby format gemspec from `file`. + # + def self.load: (untyped file) -> (nil | untyped) + + # + # Specification attributes that must be non-nil + # + def self.non_nil_attributes: () -> untyped + + # + # Make sure the YAML specification is properly formatted with dashes + # + def self.normalize_yaml_input: (untyped input) -> untyped + + # + # Return a list of all outdated local gem names. This method is HEAVY as it + # must go fetch specifications from the server. + # + # Use outdated_and_latest_version if you wish to retrieve the latest remote + # version as well. + # + def self.outdated: () -> untyped + + # + # Enumerates the outdated local gems yielding the local specification and the + # latest remote version. + # + # This method may take some time to return as it must check each local gem + # against the server's index. + # + def self.outdated_and_latest_version: () ?{ (untyped) -> untyped } -> (untyped | nil) + + # + # Is `name` a required attribute? + # + def self.required_attribute?: (untyped name) -> untyped + + # + # Required specification attributes + # + def self.required_attributes: () -> untyped + + # + # Reset the list of known specs, running pre and post reset hooks registered in + # Gem. + # + def self.reset: () -> untyped + + def self.specification_record: () -> untyped + + # + # DOC: This method needs documented or nodoc'd + # + def self.unresolved_deps: () -> untyped + + # + # Load custom marshal format, re-initializing defaults as needed + # + def self._load: (untyped str) -> untyped + + def <=>: (untyped other) -> untyped + + def ==: (untyped other) -> untyped + + # + # Dump only crucial instance variables. + # + def _dump: (untyped limit) -> untyped + + # + # Activate this spec, registering it as a loaded spec and adding it's lib paths + # to $LOAD_PATH. Returns true if the spec was activated, false if it was + # previously activated. Freaks out if there are conflicts upon activation. + # + def activate: () -> (false | true) + + # + # Activate all unambiguously resolved runtime dependencies of this spec. Add any + # ambiguous dependencies to the unresolved list to be resolved later, as needed. + # + def activate_dependencies: () -> untyped + + # + # Abbreviate the spec for downloading. Abbreviated specs are only used for + # searching, downloading and related activities and do not need deployment + # specific information (e.g. list of files). So we abbreviate the spec, making + # it much smaller for quicker downloads. + # + def abbreviate: () -> untyped + + # + # Sanitize the descriptive fields in the spec. Sometimes non-ASCII characters + # will garble the site index. Non-ASCII characters will be replaced by their + # XML entity equivalent. + # + def sanitize: () -> untyped + + # + # Sanitize a single string. + # + def sanitize_string: (untyped string) -> untyped + + # + # Returns an array with bindir attached to each executable in the `executables` + # list + # + def add_bindir: (untyped executables) -> untyped + + private + + # + # Adds a dependency on gem `dependency` with type `type` that requires + # `requirements`. Valid types are currently `:runtime` and `:development`. + # + def add_dependency_with_type: (untyped dependency, untyped type, untyped requirements) -> untyped + + public + + # + # Adds a runtime dependency named `gem` with `requirements` to this gem. + # + # Usage: + # + # spec.add_runtime_dependency 'example', '~> 1.1', '>= 1.1.4' + # + alias add_runtime_dependency add_dependency + + # + # Adds this spec's require paths to LOAD_PATH, in the proper location. + # + def add_self_to_load_path: () -> (nil | untyped) + + # + # Singular reader for #authors. Returns the first author in the list + # + def author: () -> untyped + + # + # The list of author names who wrote this gem. + # + # spec.authors = ['Chad Fowler', 'Jim Weirich', 'Rich Kilmer'] + # + def authors: () -> untyped + + # + # Returns the full path to installed gem's bin directory. + # + # NOTE: do not confuse this with `bindir`, which is just 'bin', not a full path. + # + def bin_dir: () -> untyped + + # + # Returns the full path to an executable named `name` in this gem. + # + def bin_file: (untyped name) -> untyped + + # + # Returns the build_args used to install the gem + # + def build_args: () -> (untyped | ::Array[untyped]) + + def build_extensions: () -> (nil | untyped) + + # + # Returns the full path to the build info directory + # + def build_info_dir: () -> untyped + + # + # Returns the full path to the file containing the build information generated + # when the gem was installed + # + def build_info_file: () -> untyped + + # + # Returns the full path to the cache directory containing this spec's cached + # gem. + # + def cache_dir: () -> untyped + + # + # Returns the full path to the cached gem for this spec. + # + def cache_file: () -> untyped + + # + # Return any possible conflicts against the currently loaded specs. + # + def conflicts: () -> untyped + + def conficts_when_loaded_with?: (untyped list_of_specs) -> untyped + + # + # Return true if there are possible conflicts against the currently loaded + # specs. + # + def has_conflicts?: () -> untyped + + # + # The date this gem was created. + # + # If SOURCE_DATE_EPOCH is set as an environment variable, use that to support + # reproducible builds; otherwise, default to the current UTC date. + # + # Details on SOURCE_DATE_EPOCH: + # https://reproducible-builds.org/specs/source-date-epoch/ + # + def date: () -> untyped + + DateLike: untyped + + def self.===: (untyped obj) -> untyped + + DateTimeFormat: ::Regexp + + # + # The date this gem was created + # + # DO NOT set this, it is set automatically when the gem is packaged. + # + def date=: (untyped date) -> untyped + + def default_executable: () -> untyped + + # + # The default value for specification attribute `name` + # + def default_value: (untyped name) -> untyped + + # + # A list of Gem::Dependency objects this gem depends on. + # + # Use #add_dependency or #add_development_dependency to add dependencies to a + # gem. + # + def dependencies: () -> Array[Gem::Dependency] + + # + # Return a list of all gems that have a dependency on this gemspec. The list is + # structured with entries that conform to: + # + # [depending_gem, dependency, [list_of_gems_that_satisfy_dependency]] + # + def dependent_gems: (?bool check_dev) -> untyped + + # + # Returns all specs that matches this spec's runtime dependencies. + # + def dependent_specs: () -> untyped + + # + # A detailed description of this gem. See also #summary + # + def description=: (untyped str) -> untyped + + # + # List of dependencies that are used for development + # + def development_dependencies: () -> Array[Gem::Dependency] + + # + # Returns the full path to this spec's documentation directory. If `type` is + # given it will be appended to the end. For example: + # + # spec.doc_dir # => "/path/to/gem_repo/doc/a-1" + # + # spec.doc_dir 'ri' # => "/path/to/gem_repo/doc/a-1/ri" + # + def doc_dir: (?untyped? type) -> untyped + + def encode_with: (untyped coder) -> untyped + + def eql?: (untyped other) -> untyped + + # + # Singular accessor for #executables + # + def executable: () -> untyped + + # + # Singular accessor for #executables + # + def executable=: (untyped o) -> untyped + + # + # Sets executables to `value`, ensuring it is an array. + # + def executables=: (untyped value) -> untyped + + # + # Sets extensions to `extensions`, ensuring it is an array. + # + def extensions=: (untyped extensions) -> untyped + + # + # Sets extra_rdoc_files to `files`, ensuring it is an array. + # + def extra_rdoc_files=: (untyped files) -> untyped + + # + # The default (generated) file name of the gem. See also #spec_name. + # + # spec.file_name # => "example-1.0.gem" + # + def file_name: () -> ::String + + # + # Sets files to `files`, ensuring it is an array. + # + def files=: (untyped files) -> untyped + + private + + # + # Finds all gems that satisfy `dep` + # + def find_all_satisfiers: (untyped dep) { (untyped) -> untyped } -> untyped + + public + + # + # Creates a duplicate spec without large blobs that aren't used at runtime. + # + def for_cache: () -> untyped + + # + # + def full_name: () -> untyped + + def gem_dir: () -> untyped + + # + # + def gems_dir: () -> untyped + + def has_rdoc: () -> true + + def has_rdoc=: (untyped ignored) -> untyped + + alias has_rdoc? has_rdoc + + def has_unit_tests?: () -> untyped + + # :stopdoc: + alias has_test_suite? has_unit_tests? + + def hash: () -> untyped + + def init_with: (untyped coder) -> untyped + + # + # Specification constructor. Assigns the default values to the attributes and + # yields itself for further initialization. Optionally takes `name` and + # `version`. + # + def initialize: (?untyped? name, ?untyped? version) ?{ (untyped) -> untyped } -> void + + # + # Duplicates array_attributes from `other_spec` so state isn't shared. + # + def initialize_copy: (untyped other_spec) -> untyped + + # + # + def base_dir: () -> untyped + + private + + # + # Expire memoized instance variables that can incorrectly generate, replace or + # miss files due changes in certain attributes used to compute them. + # + def invalidate_memoized_attributes: () -> untyped + + public + + def inspect: () -> (untyped | ::String) + + # + # Files in the Gem under one of the require_paths + # + def lib_files: () -> untyped + + # + # Singular accessor for #licenses + # + def license: () -> untyped + + # + # Plural accessor for setting licenses + # + # See #license= for details + # + def licenses: () -> untyped + + def internal_init: () -> untyped + + def method_missing: (untyped sym, *untyped a) { (?) -> untyped } -> (nil | untyped) + + # + # Is this specification missing its extensions? When this returns true you + # probably want to build_extensions + # + def missing_extensions?: () -> (false | true) + + # + # Normalize the list of files so that: + # * All file lists have redundancies removed. + # * Files referenced in the extra_rdoc_files are included in the package file + # list. + # + def normalize: () -> untyped + + # + # Return a NameTuple that represents this Specification + # + def name_tuple: () -> untyped + + def original_name: () -> ::String + + def original_platform: () -> untyped + + # + # The platform this gem runs on. See Gem::Platform for details. + # + def platform: () -> untyped + + def pretty_print: (untyped q) -> untyped + + private + + def check_version_conflict: (untyped other) -> (nil | untyped) + + public + + def raise_if_conflicts: () -> (untyped | nil) + + # + # Sets rdoc_options to `value`, ensuring it is an array. + # + def rdoc_options=: (untyped options) -> untyped + + # + # Singular accessor for #require_paths + # + def require_path: () -> untyped + + # + # Singular accessor for #require_paths + # + def require_path=: (untyped path) -> untyped + + # + # Set requirements to `req`, ensuring it is an array. + # + def requirements=: (untyped req) -> untyped + + def respond_to_missing?: (untyped m, ?bool include_private) -> false + + # + # Returns the full path to this spec's ri directory. + # + def ri_dir: () -> untyped + + private + + # + # Return a string containing a Ruby code representation of the given object. + # + def ruby_code: (untyped obj) -> untyped + + public + + # + # List of dependencies that will automatically be activated at runtime. + # + def runtime_dependencies: () -> untyped + + private + + # + # True if this gem has the same attributes as `other`. + # + def same_attributes?: (untyped spec) -> untyped + + public + + # + # Checks if this specification meets the requirement of `dependency`. + # + def satisfies_requirement?: (untyped dependency) -> untyped + + # + # Returns an object you can use to sort specifications in #sort_by. + # + def sort_obj: () -> ::Array[untyped] + + def source: () -> untyped + + # + # Returns the full path to the directory containing this spec's gemspec file. + # eg: /usr/local/lib/ruby/gems/1.8/specifications + # + def spec_dir: () -> untyped + + # + # Returns the full path to this spec's gemspec file. eg: + # /usr/local/lib/ruby/gems/1.8/specifications/mygem-1.0.gemspec + # + def spec_file: () -> untyped + + # + # The default name of the gemspec. See also #file_name + # + # spec.spec_name # => "example-1.0.gemspec" + # + def spec_name: () -> ::String + + # + # A short summary of this gem's description. + # + def summary=: (untyped str) -> untyped + + def test_file: () -> untyped + + def test_file=: (untyped file) -> untyped + + def test_files: () -> untyped + + # + # Returns a Ruby code representation of this specification, such that it can be + # eval'ed and reconstruct the same specification later. Attributes that still + # have their default values are omitted. + # + def to_ruby: () -> untyped + + # + # Returns a Ruby lighter-weight code representation of this specification, used + # for indexing only. + # + # See #to_ruby. + # + def to_ruby_for_cache: () -> untyped + + def to_s: () -> ::String + + # + # Returns self + # + def to_spec: () -> self + + def to_yaml: (?::Hash[untyped, untyped] opts) -> untyped + + # + # Recursively walk dependencies of this spec, executing the `block` for each + # hop. + # + def traverse: (?untyped trail, ?::Hash[untyped, untyped] visited) { (?) -> untyped } -> untyped + + # + # Checks that the specification contains all required fields, and does a very + # basic sanity check. + # + # Raises InvalidSpecificationException if the spec does not pass the checks.. + # + def validate: (?bool packaging, ?bool strict) -> untyped + + # + # + def keep_only_files_and_directories: () -> untyped + + def validate_for_resolution: () -> untyped + + # + # + def validate_metadata: () -> untyped + + # + # + def validate_dependencies: () -> untyped + + # + # + def validate_permissions: () -> untyped + + # + # Set the version to `version`, potentially also setting + # required_rubygems_version if `version` indicates it is a prerelease. + # + def version=: (untyped version) -> (nil | untyped) + + # + # + def stubbed?: () -> false + + def yaml_initialize: (untyped tag, untyped vals) -> untyped + + # + # Reset nil attributes to their default values to make the spec valid + # + def reset_nil_attributes_to_default: () -> nil + + def flatten_require_paths: () -> (nil | untyped) + + def raw_require_paths: () -> untyped +end diff --git a/sig/shims/ast/2.4/.rbs_meta.yaml b/sig/shims/ast/2.4/.rbs_meta.yaml new file mode 100644 index 000000000..f361b3112 --- /dev/null +++ b/sig/shims/ast/2.4/.rbs_meta.yaml @@ -0,0 +1,9 @@ +--- +name: ast +version: '2.4' +source: + type: git + name: ruby/gem_rbs_collection + revision: c604d278dd6c14a1bb6cf0c0051af643b268a981 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems diff --git a/sig/shims/ast/2.4/ast.rbs b/sig/shims/ast/2.4/ast.rbs new file mode 100644 index 000000000..caba287ad --- /dev/null +++ b/sig/shims/ast/2.4/ast.rbs @@ -0,0 +1,73 @@ +module AST + interface _ToAst + def to_ast: () -> Node + end + + interface _ToSym + def to_sym: () -> Symbol + end + + class Node + public + + attr_reader children: Array[Node, Object] + attr_reader hash: String + attr_reader type: Symbol + + alias + concat + + alias << append + + def ==: (untyped other) -> bool + + def append: (untyped element) -> self + + alias clone dup + + def concat: (_ToA[untyped] array) -> self + + def dup: () -> self + + def eql?: (untyped other) -> bool + + def inspect: (?Integer indent) -> String + + alias to_a children + + def to_ast: () -> self + + alias to_s to_sexp + + def to_sexp: (?Integer indent) -> String + + def to_sexp_array: () -> Array[untyped] + + def updated: (?_ToSym? `type`, ?_ToA[untyped]? children, ?Hash[Symbol, untyped]? properties) -> self + + private + + def initialize: (_ToSym `type`, ?_ToA[untyped]? children, ?Hash[Symbol, untyped] properties) -> void + + alias original_dup dup + end + + class Processor + include Mixin + + module Mixin + public + + def handler_missing: (Node node) -> Node? + + def process: (_ToAst? node) -> Node? + + def process_all: (Array[_ToAst] nodes) -> Array[Node] + end + end + + module Sexp + public + + def s: (_ToSym `type`, *untyped children) -> Node + end +end From e6c5a589fc1351baae3e45d8c107c93450da50fd Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 08:43:45 -0400 Subject: [PATCH 027/460] Generic typechecking improvements --- lib/solargraph/api_map/index.rb | 19 ++- lib/solargraph/complex_type.rb | 57 ++++--- lib/solargraph/complex_type/unique_type.rb | 120 +++++++------ .../data_definition/data_assignment_node.rb | 1 + .../data_definition/data_definition_node.rb | 3 +- .../struct_assignment_node.rb | 2 + .../struct_definition_node.rb | 3 +- lib/solargraph/gem_pins.rb | 5 +- lib/solargraph/language_server/host.rb | 3 +- .../parser/flow_sensitive_typing.rb | 6 + lib/solargraph/parser/node_processor.rb | 5 +- .../parser/parser_gem/node_methods.rb | 2 +- .../parser_gem/node_processors/if_node.rb | 2 + lib/solargraph/parser/snippet.rb | 2 +- lib/solargraph/pin/base.rb | 8 +- lib/solargraph/pin/method.rb | 2 +- lib/solargraph/pin/parameter.rb | 6 +- lib/solargraph/rbs_map/conversions.rb | 6 +- lib/solargraph/source/chain/if.rb | 2 +- lib/solargraph/source/chain/or.rb | 2 +- lib/solargraph/type_checker.rb | 61 ++++--- lib/solargraph/type_checker/rules.rb | 26 ++- lib/solargraph/yard_map/mapper/to_method.rb | 4 +- spec/complex_type/conforms_to_spec.rb | 12 +- spec/complex_type_spec.rb | 8 +- spec/rbs_map/conversions_spec.rb | 87 ++++++---- spec/type_checker/levels/strong_spec.rb | 161 ++++++++++++++++++ 27 files changed, 435 insertions(+), 180 deletions(-) diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 810600534..42bb6cc32 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -34,7 +34,7 @@ def path_pin_hash # @param klass [Class>] # @return [Set>] def pins_by_class klass - # @type [Set] + # @type [Set>] s = Set.new @pin_select_cache[klass] ||= pin_class_hash.each_with_object(s) { |(key, o), n| n.merge(o) if key <= klass } end @@ -59,7 +59,8 @@ def superclass_references @superclass_references ||= Hash.new { |h, k| h[k] = [] } end - # @param pins [Array] + # @param pins [Enumerable] + # @return [self] def merge pins deep_clone.catalog pins end @@ -69,8 +70,9 @@ def merge pins attr_writer :pins, :pin_select_cache, :namespace_hash, :pin_class_hash, :path_pin_hash, :include_references, :extend_references, :prepend_references, :superclass_references + # @return [Solargraph::ApiMap::Index] def deep_clone - Index.allocate.tap do |copy| + out = Index.allocate.tap do |copy| copy.pin_select_cache = {} copy.pins = pins.clone %i[ @@ -81,9 +83,11 @@ def deep_clone copy.send(sym)&.transform_values!(&:clone) end end + out end - # @param new_pins [Array] + # @param new_pins [Enumerable] + # @return [self] def catalog new_pins @pin_select_cache = {} pins.concat new_pins @@ -104,7 +108,7 @@ def catalog new_pins end # @param klass [Class] - # @param hash [Hash{String => Array}] + # @param hash [Hash{String => Array}] # @return [void] def map_references klass, hash pins_by_class(klass).each do |pin| @@ -114,7 +118,7 @@ def map_references klass, hash # Add references to a map # - # @param hash [Hash{String => Array}] + # @param hash [Hash{String => Array}] # @param reference_pin [Pin::Reference] # # @return [void] @@ -138,9 +142,12 @@ def map_overrides pins = path_pin_hash[ovr.name] logger.debug { "ApiMap::Index#map_overrides: pins for path=#{ovr.name}: #{pins}" } pins.each do |pin| + next unless pin.is_a?(Pin::Reference::Override) + new_pin = if pin.path.end_with?('#initialize') path_pin_hash[pin.path.sub(/#initialize/, '.new')].first end + next unless new_pin.nil? || new_pin.is_a?(Pin::Method) (ovr.tags.map(&:tag_name) + ovr.delete).uniq.each do |tag| pin.docstring.delete_tags tag new_pin.docstring.delete_tags tag if new_pin diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 53c28ed6e..05e499998 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -17,15 +17,15 @@ def initialize types = [UniqueType::UNDEFINED] # @todo @items here should not need an annotation # @type [Array] items = types.flat_map(&:items).uniq(&:to_s) + if items.any? { |i| i.name == 'false' } && items.any? { |i| i.name == 'true' } items.delete_if { |i| i.name == 'false' || i.name == 'true' } - items.unshift(ComplexType::BOOLEAN) + items.unshift(UniqueType::BOOLEAN) end items = [UniqueType::UNDEFINED] if items.any?(&:undefined?) @items = items end - # @sg-ignore Fix "Not enough arguments to Module#protected" protected def equality_fields [self.class, items] end @@ -76,9 +76,13 @@ def self_to_type dst end # @yieldparam [UniqueType] + # @yieldreturn [UniqueType] # @return [Array] - def map &block - @items.map &block + # @sg-ignore Declared return type + # ::Array<::Solargraph::ComplexType::UniqueType> does not match + # inferred type ::Array<::Proc> for Solargraph::ComplexType#map + def map(&block) + @items.map(&block) end # @yieldparam [UniqueType] @@ -155,10 +159,12 @@ def to_s map(&:tag).join(', ') end + # @return [String] def tags map(&:tag).join(', ') end + # @return [String] def simple_tags simplify_literals.tags end @@ -172,6 +178,7 @@ def downcast_to_literal_if_possible ComplexType.new(items.map(&:downcast_to_literal_if_possible)) end + # @return [String] def desc rooted_tags end @@ -184,36 +191,34 @@ def desc # @param allow_reverse_match [Boolean] if true, check if any subtypes # of the expected type match the inferred type # @param allow_empty_params [Boolean] if true, allow a general - # inferred type without parameters to allow a more specific - # expcted type + # inferred type without parameters to conform to a more specific + # expected type # @param allow_any_match [Boolean] if true, any unique type - # matched in the expected qualifies as a match + # matched in the inferred qualifies as a match + # @param allow_undefined [Boolean] if true, treat undefined as a + # wildcard that matches anything + # @param rules [Array<:allow_subtype_skew, :allow_empty_params, :allow_reverse_match, :allow_any_match, :allow_undefined, :allow_unresolved_generic, :allow_unmatched_interface>] + # @param variance [:invariant, :covariant, :contravariant] # @return [Boolean] - def conforms_to? api_map, expected, + def conforms_to?(api_map, expected, situation, - variance: erased_variance(situation), - allow_subtype_skew: false, - allow_empty_params: false, - allow_reverse_match: false, - allow_any_match: false #, -# allow_undefined_in_expected: false + rules = [], + variance: erased_variance(situation)) expected = expected.downcast_to_literal_if_possible inferred = downcast_to_literal_if_possible return duck_types_match?(api_map, expected, inferred) if expected.duck_type? - if allow_any_match - inferred.any? { |inf| inf.conforms_to?(api_map, expected, situation, - allow_subtype_skew: allow_subtype_skew, - allow_empty_params: allow_empty_params, - allow_reverse_match: allow_reverse_match, - allow_any_match: allow_any_match) } + if rules.include? :allow_any_match + inferred.any? do |inf| + inf.conforms_to?(api_map, expected, situation, rules, + variance: variance) + end else - inferred.all? { |inf| inf.conforms_to?(api_map, expected, situation, - allow_subtype_skew: allow_subtype_skew, - allow_empty_params: allow_empty_params, - allow_reverse_match: allow_reverse_match, - allow_any_match: allow_any_match) } + inferred.all? do |inf| + inf.conforms_to?(api_map, expected, situation, rules, + variance: variance) + end end end @@ -231,6 +236,7 @@ def duck_types_match? api_map, expected, inferred true end + # @return [String] def rooted_tags map(&:rooted_tag).join(', ') end @@ -255,6 +261,7 @@ def generic? any?(&:generic?) end + # @return [ComplexType] def simplify_literals ComplexType.new(map(&:simplify_literals)) end diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 6bbe9a1bd..361fe06bb 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -11,7 +11,6 @@ class UniqueType attr_reader :all_params, :subtypes, :key_types - # @sg-ignore Fix "Not enough arguments to Module#protected" protected def equality_fields [@name, @all_params, @subtypes, @key_types] end @@ -78,6 +77,7 @@ def initialize(name, key_types = [], subtypes = [], rooted:, parameters_type: ni if parameters_type.nil? raise "You must supply parameters_type if you provide parameters" unless key_types.empty? && subtypes.empty? end + raise "Please remove leading :: and set rooted instead - #{name.inspect}" if name.start_with?('::') @name = name @parameters_type = parameters_type @@ -105,6 +105,7 @@ def to_s tag end + # @return [self] def simplify_literals transform do |t| next t unless t.literal? @@ -116,10 +117,12 @@ def literal? non_literal_name != name end + # @return [String] def non_literal_name @non_literal_name ||= determine_non_literal_name end + # @return [String] def determine_non_literal_name # https://github.com/ruby/rbs/blob/master/docs/syntax.md # @@ -186,35 +189,37 @@ def parameter_variance situation, default = :covariant end end + # Whether this is an RBS interface like _ToAry or _Each. + def interface? + name.start_with?('_') + end + # @param api_map [ApiMap] - # @param expected [ComplexType, ComplexType::UniqueType] + # @param expected [ComplexType::UniqueType] # @param situation [:method_call, :return_type] - # @param allow_subtype_skew [Boolean] if false, check if any - # subtypes of the expected type match the inferred type - # @param allow_empty_params [Boolean] if true, allow a general - # inferred type without parameters to allow a more specific - # expcted type - # @param allow_reverse_match [Boolean] if true, check if any subtypes - # of the expected type match the inferred type - # @param allow_any_match [Boolean] if true, any unique type - # matched in the expected qualifies as a match + # @param rules [Array<:allow_subtype_skew, :allow_empty_params, :allow_reverse_match, :allow_any_match, :allow_undefined, :allow_unresolved_generic, :allow_unmatched_interface>] + # @param variance [:invariant, :covariant, :contravariant] def conforms_to_unique_type?(api_map, expected, situation = :method_call, - variance: erased_variance(situation), - allow_subtype_skew:, - allow_empty_params:, - allow_reverse_match:, - allow_any_match:) - if allow_reverse_match - reversed_match = expected.conforms_to_unique_type? api_map, self, situation, allow_subtype_skew: allow_subtype_skew, - allow_empty_params: allow_empty_params, - allow_reverse_match: false, - allow_any_match: allow_any_match + rules = [], + variance: erased_variance(situation)) + raise "Expected type must be a UniqueType, got #{expected.class} in #{expected.inspect}" unless expected.is_a?(UniqueType) + if literal? && !expected.literal? + return simplify_literals.conforms_to_unique_type?(api_map, expected, situation, + rules, variance: variance) + end + return true if expected.any?(&:interface?) && rules.include?(:allow_unmatched_interface) + return true if interface? && rules.include?(:allow_unmatched_interface) + + if rules.include? :allow_reverse_match + reversed_match = expected.conforms_to?(api_map, self, situation, + rules - [:allow_reverse_match], + variance: variance) return true if reversed_match end expected = expected.downcast_to_literal_if_possible inferred = downcast_to_literal_if_possible - if allow_subtype_skew + if rules.include? :allow_subtype_skew # parameters are not considered in this case expected = expected.erase_parameters end @@ -223,6 +228,10 @@ def conforms_to_unique_type?(api_map, expected, situation = :method_call, inferred = inferred.erase_parameters end + if expected.parameters? && !inferred.parameters? && rules.include?(:allow_empty_params) + expected = expected.erase_parameters + end + return true if inferred == expected if variance == :invariant @@ -240,13 +249,13 @@ def conforms_to_unique_type?(api_map, expected, situation = :method_call, # we contain the expected mix-in, or we have a more general type return false unless api_map.type_include?(inferred.name, expected.name) || - map.super_and_sub?(inferred.name, expected.name) || + api_map.super_and_sub?(inferred.name, expected.name) || inferred.name == expected.name else raise "Unknown erased variance: #{erased_variance.inspect}" end - return true if inferred.all_params.empty? && allow_empty_params + return true if inferred.all_params.empty? && rules.include?(:allow_empty_params) # at this point we know the erased type is fine - time to look at parameters @@ -260,54 +269,50 @@ def conforms_to_unique_type?(api_map, expected, situation = :method_call, return false unless ComplexType.new(inferred.key_types).conforms_to?(api_map, ComplexType.new(expected.key_types), situation, - variance: parameter_variance(situation), - allow_subtype_skew: allow_subtype_skew, - allow_empty_params: allow_empty_params, - allow_reverse_match: allow_reverse_match, - allow_any_match: allow_any_match) + rules, + variance: parameter_variance(situation)) end return true if expected.subtypes.empty? + return true if expected.subtypes.any?(&:undefined?) && rules.include?(:allow_undefined) + + return true if inferred.subtypes.any?(&:undefined?) && rules.include?(:allow_undefined) + + return true if inferred.subtypes.all?(&:generic?) && rules.include?(:allow_unresolved_generic) + + return true if expected.subtypes.all?(&:generic?) && rules.include?(:allow_unresolved_generic) + return false if inferred.subtypes.empty? - ComplexType.new(inferred.subtypes).conforms_to?(api_map, ComplexType.new(expected.subtypes), situation, - variance: parameter_variance(situation), - allow_subtype_skew: allow_subtype_skew, - allow_empty_params: allow_empty_params, - allow_reverse_match: allow_reverse_match, - allow_any_match: allow_any_match) + ComplexType.new(inferred.subtypes).conforms_to?(api_map, + ComplexType.new(expected.subtypes), + situation, + rules, + variance: parameter_variance(situation)) end # @param api_map [ApiMap] - # @param expected [ComplexType::UniqueType] + # @param expected [ComplexType::UniqueType, ComplexType] # @param situation [:method_call, :assignment, :return] - # @param allow_subtype_skew [Boolean] if false, check if any - # subtypes of the expected type match the inferred type - # @param allow_empty_params [Boolean] if true, allow a general - # inferred type without parameters to allow a more specific - # expcted type - # @param allow_reverse_match [Boolean] if true, check if any subtypes - # of the expected type match the inferred type - # @param allow_any_match [Boolean] if true, any unique type - # matched in the expected qualifies as a match + # @param rules [Array<:allow_subtype_skew, :allow_empty_params, :allow_reverse_match, :allow_any_match, :allow_undefined, :allow_unresolved_generic>] + # @param variance [:invariant, :covariant, :contravariant] def conforms_to?(api_map, expected, situation = :method_call, - allow_subtype_skew:, - allow_empty_params:, - allow_reverse_match:, - allow_any_match:) + rules, + variance:) + + return true if undefined? && rules.include?(:allow_undefined) + # @todo teach this to validate duck types as inferred type return true if duck_type? # complex types as expectations are unions - we only need to # match one of their unique types expected.any? do |expected_unique_type| + raise "Expected type must be a UniqueType, got #{expected_unique_type.class} in #{expected.inspect}" unless expected.is_a?(UniqueType) unless expected_unique_type.instance_of?(UniqueType) conforms_to_unique_type?(api_map, expected_unique_type, situation, - allow_subtype_skew: allow_subtype_skew, - allow_empty_params: allow_empty_params, - allow_reverse_match: allow_reverse_match, - allow_any_match: allow_any_match) + rules, variance: variance) end end @@ -315,6 +320,7 @@ def hash [self.class, @name, @key_types, @sub_types, @rooted, @all_params, @parameters_type].hash end + # @return [self] def erase_parameters UniqueType.new(name, rooted: rooted?, parameters_type: parameters_type) end @@ -335,6 +341,7 @@ def rbs_name end end + # @return [String] def desc rooted_tags end @@ -407,7 +414,7 @@ def downcast_to_literal_if_possible # @param generics_to_resolve [Enumerable] # @param context_type [UniqueType, nil] - # @param resolved_generic_values [Hash{String => ComplexType}] Added to as types are encountered or resolved + # @param resolved_generic_values [Hash{String => ComplexType, UniqueType}] Added to as types are encountered or resolved # @return [UniqueType, ComplexType] def resolve_generics_from_context generics_to_resolve, context_type, resolved_generic_values: {} if name == ComplexType::GENERIC_TAG_NAME @@ -505,9 +512,9 @@ def to_a # @param new_name [String, nil] # @param make_rooted [Boolean, nil] - # @param new_key_types [Array, nil] + # @param new_key_types [Array, nil] # @param rooted [Boolean, nil] - # @param new_subtypes [Array, nil] + # @param new_subtypes [Array, nil] # @return [self] def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: nil) raise "Please remove leading :: and set rooted instead - #{new_name}" if new_name&.start_with?('::') @@ -589,6 +596,7 @@ def self_to_type dst end end + # @yieldreturn [Boolean] def any? &block block.yield self end diff --git a/lib/solargraph/convention/data_definition/data_assignment_node.rb b/lib/solargraph/convention/data_definition/data_assignment_node.rb index 7aadcf190..0ecfb88eb 100644 --- a/lib/solargraph/convention/data_definition/data_assignment_node.rb +++ b/lib/solargraph/convention/data_definition/data_assignment_node.rb @@ -22,6 +22,7 @@ class << self # s(:def, :foo, # s(:args), # s(:send, nil, :bar)))) + # @param node [Parser::AST::Node] def match?(node) return false unless node&.type == :casgn return false if node.children[2].nil? diff --git a/lib/solargraph/convention/data_definition/data_definition_node.rb b/lib/solargraph/convention/data_definition/data_definition_node.rb index dd5929822..5ee79b73d 100644 --- a/lib/solargraph/convention/data_definition/data_definition_node.rb +++ b/lib/solargraph/convention/data_definition/data_definition_node.rb @@ -25,6 +25,7 @@ class << self # s(:def, :foo, # s(:args), # s(:send, nil, :bar))) + # @param node [Parser::AST::Node] def match?(node) return false unless node&.type == :class @@ -46,7 +47,7 @@ def data_definition_node?(data_node) end end - # @return [Parser::AST::Node] + # @param node [Parser::AST::Node] def initialize(node) @node = node end diff --git a/lib/solargraph/convention/struct_definition/struct_assignment_node.rb b/lib/solargraph/convention/struct_definition/struct_assignment_node.rb index 04f96d40e..2816de6ed 100644 --- a/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_assignment_node.rb @@ -22,6 +22,8 @@ class << self # s(:def, :foo, # s(:args), # s(:send, nil, :bar)))) + # + # @param node [Parser::AST::Node] def match?(node) return false unless node&.type == :casgn return false if node.children[2].nil? diff --git a/lib/solargraph/convention/struct_definition/struct_definition_node.rb b/lib/solargraph/convention/struct_definition/struct_definition_node.rb index 540320c37..7c3d722d0 100644 --- a/lib/solargraph/convention/struct_definition/struct_definition_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_definition_node.rb @@ -25,6 +25,7 @@ class << self # s(:def, :foo, # s(:args), # s(:send, nil, :bar))) + # @param node [Parser::AST::Node] def match?(node) return false unless node&.type == :class @@ -46,7 +47,7 @@ def struct_definition_node?(struct_node) end end - # @return [Parser::AST::Node] + # @param node [Parser::AST::Node] def initialize(node) @node = node end diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index f1dd25a9f..2a3f392f6 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -25,13 +25,14 @@ def self.combine_method_pins_by_path(pins) # bad_pins = pins.select { |pin| pin.is_a?(Pin::Method) && pin.path == 'StringIO.open' && pin.source == :rbs }; raise "wtf: #{bad_pins}" if bad_pins.length > 1 method_pins, alias_pins = pins.partition { |pin| pin.class == Pin::Method } by_path = method_pins.group_by(&:path) - by_path.transform_values! do |pins| + combined_by_path = by_path.transform_values do |pins| GemPins.combine_method_pins(*pins) end - by_path.values + alias_pins + combined_by_path.values + alias_pins end def self.combine_method_pins(*pins) + # @type [Pin::Method, nil] out = pins.reduce(nil) do |memo, pin| next pin if memo.nil? if memo == pin && memo.source != :combined diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index 1c5831bda..e85fc813a 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -299,6 +299,7 @@ def prepare directory, name = nil end end + # @return [String] def command_path options['commandPath'] || 'solargraph' end @@ -716,7 +717,7 @@ def diagnoser # A hash of client requests by ID. The host uses this to keep track of # pending responses. # - # @return [Hash{Integer => Solargraph::LanguageServer::Host}] + # @return [Hash{Integer => Request}] def requests @requests ||= {} end diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 8fb26d498..8dd80a5a0 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -11,7 +11,9 @@ def initialize(locals, enclosing_breakable_pin = nil) # @param and_node [Parser::AST::Node] def process_and(and_node, true_ranges = []) + # @type [Parser::AST::Node] lhs = and_node.children[0] + # @type [Parser::AST::Node] rhs = and_node.children[1] before_rhs_loc = rhs.location.expression.adjust(begin_pos: -1) @@ -36,7 +38,9 @@ def process_if(if_node) # s(:send, nil, :bar)) # [4] pry(main)> conditional_node = if_node.children[0] + # @type [Parser::AST::Node] then_clause = if_node.children[1] + # @type [Parser::AST::Node] else_clause = if_node.children[2] true_ranges = [] @@ -142,6 +146,8 @@ def process_facts(facts_by_pin, presences) end # @param conditional_node [Parser::AST::Node] + # @param true_ranges [Array] + # @return [void] def process_conditional(conditional_node, true_ranges) if conditional_node.type == :send process_isa(conditional_node, true_ranges) diff --git a/lib/solargraph/parser/node_processor.rb b/lib/solargraph/parser/node_processor.rb index a55b7120b..a1a4d811f 100644 --- a/lib/solargraph/parser/node_processor.rb +++ b/lib/solargraph/parser/node_processor.rb @@ -23,6 +23,9 @@ def register type, cls @@processors[type] << cls end + # @param type [Symbol] + # @param cls [Class] + # @return [void] def deregister type, cls @@processors[type].delete(cls) end @@ -31,7 +34,7 @@ def deregister type, cls # @param node [Parser::AST::Node] # @param region [Region] # @param pins [Array] - # @param locals [Array] + # @param locals [Array] # @return [Array(Array, Array)] def self.process node, region = Region.new, pins = [], locals = [] if pins.empty? diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index bc0c37eb6..674013257 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -345,7 +345,7 @@ def value_position_nodes_only(node) # Look at known control statements and use them to find # more specific return nodes. # - # @param node [Parser::AST::Node] Statement which is in + # @param node [AST::Node] Statement which is in # value position for a method body # @param include_explicit_returns [Boolean] If true, # include the value nodes of the parameter of the diff --git a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb index 5784afcbe..2452b9cc5 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb @@ -11,6 +11,8 @@ def process process_children position = get_node_start_position(node) + # @sg-ignore + # @type [Solargraph::Pin::Breakable, nil] enclosing_breakable_pin = pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location.range.contain?(position)}.last FlowSensitiveTyping.new(locals, enclosing_breakable_pin).process_if(node) end diff --git a/lib/solargraph/parser/snippet.rb b/lib/solargraph/parser/snippet.rb index d28c57c8c..3b609d31b 100644 --- a/lib/solargraph/parser/snippet.rb +++ b/lib/solargraph/parser/snippet.rb @@ -1,7 +1,7 @@ module Solargraph module Parser class Snippet - # @return [Range] + # @return [Solargraph::Range] attr_reader :range # @return [String] attr_reader :text diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index cdd6a5ace..c9e308056 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -269,9 +269,13 @@ def assert_same_count(other, attr) # @param other [self] # @param attr [::Symbol] # - # @return [Object, nil] + # @return [undefined] def assert_same(other, attr) - return false if other.nil? + if other.nil? + Solargraph.assert_or_log("combine_with_#{attr}".to_sym, + "Inconsistent #{attr.inspect} values between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self.#{attr} = #{val1.inspect}\nother.#{attr} = #{val2.inspect}") + return send(attr) + end val1 = send(attr) val2 = other.send(attr) return val1 if val1 == val2 diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 749868246..c3e29b8e3 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -50,7 +50,7 @@ def combine_all_signature_pins(*signature_pins) end # @param other [Pin::Method] - # @return [Symbol] + # @return [::Symbol] def combine_visibility(other) if dodgy_visibility_source? && !other.dodgy_visibility_source? other.visibility diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index b4fc3d9b2..d806d6de1 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -171,11 +171,7 @@ def compatible_arg?(atype, api_map) return true if atype.conforms_to?(api_map, ptype, :method_call, - allow_subtype_skew: false, - allow_reverse_match: false, - allow_empty_params: true, - allow_any_match: false) - + [:allow_empty_params, :allow_undefined]) ptype.generic? end diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 6e50c022a..44fb72946 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -95,7 +95,7 @@ def convert_self_type_to_pins decl, closure type = build_type(decl.name, decl.args) generic_values = type.all_params.map(&:to_s) include_pin = Solargraph::Pin::Reference::Include.new( - name: decl.name.relative!.to_s, + name: type.rooted_name, type_location: location_decl_to_pin_location(decl.location), generic_values: generic_values, closure: closure, @@ -184,7 +184,7 @@ def class_decl_to_pin decl type_location: location_decl_to_pin_location(decl.super_class.location), closure: class_pin, generic_values: generic_values, - name: decl.super_class.name.relative!.to_s, + name: type.rooted_name, source: :rbs ) end @@ -229,6 +229,8 @@ def module_decl_to_pin decl convert_self_types_to_pins decl, module_pin convert_members_to_pins decl, module_pin + raise "Invalid type for module declaration: #{module_pin.class}" unless module_pin.is_a?(Pin::Namespace) + add_mixins decl, module_pin.closure end diff --git a/lib/solargraph/source/chain/if.rb b/lib/solargraph/source/chain/if.rb index c14d00ddf..3a7fa0ca9 100644 --- a/lib/solargraph/source/chain/if.rb +++ b/lib/solargraph/source/chain/if.rb @@ -8,7 +8,7 @@ def word '' end - # @param links [::Array] + # @param links [::Array] def initialize links @links = links end diff --git a/lib/solargraph/source/chain/or.rb b/lib/solargraph/source/chain/or.rb index 1e3a70f40..9264d4107 100644 --- a/lib/solargraph/source/chain/or.rb +++ b/lib/solargraph/source/chain/or.rb @@ -8,7 +8,7 @@ def word '' end - # @param links [::Array] + # @param links [::Array] def initialize links @links = links end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index ab87d5863..953832a36 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -36,6 +36,35 @@ def source_map @source_map ||= api_map.source_map(filename) end + # @return [Source] + def source + @source_map.source + end + + def return_type_conforms_to?(inferred, expected) + conforms_to?(inferred, expected, :return_type) + end + + def arg_conforms_to?(inferred, expected) + conforms_to?(inferred, expected, :method_call) + end + + def assignment_conforms_to?(inferred, expected) + conforms_to?(inferred, expected, :assignment) + end + + def conforms_to?(inferred, expected, scenario) + rules_arr = [] + rules_arr << :allow_empty_params unless rules.require_inferred_type_params? + rules_arr << :allow_any_match unless rules.require_all_unique_types_match_declared? + rules_arr << :allow_undefined unless rules.require_no_undefined_args? + rules_arr << :allow_unresolved_generic unless rules.require_generics_resolved? + rules_arr << :allow_unmatched_interface unless rules.require_interfaces_resolved? + rules_arr << :allow_reverse_match unless rules.require_downcasts? + inferred.conforms_to?(api_map, expected, scenario, + rules_arr) + end + # @return [Array] def problems @problems ||= begin @@ -111,11 +140,7 @@ def method_return_type_problems_for pin result.push Problem.new(pin.location, "#{pin.path} return type could not be inferred", pin: pin) end else - unless inferred.conforms_to?(api_map, declared, :return_type, - allow_subtype_skew: false, - allow_empty_params: !rules.require_inferred_type_params, - allow_reverse_match: false, - allow_any_match: !rules.require_all_unique_types_match_declared?) + unless return_type_conforms_to?(inferred, declared) result.push Problem.new(pin.location, "Declared return type #{declared.rooted_tags} does not match inferred type #{inferred.rooted_tags} for #{pin.path}", pin: pin) end end @@ -204,11 +229,7 @@ def variable_type_tag_problems result.push Problem.new(pin.location, "Variable type could not be inferred for #{pin.name}", pin: pin) end else - unless inferred.conforms_to?(api_map, declared, :assignment, - allow_subtype_skew: false, - allow_empty_params: !rules.require_inferred_type_params, - allow_reverse_match: false, - allow_any_match: !rules.require_all_unique_types_match_declared?) + unless assignment_conforms_to?(inferred, declared) result.push Problem.new(pin.location, "Declared type #{declared} does not match inferred type #{inferred} for variable #{pin.name}", pin: pin) end end @@ -389,11 +410,7 @@ def argument_problems_for chain, api_map, block_pin, locals, location # @todo Some level (strong, I guess) should require the param here else argtype = argchain.infer(api_map, block_pin, locals) - if argtype.defined? && ptype.defined? && !argtype.conforms_to?(api_map, ptype, :method_call, - allow_subtype_skew: false, - allow_empty_params: !rules.require_inferred_type_params, - allow_reverse_match: false, - allow_any_match: !rules.require_all_unique_types_match_declared?) + if argtype.defined? && ptype.defined? && !arg_conforms_to?(argtype, ptype) errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") next end @@ -443,13 +460,8 @@ def kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, else ptype = data[:qualified] unless ptype.undefined? - argtype = argchain.infer(api_map, block_pin, locals) - if argtype.defined? && ptype && !argtype.conforms_to?(api_map, ptype, :method_call, - allow_subtype_skew: false, - allow_empty_params: !rules.require_inferred_type_params, - allow_reverse_match: false, - allow_any_match: !rules.require_all_unique_types_match_declared?) + if argtype.defined? && ptype && !arg_conforms_to?(argtype, ptype) result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") end end @@ -475,12 +487,7 @@ def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kw next unless params.key?(pname.to_s) ptype = params[pname.to_s][:qualified] argtype = argchain.infer(api_map, block_pin, locals) - if argtype.defined? && ptype && !argtype.conforms_to?(api_map, ptype, :method_call, - allow_subtype_skew: false, - allow_empty_params: !rules.require_inferred_type_params, - allow_reverse_match: false, - allow_any_match: !rules.require_all_unique_types_match_declared?) - + if argtype.defined? && ptype && !arg_conforms_to?(argtype, ptype) result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}") end end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 8f2027d30..33ec0c4d0 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -54,13 +54,37 @@ def validate_tags? rank > LEVELS[:normal] end - def require_inferred_type_params + def require_inferred_type_params? rank >= LEVELS[:alpha] end def require_all_unique_types_match_declared? rank >= LEVELS[:alpha] end + + def require_no_undefined_args? + rank >= LEVELS[:alpha] + end + + def require_generics_resolved? + rank >= LEVELS[:alpha] + end + + def require_interfaces_resolved? + rank >= LEVELS[:alpha] + end + + def require_downcasts? + rank >= LEVELS[:alpha] + end + + # We keep this at strong because if you added an @sg-ignore to + # address a strong-level issue, then ran at a lower level, you'd + # get a false positive - we don't run stronger level checks than + # requested for performance reasons + def validate_sg_ignores? + rank >= LEVELS[:strong] + end end end end diff --git a/lib/solargraph/yard_map/mapper/to_method.rb b/lib/solargraph/yard_map/mapper/to_method.rb index df431bb3c..d8e3b8b43 100644 --- a/lib/solargraph/yard_map/mapper/to_method.rb +++ b/lib/solargraph/yard_map/mapper/to_method.rb @@ -27,8 +27,8 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = final_scope = scope || code_object.scope override_key = [closure.path, final_scope, name] final_visibility = VISIBILITY_OVERRIDE[override_key] - final_visibility ||= VISIBILITY_OVERRIDE[override_key[0..-2]] - final_visibility ||= :private if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(name) + final_visibility ||= VISIBILITY_OVERRIDE[[closure.path, final_scope]] + final_visibility ||= :private if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(name.to_sym) final_visibility ||= visibility final_visibility ||= :private if code_object.module_function? && final_scope == :instance final_visibility ||= :public if code_object.module_function? && final_scope == :class diff --git a/spec/complex_type/conforms_to_spec.rb b/spec/complex_type/conforms_to_spec.rb index 847da8563..5755721b4 100644 --- a/spec/complex_type/conforms_to_spec.rb +++ b/spec/complex_type/conforms_to_spec.rb @@ -67,7 +67,7 @@ class Sub < Sup; end api_map = Solargraph::ApiMap.new exp = Solargraph::ComplexType.parse('Hash{ Symbol => String}') inf = Solargraph::ComplexType.parse('Hash') - match = inf.conforms_to?(api_map, exp, :method_call, allow_empty_params: true) + match = inf.conforms_to?(api_map, exp, :method_call, [:allow_empty_params]) expect(match).to be(true) end @@ -115,7 +115,7 @@ class Sub < Sup; end api_map = Solargraph::ApiMap.new exp = Solargraph::ComplexType.parse('Class') inf = Solargraph::ComplexType.parse('Class') - match = inf.conforms_to?(api_map, exp, :method_call, allow_empty_params: true) + match = inf.conforms_to?(api_map, exp, :method_call, [:allow_empty_params]) expect(match).to be(true) end @@ -136,9 +136,9 @@ class Sub < Sup; end api_map.map source sup = Solargraph::ComplexType.parse('Sup') sub = Solargraph::ComplexType.parse('Sub') - match = sub.conforms_to?(api_map, sup, :method_call, allow_reverse_match: true) + match = sub.conforms_to?(api_map, sup, :method_call, [:allow_reverse_match]) expect(match).to be(true) - match = sup.conforms_to?(api_map, sub, :method_call, allow_reverse_match: true) + match = sup.conforms_to?(api_map, sub, :method_call, [:allow_reverse_match]) expect(match).to be(true) end @@ -146,9 +146,9 @@ class Sub < Sup; end api_map = Solargraph::ApiMap.new sup = Solargraph::ComplexType.parse('String') sub = Solargraph::ComplexType.parse('Array') - match = sub.conforms_to?(api_map, sup, :method_call, allow_reverse_match: true) + match = sub.conforms_to?(api_map, sup, :method_call, [:allow_reverse_match]) expect(match).to be(false) - match = sup.conforms_to?(api_map, sub, :method_call, allow_reverse_match: true) + match = sup.conforms_to?(api_map, sub, :method_call, [:allow_reverse_match]) expect(match).to be(false) end end diff --git a/spec/complex_type_spec.rb b/spec/complex_type_spec.rb index 2c060ceed..dd20099eb 100644 --- a/spec/complex_type_spec.rb +++ b/spec/complex_type_spec.rb @@ -738,28 +738,28 @@ def make_bar api_map = Solargraph::ApiMap.new ptype = Solargraph::ComplexType.parse('String') atype = Solargraph::ComplexType.parse('String') - expect(ptype.conforms_to?(api_map, atype, :method_call)).to be(true) + expect(atype.conforms_to?(api_map, ptype, :method_call)).to be(true) end it 'recognizes an erased container type conforms with itself' do api_map = Solargraph::ApiMap.new ptype = Solargraph::ComplexType.parse('Hash') atype = Solargraph::ComplexType.parse('Hash') - expect(ptype.conforms_to?(api_map, atype, :method_call)).to be(true) + expect(atype.conforms_to?(api_map, ptype, :method_call)).to be(true) end it 'recognizes an unerased container type conforms with itself' do api_map = Solargraph::ApiMap.new ptype = Solargraph::ComplexType.parse('Array') atype = Solargraph::ComplexType.parse('Array') - expect(ptype.conforms_to?(api_map, atype, :method_call)).to be(true) + expect(atype.conforms_to?(api_map, ptype, :method_call)).to be(true) end it 'recognizes a literal conforms with its type' do api_map = Solargraph::ApiMap.new ptype = Solargraph::ComplexType.parse('Symbol') atype = Solargraph::ComplexType.parse(':foo') - expect(ptype.conforms_to?(api_map, atype, :method_call)).to be(true) + expect(atype.conforms_to?(api_map, ptype, :method_call)).to be(true) end end end diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 09c203687..75ec1c311 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -1,54 +1,75 @@ describe Solargraph::RbsMap::Conversions do - # create a temporary directory with the scope of the spec - around do |example| - require 'tmpdir' - Dir.mktmpdir("rspec-solargraph-") do |dir| - @temp_dir = dir - example.run + context 'with custom RBS files' do + # create a temporary directory with the scope of the spec + around do |example| + require 'tmpdir' + Dir.mktmpdir("rspec-solargraph-") do |dir| + @temp_dir = dir + example.run + end end - end - let(:rbs_repo) do - RBS::Repository.new(no_stdlib: false) - end + let(:rbs_repo) do + RBS::Repository.new(no_stdlib: false) + end - let(:loader) do - RBS::EnvironmentLoader.new(core_root: nil, repository: rbs_repo) - end + let(:loader) do + RBS::EnvironmentLoader.new(core_root: nil, repository: rbs_repo) + end - let(:conversions) do - Solargraph::RbsMap::Conversions.new(loader: loader) - end + let(:conversions) do + Solargraph::RbsMap::Conversions.new(loader: loader) + end - let(:pins) do - conversions.pins - end + let(:pins) do + conversions.pins + end - before do - rbs_file = File.join(temp_dir, 'foo.rbs') - File.write(rbs_file, rbs) - loader.add(path: Pathname(temp_dir)) - end + before do + rbs_file = File.join(temp_dir, 'foo.rbs') + File.write(rbs_file, rbs) + loader.add(path: Pathname(temp_dir)) + end - attr_reader :temp_dir + attr_reader :temp_dir - context 'with untyped response' do - let(:rbs) do - <<~RBS + context 'with untyped response' do + let(:rbs) do + <<~RBS class Foo def bar: () -> untyped end RBS + end + + subject(:method_pin) { pins.find { |pin| pin.path == 'Foo#bar' } } + + it { should_not be_nil } + + it { should be_a(Solargraph::Pin::Method) } + + it 'maps untyped in RBS to undefined in Solargraph 'do + expect(method_pin.return_type.tag).to eq('undefined') + end end + end - subject(:method_pin) { pins.find { |pin| pin.path == 'Foo#bar' } } + context 'with standard loads for solargraph project' do + let(:api_map) { Solargraph::ApiMap.load('.') } - it { should_not be_nil } + let(:superclass_pin) do + api_map.pins.find do |pin| + pin.is_a?(Solargraph::Pin::Reference::Superclass) && pin.context.namespace == 'Parser::AST::Node' + end + end - it { should be_a(Solargraph::Pin::Method) } + it 'finds a superclass pin for Parser::AST::Node' do + expect(superclass_pin).not_to be_nil + end - it 'maps untyped in RBS to undefined in Solargraph 'do - expect(method_pin.return_type.tag).to eq('undefined') + it 'generates a rooted pin for superclass of Parser::AST::Node' do + # rooted! + expect(superclass_pin.name) .to eq('::AST::Node') end end end diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 054a09efa..4c45056ea 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -141,6 +141,167 @@ def bar &block expect(checker.problems).to be_empty end + it 'does not need fully specified container types' do + checker = type_checker(%( + class Foo + # @param foo [Array] + # @return [void] + def bar foo: []; end + + # @param bing [Array] + # @return [void] + def baz(bing) + bar(foo: bing) + generic_values = [1,2,3].map(&:to_s) + bar(foo: generic_values) + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'treats a parameter type of undefined as not provided' do + checker = type_checker(%( + class Foo + # @param foo [Array] + # @return [void] + def bar foo: []; end + + # @param bing [Array] + # @return [void] + def baz(bing) + bar(foo: bing) + generic_values = [1,2,3].map(&:to_s) + bar(foo: generic_values) + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'treats a parameter type of undefined as not provided' do + checker = type_checker(%( + class Foo + # @param foo [Class] + # @return [void] + def bar foo:; end + + # @param bing [Class] + # @return [void] + def baz(bing) + bar(foo: bing) + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'ignores generic resolution failures' do + checker = type_checker(%( + class Foo + # @param foo [Class] + # @return [void] + def bar foo:; end + + # @param bing [Class>] + # @return [void] + def baz(bing) + bar(foo: bing) + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'ignores undefined resolution failures' do + checker = type_checker(%( + class Foo + # @generic T + # @param klass [Class>] + # @return [Set>] + def pins_by_class klass; [].to_set; end + end + class Bar + # @return [Enumerable] + def block_pins + foo = Foo.new + foo.pins_by_class(Integer) + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + + it 'ignores generic resolution failures' do + checker = type_checker(%( + class Foo + # @generic T + # @param klass [Class>] + # @return [Set>] + def pins_by_class klass; [].to_set; end + end + class Bar + # @return [Enumerable] + def block_pins + foo = Foo.new + foo.pins_by_class(Integer) + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'ignores generic resolution failures' do + checker = type_checker(%( + # @generic T + # @param path [String] + # @param klass [Class>] + # @return [void] + def code_object_at path, klass = Integer + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'does not complain on select { is_a? } pattern' do + checker = type_checker(%( + # @param arr [Enumerable} + # @return [Enumerable] + def downcast_arr(arr) + arr.select { |pin| pin.is_a?(Integer) } + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'does not complain on adding nil to types via return value' do + checker = type_checker(%( + # @param bar [Integer] + # @return [Integer, nil] + def foo(bar) + bar + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'does not complain on adding nil to types via select' do + checker = type_checker(%( + # @return [Float, nil]} + def bar; rand; end + + # @param arr [Enumerable} + # @return [Integer, nil] + def downcast_arr(arr) + # @type [Object, nil] + foo = arr.select { |pin| pin.is_a?(Integer) && bar }.last + foo + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'inherits param tags from superclass methods' do checker = type_checker(%( class Foo From 898bb87e30bdf9f9837bb986deb467e7b5324404 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 09:11:41 -0400 Subject: [PATCH 028/460] Fix specs --- lib/solargraph/api_map/index.rb | 11 ++++------- lib/solargraph/doc_map.rb | 2 ++ lib/solargraph/rbs_map/core_map.rb | 5 ++++- spec/rbs_map/core_map_spec.rb | 2 +- spec/type_checker/levels/strict_spec.rb | 13 +++++++++++++ spec/type_checker/levels/typed_spec.rb | 12 ------------ 6 files changed, 24 insertions(+), 21 deletions(-) diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 42bb6cc32..9015cd6d5 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -39,22 +39,22 @@ def pins_by_class klass @pin_select_cache[klass] ||= pin_class_hash.each_with_object(s) { |(key, o), n| n.merge(o) if key <= klass } end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def include_references @include_references ||= Hash.new { |h, k| h[k] = [] } end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def extend_references @extend_references ||= Hash.new { |h, k| h[k] = [] } end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def prepend_references @prepend_references ||= Hash.new { |h, k| h[k] = [] } end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def superclass_references @superclass_references ||= Hash.new { |h, k| h[k] = [] } end @@ -142,12 +142,9 @@ def map_overrides pins = path_pin_hash[ovr.name] logger.debug { "ApiMap::Index#map_overrides: pins for path=#{ovr.name}: #{pins}" } pins.each do |pin| - next unless pin.is_a?(Pin::Reference::Override) - new_pin = if pin.path.end_with?('#initialize') path_pin_hash[pin.path.sub(/#initialize/, '.new')].first end - next unless new_pin.nil? || new_pin.is_a?(Pin::Method) (ovr.tags.map(&:tag_name) + ovr.delete).uniq.each do |tag| pin.docstring.delete_tags tag new_pin.docstring.delete_tags tag if new_pin diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index d51fc3022..186037460 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -150,6 +150,8 @@ def load_serialized_gem_pins @uncached_yard_gemspecs = [] @uncached_rbs_collection_gemspecs = [] with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v } + # @sg-ignore Need Hash[] support + # @type [Array] paths = Hash[without_gemspecs].keys gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a diff --git a/lib/solargraph/rbs_map/core_map.rb b/lib/solargraph/rbs_map/core_map.rb index 0d265d773..5e030d9f6 100644 --- a/lib/solargraph/rbs_map/core_map.rb +++ b/lib/solargraph/rbs_map/core_map.rb @@ -24,8 +24,11 @@ def pins else loader.add(path: Pathname(FILLS_DIRECTORY)) @pins = conversions.pins + # add some overrides @pins.concat RbsMap::CoreFills::ALL - processed = ApiMap::Store.new(pins).pins.reject { |p| p.is_a?(Solargraph::Pin::Reference::Override) } + # process overrides, then remove any which couldn't be resolved + processed = ApiMap::Store.new(@pins).pins.reject { |p| p.is_a?(Solargraph::Pin::Reference::Override) } + STDOUT.puts "RBS core pins cache size: #{@pins.size}" @pins.replace processed PinCache.serialize_core @pins diff --git a/spec/rbs_map/core_map_spec.rb b/spec/rbs_map/core_map_spec.rb index 352d29937..88590925b 100644 --- a/spec/rbs_map/core_map_spec.rb +++ b/spec/rbs_map/core_map_spec.rb @@ -6,7 +6,7 @@ pin = store.get_path_pins("Errno::#{const}").first expect(pin).to be_a(Solargraph::Pin::Namespace) superclass = store.get_superclass(pin.path) - expect(superclass).to eq('SystemCallError') + expect(superclass).to eq('::SystemCallError') end end diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index b198cec89..7861c8817 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -666,6 +666,19 @@ def test(foo: nil) expect(checker.problems).to be_empty end + + it 'validates parameters in function calls' do + checker = type_checker(%( + # @param bar [String] + def foo(bar); end + + def baz + foo(123) + end + )) + expect(checker.problems.map(&:message)).to eq(['Wrong argument type for #foo: bar expected String, received 123']) + end + it 'validates inferred return types with complex tags' do checker = type_checker(%( # @param foo [Numeric, nil] a foo diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index 659ccee39..6e71ee9ff 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -202,18 +202,6 @@ def foo expect(checker.problems).to be_empty end - it 'validates parameters in function calls' do - checker = type_checker(%( - # @param bar [String] - def foo(bar); end - - def baz - foo(123) - end - )) - expect(checker.problems.map(&:message)).to eq(['123']) - end - it 'validates default values of parameters' do checker = type_checker(%( # @param bar [String] From b6bfe7b4fd9c8a52038cc096bd29050e244b6fed Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 10:11:45 -0400 Subject: [PATCH 029/460] Generic typechecking improvements --- .../language_server/message/base.rb | 2 +- .../message/extended/check_gem_version.rb | 6 - rbs/fills/rubygems/0/spec_fetcher.rbs | 107 ++++++++++++++++++ 3 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 rbs/fills/rubygems/0/spec_fetcher.rbs diff --git a/lib/solargraph/language_server/message/base.rb b/lib/solargraph/language_server/message/base.rb index fbc55ccbd..b2df0c46a 100644 --- a/lib/solargraph/language_server/message/base.rb +++ b/lib/solargraph/language_server/message/base.rb @@ -16,7 +16,7 @@ class Base # @return [String] attr_reader :method - # @return [Hash{String => Array, Hash{String => undefined}, String, Integer}] + # @return [Hash{String => undefined}] attr_reader :params # @return [Hash, Array, nil] diff --git a/lib/solargraph/language_server/message/extended/check_gem_version.rb b/lib/solargraph/language_server/message/extended/check_gem_version.rb index 2e80f40c6..06892ed19 100644 --- a/lib/solargraph/language_server/message/extended/check_gem_version.rb +++ b/lib/solargraph/language_server/message/extended/check_gem_version.rb @@ -1,12 +1,6 @@ # frozen_string_literal: true -# @todo PR the RBS gem to add this -# @!parse -# module ::Gem -# class SpecFetcher; end -# end - module Solargraph module LanguageServer module Message diff --git a/rbs/fills/rubygems/0/spec_fetcher.rbs b/rbs/fills/rubygems/0/spec_fetcher.rbs new file mode 100644 index 000000000..9914dc85d --- /dev/null +++ b/rbs/fills/rubygems/0/spec_fetcher.rbs @@ -0,0 +1,107 @@ +# +# SpecFetcher handles metadata updates from remote gem repositories. +# +class Gem::SpecFetcher + self.@fetcher: untyped + + @sources: untyped + + @update_cache: untyped + + @specs: untyped + + @latest_specs: untyped + + @prerelease_specs: untyped + + @caches: untyped + + @fetcher: untyped + + include Gem::UserInteraction + + include Gem::Text + + attr_reader latest_specs: untyped + + attr_reader sources: untyped + + attr_reader specs: untyped + + attr_reader prerelease_specs: untyped + + # + # Default fetcher instance. Use this instead of ::new to reduce object + # allocation. + # + def self.fetcher: () -> untyped + + def self.fetcher=: (untyped fetcher) -> untyped + + # + # Creates a new SpecFetcher. Ordinarily you want to use the default fetcher + # from Gem::SpecFetcher::fetcher which uses the Gem.sources. + # + # If you need to retrieve specifications from a different `source`, you can send + # it as an argument. + # + def initialize: (?untyped? sources) -> void + + # + # Find and fetch gem name tuples that match `dependency`. + # + # If `matching_platform` is false, gems for all platforms are returned. + # + def search_for_dependency: (untyped dependency, ?bool matching_platform) -> ::Array[untyped] + + # + # Return all gem name tuples who's names match `obj` + # + def detect: (?::Symbol type) { (untyped) -> untyped } -> untyped + + # + # Find and fetch specs that match `dependency`. + # + # If `matching_platform` is false, gems for all platforms are returned. + # + def spec_for_dependency: (untyped dependency, ?bool matching_platform) -> ::Array[untyped] + + # + # Suggests gems based on the supplied `gem_name`. Returns an array of + # alternative gem names. + # + def suggest_gems_from_name: (untyped gem_name, ?::Symbol type, ?::Integer num_results) -> (::Array[untyped] | untyped) + + # + # Returns a list of gems available for each source in Gem::sources. + # + # `type` can be one of 3 values: :released => Return the list of all released + # specs :complete => Return the list of all specs :latest => Return the + # list of only the highest version of each gem :prerelease => Return the list of + # all prerelease only specs + # + def available_specs: (untyped type) -> ::Array[untyped] + + def tuples_for: (untyped source, untyped type, ?bool gracefully_ignore) -> untyped +end From 0d0bf6419c0deed85388a454b534ba4a2c5b28f8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 17:33:33 -0400 Subject: [PATCH 030/460] Type annotation improvements --- lib/solargraph/api_map/index.rb | 2 + lib/solargraph/bench.rb | 1 + lib/solargraph/complex_type/type_methods.rb | 1 + .../convention/struct_definition.rb | 2 +- lib/solargraph/doc_map.rb | 1 + lib/solargraph/gem_pins.rb | 6 +- .../language_server/host/dispatch.rb | 2 + .../language_server/host/message_worker.rb | 3 + .../language_server/message/base.rb | 1 + .../message/text_document/definition.rb | 2 + .../message/text_document/type_definition.rb | 1 + .../workspace/did_change_workspace_folders.rb | 2 + lib/solargraph/language_server/progress.rb | 8 + lib/solargraph/language_server/request.rb | 1 + lib/solargraph/location.rb | 2 + lib/solargraph/parser/comment_ripper.rb | 7 + lib/solargraph/parser/node_processor.rb | 4 +- lib/solargraph/parser/region.rb | 3 + lib/solargraph/parser/snippet.rb | 2 + lib/solargraph/pin/base.rb | 15 +- lib/solargraph/pin/callable.rb | 9 + lib/solargraph/pin/constant.rb | 1 + lib/solargraph/pin/local_variable.rb | 2 +- lib/solargraph/pin/method.rb | 2 +- lib/solargraph/pin/method_alias.rb | 2 + lib/solargraph/pin/proxy_type.rb | 1 + lib/solargraph/pin/reference/override.rb | 16 +- lib/solargraph/pin/search.rb | 2 + lib/solargraph/pin/signature.rb | 2 + lib/solargraph/pin/symbol.rb | 1 + lib/solargraph/position.rb | 1 + lib/solargraph/rbs_map/conversions.rb | 4 +- lib/solargraph/source.rb | 2 + lib/solargraph/source/chain.rb | 5 +- lib/solargraph/source_map.rb | 6 +- lib/solargraph/source_map/data.rb | 4 + lib/solargraph/type_checker/param_def.rb | 2 + lib/solargraph/workspace.rb | 1 + sig/shims/parser/3.2.0.1/builders.rb | 2 + sig/shims/parser/3.2.0.1/builders/default.rbs | 195 ++++++++++++++++++ sig/shims/thor/1.2.0.1/.rbs_meta.yaml | 9 + sig/shims/thor/1.2.0.1/manifest.yaml | 7 + sig/shims/thor/1.2.0.1/thor.rbs | 17 ++ solargraph.gemspec | 5 +- spec/source_map/mapper_spec.rb | 6 +- 45 files changed, 350 insertions(+), 20 deletions(-) create mode 100644 sig/shims/parser/3.2.0.1/builders.rb create mode 100644 sig/shims/parser/3.2.0.1/builders/default.rbs create mode 100644 sig/shims/thor/1.2.0.1/.rbs_meta.yaml create mode 100644 sig/shims/thor/1.2.0.1/manifest.yaml create mode 100644 sig/shims/thor/1.2.0.1/thor.rbs diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 810600534..a51a1fc58 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -36,6 +36,7 @@ def path_pin_hash def pins_by_class klass # @type [Set] s = Set.new + # @sg-ignore need to support destructured args in blocks @pin_select_cache[klass] ||= pin_class_hash.each_with_object(s) { |(key, o), n| n.merge(o) if key <= klass } end @@ -85,6 +86,7 @@ def deep_clone # @param new_pins [Array] def catalog new_pins + # @type [Hash{Class> => Set>}] @pin_select_cache = {} pins.concat new_pins set = new_pins.to_set diff --git a/lib/solargraph/bench.rb b/lib/solargraph/bench.rb index 3cb19a64b..e6180c933 100644 --- a/lib/solargraph/bench.rb +++ b/lib/solargraph/bench.rb @@ -37,6 +37,7 @@ def source_map_hash .to_h end + # @return [Set] def icebox @icebox ||= (source_maps - [live_map]) end diff --git a/lib/solargraph/complex_type/type_methods.rb b/lib/solargraph/complex_type/type_methods.rb index e6d596244..e68f5100e 100644 --- a/lib/solargraph/complex_type/type_methods.rb +++ b/lib/solargraph/complex_type/type_methods.rb @@ -130,6 +130,7 @@ def namespace end.call end + # @return [ComplexType, UniqueType] def namespace_type return ComplexType.parse('::Object') if duck_type? return ComplexType.parse('::NilClass') if nil_type? diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index bc3adeada..4748f4aa2 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -88,7 +88,7 @@ def process private - # @return [StructDefintionNode, nil] + # @return [StructDefintionNode, StructAssignmentNode, nil] def struct_definition_node @struct_definition_node ||= if StructDefintionNode.match?(node) StructDefintionNode.new(node) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index d51fc3022..68f911bca 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -151,6 +151,7 @@ def load_serialized_gem_pins @uncached_rbs_collection_gemspecs = [] with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v } paths = Hash[without_gemspecs].keys + # @type [Array] gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a paths.each do |path| diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index b92cbd6af..83954748f 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -30,8 +30,12 @@ def self.combine_method_pins_by_path(pins) by_path.values + alias_pins end + # @param pins [Array] + # @return [Pin::Method, nil] def self.combine_method_pins(*pins) - out = pins.reduce(nil) do |memo, pin| + # @type [Pin::Method, nil] + combined_pin = nil + out = pins.reduce(combined_pin) do |memo, pin| next pin if memo.nil? if memo == pin && memo.source != :combined # @todo we should track down situations where we are handled diff --git a/lib/solargraph/language_server/host/dispatch.rb b/lib/solargraph/language_server/host/dispatch.rb index 1480e20a2..1ff1227b8 100644 --- a/lib/solargraph/language_server/host/dispatch.rb +++ b/lib/solargraph/language_server/host/dispatch.rb @@ -95,6 +95,7 @@ def implicit_library_for uri nil end + # @return [Hash{String => undefined}] def options @options ||= {}.freeze end @@ -118,6 +119,7 @@ def generic_library end # @param library [Solargraph::Library] + # @param progress [Solargraph::LanguageServer::Progress, nil] # @return [void] def update progress progress&.send(self) diff --git a/lib/solargraph/language_server/host/message_worker.rb b/lib/solargraph/language_server/host/message_worker.rb index 482a40e56..ec426b99f 100644 --- a/lib/solargraph/language_server/host/message_worker.rb +++ b/lib/solargraph/language_server/host/message_worker.rb @@ -72,10 +72,12 @@ def tick private + # @return [Hash, nil] def next_message cancel_message || next_priority end + # @return [Hash, nil] def cancel_message # Handle cancellations first idx = messages.find_index { |msg| msg['method'] == '$/cancelRequest' } @@ -86,6 +88,7 @@ def cancel_message msg end + # @return [Hash, nil] def next_priority # Prioritize updates and version-dependent messages for performance idx = messages.find_index do |msg| diff --git a/lib/solargraph/language_server/message/base.rb b/lib/solargraph/language_server/message/base.rb index fbc55ccbd..2a871419f 100644 --- a/lib/solargraph/language_server/message/base.rb +++ b/lib/solargraph/language_server/message/base.rb @@ -79,6 +79,7 @@ def send_response private + # @return [void] def accept_or_cancel if host.cancel?(id) # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#cancelRequest diff --git a/lib/solargraph/language_server/message/text_document/definition.rb b/lib/solargraph/language_server/message/text_document/definition.rb index 47bf7a60d..5f143cc82 100644 --- a/lib/solargraph/language_server/message/text_document/definition.rb +++ b/lib/solargraph/language_server/message/text_document/definition.rb @@ -10,6 +10,7 @@ def process private + # @return [Array] def code_location suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column) return nil if suggestions.empty? @@ -21,6 +22,7 @@ def code_location end end + # @return [Array] def require_location # @todo Terrible hack lib = host.library_for(params['textDocument']['uri']) diff --git a/lib/solargraph/language_server/message/text_document/type_definition.rb b/lib/solargraph/language_server/message/text_document/type_definition.rb index 8143d7710..8c95c231e 100644 --- a/lib/solargraph/language_server/message/text_document/type_definition.rb +++ b/lib/solargraph/language_server/message/text_document/type_definition.rb @@ -10,6 +10,7 @@ def process private + # @return [Array] def code_location suggestions = host.type_definitions_at(params['textDocument']['uri'], @line, @column) return nil if suggestions.empty? diff --git a/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb b/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb index 2e7b4130c..e1e83fc1e 100644 --- a/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +++ b/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb @@ -9,11 +9,13 @@ def process private + # @return [void] def add_folders return unless params['event'] && params['event']['added'] host.prepare_folders params['event']['added'] end + # @return [void] def remove_folders return unless params['event'] && params['event']['removed'] params['event']['removed'].each do |folder| diff --git a/lib/solargraph/language_server/progress.rb b/lib/solargraph/language_server/progress.rb index 0b2aac5fe..10900a37e 100644 --- a/lib/solargraph/language_server/progress.rb +++ b/lib/solargraph/language_server/progress.rb @@ -39,6 +39,7 @@ def initialize title # @param message [String] # @param percentage [Integer] + # @return [void] def begin message, percentage @kind = 'begin' @message = message @@ -47,6 +48,7 @@ def begin message, percentage # @param message [String] # @param percentage [Integer] + # @return [void] def report message, percentage @kind = 'report' @message = message @@ -54,6 +56,7 @@ def report message, percentage end # @param message [String] + # @return [void] def finish message @kind = 'end' @message = message @@ -62,6 +65,7 @@ def finish message end # @param host [Solargraph::LanguageServer::Host] + # @return [void] def send host return unless host.client_supports_progress? && !finished? @@ -91,6 +95,7 @@ def create host @status = CREATED end + # @return [Hash] def build { token: uuid, @@ -101,6 +106,7 @@ def build } end + # @return [Hash] def build_value case kind when 'begin' @@ -115,6 +121,7 @@ def build_value end # @param host [Host] + # @return [void] def keep_alive host mutex.synchronize { @last = Time.now } @keep_alive ||= Thread.new do @@ -127,6 +134,7 @@ def keep_alive host end end + # @return [Mutex] def mutex @mutex ||= Mutex.new end diff --git a/lib/solargraph/language_server/request.rb b/lib/solargraph/language_server/request.rb index e9aea65eb..dcad7084d 100644 --- a/lib/solargraph/language_server/request.rb +++ b/lib/solargraph/language_server/request.rb @@ -16,6 +16,7 @@ def process result @block.call(result) unless @block.nil? end + # @return [void] def send_response # noop end diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index 74d1318df..713b4fef1 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -25,6 +25,7 @@ def initialize filename, range [filename, range] end + # @param other [self] def <=>(other) return nil unless other.is_a?(Location) if filename == other.filename @@ -60,6 +61,7 @@ def to_hash end # @param node [Parser::AST::Node, nil] + # @return [Location, nil] def self.from_node(node) return nil if node.nil? || node.loc.nil? range = Range.from_node(node) diff --git a/lib/solargraph/parser/comment_ripper.rb b/lib/solargraph/parser/comment_ripper.rb index 62a4dacc5..ae5b3cc60 100644 --- a/lib/solargraph/parser/comment_ripper.rb +++ b/lib/solargraph/parser/comment_ripper.rb @@ -3,6 +3,13 @@ module Solargraph module Parser class CommentRipper < Ripper::SexpBuilderPP + # @!override Ripper::SexpBuilder#on_embdoc_beg + # @return [Array(Symbol, String, Array)] + # @!override Ripper::SexpBuilder#on_embdoc + # @return [Array(Symbol, String, Array)] + # @!override Ripper::SexpBuilder#on_embdoc_end + # @return [Array(Symbol, String, Array)] + # @param src [String] # @param filename [String] # @param lineno [Integer] diff --git a/lib/solargraph/parser/node_processor.rb b/lib/solargraph/parser/node_processor.rb index a55b7120b..0067af647 100644 --- a/lib/solargraph/parser/node_processor.rb +++ b/lib/solargraph/parser/node_processor.rb @@ -9,7 +9,7 @@ module NodeProcessor autoload :Base, 'solargraph/parser/node_processor/base' class << self - # @type [Hash>>] + # @type [Hash{Symbol => Array>}] @@processors ||= {} # Register a processor for a node type. You can register multiple processors for the same type. @@ -17,7 +17,7 @@ class << self # # @param type [Symbol] # @param cls [Class] - # @return [Class] + # @return [Array>] def register type, cls @@processors[type] ||= [] @@processors[type] << cls diff --git a/lib/solargraph/parser/region.rb b/lib/solargraph/parser/region.rb index 8280c99b6..a6559bc8a 100644 --- a/lib/solargraph/parser/region.rb +++ b/lib/solargraph/parser/region.rb @@ -23,8 +23,10 @@ class Region # @param source [Source] # @param namespace [String] + # @param closure [Pin::Closure, nil] # @param scope [Symbol, nil] # @param visibility [Symbol] + # @param lvars [Array] def initialize source: Solargraph::Source.load_string(''), closure: nil, scope: nil, visibility: :public, lvars: [] @source = source @@ -45,6 +47,7 @@ def filename # @param closure [Pin::Closure, nil] # @param scope [Symbol, nil] # @param visibility [Symbol, nil] + # @param lvars [Array, nil] # @return [Region] def update closure: nil, scope: nil, visibility: nil, lvars: nil Region.new( diff --git a/lib/solargraph/parser/snippet.rb b/lib/solargraph/parser/snippet.rb index d28c57c8c..081dec3e0 100644 --- a/lib/solargraph/parser/snippet.rb +++ b/lib/solargraph/parser/snippet.rb @@ -6,6 +6,8 @@ class Snippet # @return [String] attr_reader :text + # @param range [Solargraph::Range] + # @param text [String] def initialize range, text @range = range @text = text diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index cdd6a5ace..1261963df 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -62,7 +62,7 @@ def assert_location_provided end # @param other [self] - # @param attrs [Hash{Symbol => Object}] + # @param attrs [Hash{::Symbol => Object}] # # @return [self] def combine_with(other, attrs={}) @@ -188,7 +188,7 @@ def choose(other, attr) end # @param other [self] - # @param attr [Symbol] + # @param attr [::Symbol] # @sg-ignore # @return [undefined] def choose_node(other, attr) @@ -269,9 +269,13 @@ def assert_same_count(other, attr) # @param other [self] # @param attr [::Symbol] # - # @return [Object, nil] + # @sg-ignore + # @return [undefined] def assert_same(other, attr) - return false if other.nil? + if other.nil? + Solargraph.assert_or_log("combine_with_#{attr}".to_sym, "Sent nil for comparison") + return send(attr) + end val1 = send(attr) val2 = other.send(attr) return val1 if val1 == val2 @@ -300,6 +304,8 @@ def choose_pin_attr_with_same_name(other, attr) # @param other [self] # @param attr [::Symbol] + # + # @sg-ignore # @return [undefined] def choose_pin_attr(other, attr) # @type [Pin::Base, nil] @@ -312,6 +318,7 @@ def choose_pin_attr(other, attr) return val1 end # arbitrary way of choosing a pin + # @sg-ignore Need _1 support [val1, val2].compact.min_by { _1.best_location.to_s } end diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 20d2301eb..504dd4862 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -21,10 +21,13 @@ def initialize block: nil, return_type: nil, parameters: [], **splat @parameters = parameters end + # @return [String] def method_namespace closure.namespace end + # @param other [self] + # @return [Pin::Block, nil] def combine_blocks(other) if block.nil? other.block @@ -57,6 +60,8 @@ def generics [] end + # @param other [self] + # @return [Array] def choose_parameters(other) raise "Trying to combine two pins with different arities - \nself =#{inspect}, \nother=#{other.inspect}, \n\n self.arity=#{self.arity}, \nother.arity=#{other.arity}" if other.arity != arity parameters.zip(other.parameters).map do |param, other_param| @@ -70,6 +75,7 @@ def choose_parameters(other) end end + # @return [Array] def blockless_parameters if parameters.last&.block? parameters[0..-2] @@ -78,6 +84,7 @@ def blockless_parameters end end + # @return [Array] def arity [generics, blockless_parameters.map(&:arity_decl), block&.arity] end @@ -125,6 +132,7 @@ def typify api_map end end + # @return [String] def method_name raise "closure was nil in #{self.inspect}" if closure.nil? @method_name ||= closure.name @@ -197,6 +205,7 @@ def arity_matches? arguments, with_block true end + # @return [Integer] def mandatory_positional_param_count parameters.count(&:arg?) end diff --git a/lib/solargraph/pin/constant.rb b/lib/solargraph/pin/constant.rb index 345bbd50a..a1e0c47f3 100644 --- a/lib/solargraph/pin/constant.rb +++ b/lib/solargraph/pin/constant.rb @@ -5,6 +5,7 @@ module Pin class Constant < BaseVariable attr_reader :visibility + # @param visibility [::Symbol] The visibility of the constant (:public, :protected, or :private) def initialize visibility: :public, **splat super(**splat) @visibility = visibility diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index c680bebd0..36b75773c 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -26,7 +26,7 @@ def combine_with(other, attrs={}) assignment: assert_same(other, :assignment), presence_certain: assert_same(other, :presence_certain?), }.merge(attrs) - new_attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence) + new_attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence) super(other, new_attrs) end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 2f807f444..9098f9d67 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -50,7 +50,7 @@ def combine_all_signature_pins(*signature_pins) end # @param other [Pin::Method] - # @return [Symbol] + # @return [::Symbol] def combine_visibility(other) if dodgy_visibility_source? && !other.dodgy_visibility_source? other.visibility diff --git a/lib/solargraph/pin/method_alias.rb b/lib/solargraph/pin/method_alias.rb index 8636169a8..210794976 100644 --- a/lib/solargraph/pin/method_alias.rb +++ b/lib/solargraph/pin/method_alias.rb @@ -13,6 +13,8 @@ class MethodAlias < Method # @return [String] attr_reader :original + # @param scope [::Symbol] + # @param original [String, nil] The name of the original method def initialize scope: :instance, original: nil, **splat super(**splat) @scope = scope diff --git a/lib/solargraph/pin/proxy_type.rb b/lib/solargraph/pin/proxy_type.rb index 819c97481..2323489a7 100644 --- a/lib/solargraph/pin/proxy_type.rb +++ b/lib/solargraph/pin/proxy_type.rb @@ -4,6 +4,7 @@ module Solargraph module Pin class ProxyType < Base # @param return_type [ComplexType] + # @param binder [ComplexType, ComplexType::UniqueType, nil] def initialize return_type: ComplexType::UNDEFINED, binder: nil, **splat super(**splat) @return_type = return_type diff --git a/lib/solargraph/pin/reference/override.rb b/lib/solargraph/pin/reference/override.rb index d547e3caf..878c309db 100644 --- a/lib/solargraph/pin/reference/override.rb +++ b/lib/solargraph/pin/reference/override.rb @@ -14,16 +14,30 @@ def closure nil end + # @param location [Location, nil] + # @param name [String] + # @param tags [::Array] + # @param delete [::Array] + # @param splat [Hash] def initialize location, name, tags, delete = [], **splat super(location: location, name: name, **splat) @tags = tags @delete = delete end + # @param name [String] + # @param tags [::Array] + # @param delete [::Array] + # @param splat [Hash] + # @return [Solargraph::Pin::Reference::Override] def self.method_return name, *tags, delete: [], **splat - new(nil, name, [YARD::Tags::Tag.new('return', nil, tags)], delete, **splat) + new(nil, name, [YARD::Tags::Tag.new('return', '', tags)], delete, **splat) end + # @param name [String] + # @param comment [String] + # @param splat [Hash] + # @return [Solargraph::Pin::Reference::Override] def self.from_comment name, comment, **splat new(nil, name, Solargraph::Source.parse_docstring(comment).to_docstring.tags, **splat) end diff --git a/lib/solargraph/pin/search.rb b/lib/solargraph/pin/search.rb index fc0f000cd..33f02e027 100644 --- a/lib/solargraph/pin/search.rb +++ b/lib/solargraph/pin/search.rb @@ -12,6 +12,8 @@ class Result # @return [Pin::Base] attr_reader :pin + # @param match [Float] The match score for the pin + # @param pin [Pin::Base] def initialize match, pin @match = match @pin = pin diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 818d66411..4c25e028b 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -39,6 +39,8 @@ def typify api_map end return ComplexType::UNDEFINED if closure.nil? return ComplexType::UNDEFINED unless closure.is_a?(Pin::Method) + # @sg-ignore need is_a? support + # @type [Array] method_stack = closure.rest_of_stack api_map logger.debug { "Signature#typify(self=#{self}) - method_stack: #{method_stack}" } method_stack.each do |pin| diff --git a/lib/solargraph/pin/symbol.rb b/lib/solargraph/pin/symbol.rb index 9e11c3d7d..82c1b1c6e 100644 --- a/lib/solargraph/pin/symbol.rb +++ b/lib/solargraph/pin/symbol.rb @@ -36,6 +36,7 @@ def directives [] end + # @return [::Symbol] def visibility :public end diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 1bd31e0f5..1197038ef 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -26,6 +26,7 @@ def initialize line, character [line, character] end + # @param other [Position] def <=>(other) return nil unless other.is_a?(Position) if line == other.line diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 6e50c022a..a6291c4a0 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -699,13 +699,13 @@ def method_type_to_tag type # @return [ComplexType::UniqueType] def build_type(type_name, type_args = []) base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s - params = type_args.map { |a| other_type_to_tag(a) }.reject { |t| t == 'undefined' }.map do |t| + params = type_args.map { |a| other_type_to_tag(a) }.map do |t| ComplexType.try_parse(t).force_rooted end if base == 'Hash' && params.length == 2 ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) else - ComplexType::UniqueType.new(base, [], params, rooted: true, parameters_type: :list) + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) end end diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 11ab215ed..4190b073a 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -386,6 +386,7 @@ def changes # @return [Integer] attr_writer :version + # @return [void] def finalize return if @finalized && changes.empty? @@ -440,6 +441,7 @@ def code=(val) # @return [String] attr_writer :repaired + # @return [String] def repaired finalize @repaired diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index 8fdeed228..4567005b2 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -265,7 +265,10 @@ def infer_from_definitions pins, context, api_map, locals else ComplexType.new(types) end - return type if context.nil? || context.return_type.undefined? + if context.nil? || context.return_type.undefined? + # up to downstream to resolve self type + return type + end type.self_to_type(context.return_type) end diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 84b3a4bcc..61b08eea8 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -32,11 +32,13 @@ def initialize source environ.merge Convention.for_local(self) unless filename.nil? self.convention_pins = environ.pins + # @type [Hash{Class> => Array>}] @pin_select_cache = {} end - # @param klass [Class] - # @return [Array] + # @generic T + # @param klass [Class>] + # @return [Array>] def pins_by_class klass @pin_select_cache[klass] ||= pin_class_hash.select { |key, _| key <= klass }.values.flatten end diff --git a/lib/solargraph/source_map/data.rb b/lib/solargraph/source_map/data.rb index 9d1b30bac..453520414 100644 --- a/lib/solargraph/source_map/data.rb +++ b/lib/solargraph/source_map/data.rb @@ -3,15 +3,18 @@ module Solargraph class SourceMap class Data + # @param source [Solargraph::Source] def initialize source @source = source end + # @return [Array] def pins generate @pins || [] end + # @return [Array] def locals generate @locals || [] @@ -19,6 +22,7 @@ def locals private + # @return [void] def generate return if @generated diff --git a/lib/solargraph/type_checker/param_def.rb b/lib/solargraph/type_checker/param_def.rb index 2c626270a..a60448c98 100644 --- a/lib/solargraph/type_checker/param_def.rb +++ b/lib/solargraph/type_checker/param_def.rb @@ -12,6 +12,8 @@ class ParamDef # @return [Symbol] attr_reader :type + # @param name [String] + # @param type [Symbol] def initialize name, type @name = name @type = type diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index aabadd333..ffd653d96 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -133,6 +133,7 @@ def rbs_collection_path @gem_rbs_collection ||= read_rbs_collection_path end + # @return [String, nil] def rbs_collection_config_path @rbs_collection_config_path ||= begin unless directory.empty? || directory == '*' diff --git a/sig/shims/parser/3.2.0.1/builders.rb b/sig/shims/parser/3.2.0.1/builders.rb new file mode 100644 index 000000000..b932108a9 --- /dev/null +++ b/sig/shims/parser/3.2.0.1/builders.rb @@ -0,0 +1,2 @@ +module Builders +end diff --git a/sig/shims/parser/3.2.0.1/builders/default.rbs b/sig/shims/parser/3.2.0.1/builders/default.rbs new file mode 100644 index 000000000..861a9e371 --- /dev/null +++ b/sig/shims/parser/3.2.0.1/builders/default.rbs @@ -0,0 +1,195 @@ +# frozen_string_literal: true + +module Parser + ## + # Default AST builder. Uses {AST::Node}s. + # + module Builders + class Default + ## + # AST compatibility attribute; since `-> {}` is not semantically + # equivalent to `lambda {}`, all new code should set this attribute + # to true. + # + # If set to false (the default), `-> {}` is emitted as + # `s(:block, s(:send, nil, :lambda), s(:args), nil)`. + # + # If set to true, `-> {}` is emitted as + # `s(:block, s(:lambda), s(:args), nil)`. + # + # @return [Boolean] + attr_accessor self.emit_lambda: bool + + ## + # AST compatibility attribute; block arguments of `m { |a| }` are + # not semantically equivalent to block arguments of `m { |a,| }` or `m { |a, b| }`, + # all new code should set this attribute to true. + # + # If set to false (the default), arguments of `m { |a| }` are emitted as + # `s(:args, s(:arg, :a))`. + # + # If set to true, arguments of `m { |a| }` are emitted as + # `s(:args, s(:procarg0, :a)). + # + # @return [Boolean] + attr_accessor self.emit_procarg0: bool + + ## + # AST compatibility attribute; locations of `__ENCODING__` are not the same + # as locations of `Encoding::UTF_8` causing problems during rewriting, + # all new code should set this attribute to true. + # + # If set to false (the default), `__ENCODING__` is emitted as + # ` s(:const, s(:const, nil, :Encoding), :UTF_8)`. + # + # If set to true, `__ENCODING__` is emitted as + # `s(:__ENCODING__)`. + # + # @return [Boolean] + attr_accessor self.emit_encoding: bool + + ## + # AST compatibility attribute; indexed assignment, `x[] = 1`, is not + # semantically equivalent to calling the method directly, `x.[]=(1)`. + # Specifically, in the former case, the expression's value is always 1, + # and in the latter case, the expression's value is the return value + # of the `[]=` method. + # + # If set to false (the default), `self[1]` is emitted as + # `s(:send, s(:self), :[], s(:int, 1))`, and `self[1] = 2` is + # emitted as `s(:send, s(:self), :[]=, s(:int, 1), s(:int, 2))`. + # + # If set to true, `self[1]` is emitted as + # `s(:index, s(:self), s(:int, 1))`, and `self[1] = 2` is + # emitted as `s(:indexasgn, s(:self), s(:int, 1), s(:int, 2))`. + # + # @return [Boolean] + attr_accessor self.emit_index: bool + + ## + # AST compatibility attribute; causes a single non-mlhs + # block argument to be wrapped in s(:procarg0). + # + # If set to false (the default), block arguments `|a|` are emitted as + # `s(:args, s(:procarg0, :a))` + # + # If set to true, block arguments `|a|` are emitted as + # `s(:args, s(:procarg0, s(:arg, :a))` + # + # @return [Boolean] + attr_accessor self.emit_arg_inside_procarg0: bool + + ## + # AST compatibility attribute; arguments forwarding initially + # didn't have support for leading arguments + # (i.e. `def m(a, ...); end` was a syntax error). However, Ruby 3.0 + # added support for any number of arguments in front of the `...`. + # + # If set to false (the default): + # 1. `def m(...) end` is emitted as + # s(:def, :m, s(:forward_args), nil) + # 2. `def m(a, b, ...) end` is emitted as + # s(:def, :m, + # s(:args, s(:arg, :a), s(:arg, :b), s(:forward_arg))) + # + # If set to true it uses a single format: + # 1. `def m(...) end` is emitted as + # s(:def, :m, s(:args, s(:forward_arg))) + # 2. `def m(a, b, ...) end` is emitted as + # s(:def, :m, s(:args, s(:arg, :a), s(:arg, :b), s(:forward_arg))) + # + # It does't matter that much on 2.7 (because there can't be any leading arguments), + # but on 3.0 it should be better enabled to use a single AST format. + # + # @return [Boolean] + attr_accessor self.emit_forward_arg: bool + + ## + # AST compatibility attribute; Starting from Ruby 2.7 keyword arguments + # of method calls that are passed explicitly as a hash (i.e. with curly braces) + # are treated as positional arguments and Ruby 2.7 emits a warning on such method + # call. Ruby 3.0 given an ArgumentError. + # + # If set to false (the default) the last hash argument is emitted as `hash`: + # + # ``` + # (send nil :foo + # (hash + # (pair + # (sym :bar) + # (int 42)))) + # ``` + # + # If set to true it is emitted as `kwargs`: + # + # ``` + # (send nil :foo + # (kwargs + # (pair + # (sym :bar) + # (int 42)))) + # ``` + # + # Note that `kwargs` node is just a replacement for `hash` argument, + # so if there's are multiple arguments (or a `kwsplat`) all of them + # are wrapped into `kwargs` instead of `hash`: + # + # ``` + # (send nil :foo + # (kwargs + # (pair + # (sym :a) + # (int 42)) + # (kwsplat + # (send nil :b)) + # (pair + # (sym :c) + # (int 10)))) + # ``` + attr_accessor self.emit_kwargs: bool + + ## + # AST compatibility attribute; Starting from 3.0 Ruby returns + # true/false from single-line pattern matching with `in` keyword. + # + # Before 3.0 there was an exception if given value doesn't match pattern. + # + # NOTE: This attribute affects only Ruby 2.7 grammar. + # 3.0 grammar always emits `match_pattern`/`match_pattern_p` + # + # If compatibility attribute set to false `foo in bar` is emitted as `in_match`: + # + # ``` + # (in-match + # (send nil :foo) + # (match-var :bar)) + # ``` + # + # If set to true it's emitted as `match_pattern_p`: + # ``` + # (match-pattern-p + # (send nil :foo) + # (match-var :bar)) + # ``` + attr_accessor self.emit_match_pattern: bool + + ## + # If set to true (the default), `__FILE__` and `__LINE__` are transformed to + # literal nodes. For example, `s(:str, "lib/foo.rb")` and `s(:int, 10)`. + # + # If set to false, `__FILE__` and `__LINE__` are emitted as-is, + # i.e. as `s(:__FILE__)` and `s(:__LINE__)` nodes. + # + # Source maps are identical in both cases. + # + # @return [Boolean] + attr_accessor emit_file_line_as_literals: bool + + def value: (untyped token) -> untyped + + def string_value: (untyped token) -> String + + def loc: (untyped token) -> untyped + end + end +end diff --git a/sig/shims/thor/1.2.0.1/.rbs_meta.yaml b/sig/shims/thor/1.2.0.1/.rbs_meta.yaml new file mode 100644 index 000000000..27c2fb2e4 --- /dev/null +++ b/sig/shims/thor/1.2.0.1/.rbs_meta.yaml @@ -0,0 +1,9 @@ +--- +name: thor +version: '1.2' +source: + type: git + name: ruby/gem_rbs_collection + revision: 98541aabafdf403b16ebae6fe4060d18bee75e93 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems diff --git a/sig/shims/thor/1.2.0.1/manifest.yaml b/sig/shims/thor/1.2.0.1/manifest.yaml new file mode 100644 index 000000000..a0cb4d149 --- /dev/null +++ b/sig/shims/thor/1.2.0.1/manifest.yaml @@ -0,0 +1,7 @@ +# manifest.yaml describes dependencies which do not appear in the gemspec. +# If this gem includes such dependencies, comment-out the following lines and +# declare the dependencies. +# If all dependencies appear in the gemspec, you should remove this file. +# +# dependencies: +# - name: pathname diff --git a/sig/shims/thor/1.2.0.1/thor.rbs b/sig/shims/thor/1.2.0.1/thor.rbs new file mode 100644 index 000000000..2247507e7 --- /dev/null +++ b/sig/shims/thor/1.2.0.1/thor.rbs @@ -0,0 +1,17 @@ +class Thor + class Group + end + + module Actions + class CreateFile + end + + def create_file: (String destination, String data, ?verbose: bool) -> String + | (String destination, ?verbose: bool) { () -> String } -> String + end + + class Error + end + + def self.start: (Array[String] given_args, ?Hash[Symbol, untyped] config) -> void +end diff --git a/solargraph.gemspec b/solargraph.gemspec index 666f19cba..535c73010 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -11,7 +11,10 @@ Gem::Specification.new do |s| s.authors = ["Fred Snyder"] s.email = 'admin@castwide.com' s.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + # @sg-ignore Need backtick support + # @type [String] + all_files = `git ls-files -z` + all_files.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end s.homepage = 'https://solargraph.org' s.license = 'MIT' diff --git a/spec/source_map/mapper_spec.rb b/spec/source_map/mapper_spec.rb index af3678cfd..96d2bdab5 100644 --- a/spec/source_map/mapper_spec.rb +++ b/spec/source_map/mapper_spec.rb @@ -1525,9 +1525,9 @@ def bar; end def quz; end end )) - expect(map.first_pin('Foo#bar').visibility).to be(:public) - expect(map.first_pin('Foo#baz').visibility).to be(:private) - expect(map.first_pin('Foo#quz').visibility).to be(:public) + expect(map.first_pin('Foo#bar').visibility).to eq(:public) + expect(map.first_pin('Foo#baz').visibility).to eq(:private) + expect(map.first_pin('Foo#quz').visibility).to eq(:public) end it 'encloses class_eval calls in receivers' do From 1d0b23d13f20646992a4b17a44c492826f433cab Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 17:53:02 -0400 Subject: [PATCH 031/460] Generic typechecking improvements --- lib/solargraph/api_map/index.rb | 3 +-- lib/solargraph/complex_type/type_methods.rb | 2 ++ lib/solargraph/doc_map.rb | 1 + lib/solargraph/location.rb | 1 - lib/solargraph/position.rb | 1 - lib/solargraph/range.rb | 1 - lib/solargraph/type_checker.rb | 11 +++++++++++ lib/solargraph/type_checker/rules.rb | 2 +- 8 files changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 9015cd6d5..fbfb5c218 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -72,7 +72,7 @@ def merge pins # @return [Solargraph::ApiMap::Index] def deep_clone - out = Index.allocate.tap do |copy| + Index.allocate.tap do |copy| copy.pin_select_cache = {} copy.pins = pins.clone %i[ @@ -83,7 +83,6 @@ def deep_clone copy.send(sym)&.transform_values!(&:clone) end end - out end # @param new_pins [Enumerable] diff --git a/lib/solargraph/complex_type/type_methods.rb b/lib/solargraph/complex_type/type_methods.rb index 4fcaadb7f..791ab80b0 100644 --- a/lib/solargraph/complex_type/type_methods.rb +++ b/lib/solargraph/complex_type/type_methods.rb @@ -70,6 +70,8 @@ def undefined? end # Variance of the type ignoring any type parameters + # @return [Symbol] + # @param situation [Symbol] The situation in which the variance is being considered. def erased_variance situation = :method_call if [:method_call, :return_type, :assignment].include?(situation) :covariant diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 186037460..9136da26b 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -153,6 +153,7 @@ def load_serialized_gem_pins # @sg-ignore Need Hash[] support # @type [Array] paths = Hash[without_gemspecs].keys + # @type [Array] gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a paths.each do |path| diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index 74d1318df..3af8016b3 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -20,7 +20,6 @@ def initialize filename, range @range = range end - # @sg-ignore Fix "Not enough arguments to Module#protected" protected def equality_fields [filename, range] end diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 1bd31e0f5..27289d28f 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -21,7 +21,6 @@ def initialize line, character @character = character end - # @sg-ignore Fix "Not enough arguments to Module#protected" protected def equality_fields [line, character] end diff --git a/lib/solargraph/range.rb b/lib/solargraph/range.rb index c508e48fa..2bea62797 100644 --- a/lib/solargraph/range.rb +++ b/lib/solargraph/range.rb @@ -19,7 +19,6 @@ def initialize start, ending @ending = ending end - # @sg-ignore Fix "Not enough arguments to Module#protected" protected def equality_fields [start, ending] end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 953832a36..8b86f02df 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -41,18 +41,27 @@ def source @source_map.source end + # @param inferred [ComplexType] + # @param expected [ComplexType] def return_type_conforms_to?(inferred, expected) conforms_to?(inferred, expected, :return_type) end + # @param inferred [ComplexType] + # @param expected [ComplexType] def arg_conforms_to?(inferred, expected) conforms_to?(inferred, expected, :method_call) end + # @param inferred [ComplexType] + # @param expected [ComplexType] def assignment_conforms_to?(inferred, expected) conforms_to?(inferred, expected, :assignment) end + # @param inferred [ComplexType] + # @param expected [ComplexType] + # @param scenario [Symbol] def conforms_to?(inferred, expected, scenario) rules_arr = [] rules_arr << :allow_empty_params unless rules.require_inferred_type_params? @@ -486,7 +495,9 @@ def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kw kwargs.each_pair do |pname, argchain| next unless params.key?(pname.to_s) ptype = params[pname.to_s][:qualified] + ptype = ptype.self_to_type(pin.context) argtype = argchain.infer(api_map, block_pin, locals) + argtype = argtype.self_to_type(block_pin.context) if argtype.defined? && ptype && !arg_conforms_to?(argtype, ptype) result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}") end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 33ec0c4d0..5290c8c12 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -78,7 +78,7 @@ def require_downcasts? rank >= LEVELS[:alpha] end - # We keep this at strong because if you added an @sg-ignore to + # We keep this at strong because if you added an @ sg-ignore to # address a strong-level issue, then ran at a lower level, you'd # get a false positive - we don't run stronger level checks than # requested for performance reasons From 0741a1837eb72a54a3febe5b24da55579367f474 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 21:09:52 -0400 Subject: [PATCH 032/460] Type annotation improvements --- lib/solargraph/parser/parser_gem/flawed_builder.rb | 1 + lib/solargraph/parser/parser_gem/node_methods.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/solargraph/parser/parser_gem/flawed_builder.rb b/lib/solargraph/parser/parser_gem/flawed_builder.rb index b5750413d..acf665e16 100644 --- a/lib/solargraph/parser/parser_gem/flawed_builder.rb +++ b/lib/solargraph/parser/parser_gem/flawed_builder.rb @@ -9,6 +9,7 @@ module ParserGem class FlawedBuilder < ::Parser::Builders::Default # @param token [::Parser::AST::Node] # @return [String] + # @sg-ignore def string_value(token) value(token) end diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index b716b352d..88cf09718 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -232,6 +232,7 @@ def find_recipient_node cursor else source.tree_at(position.line, position.column - 1) end + # @type [AST::Node, nil] prev = nil tree.each do |node| if node.type == :send From dfe2d10d0528562de45abc0c923f47fc2e2fb17f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 21:14:28 -0400 Subject: [PATCH 033/460] Fix children definition --- sig/shims/ast/2.4/ast.rbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sig/shims/ast/2.4/ast.rbs b/sig/shims/ast/2.4/ast.rbs index caba287ad..e7fb8975e 100644 --- a/sig/shims/ast/2.4/ast.rbs +++ b/sig/shims/ast/2.4/ast.rbs @@ -10,7 +10,7 @@ module AST class Node public - attr_reader children: Array[Node, Object] + attr_reader children: Array[Node] attr_reader hash: String attr_reader type: Symbol From 3850399ccf0fd63e5ccaff9a32751a9633bbac59 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 21:16:31 -0400 Subject: [PATCH 034/460] Generic typechecking improvements --- lib/solargraph/complex_type.rb | 7 +++++++ lib/solargraph/complex_type/unique_type.rb | 5 +++++ lib/solargraph/pin/base.rb | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 05e499998..a558f3ba5 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -315,6 +315,13 @@ def all_rooted? all?(&:all_rooted?) end + # @param other [ComplexType, UniqueType] + def erased_version_of?(other) + return false if items.length != 1 || other.items.length != 1 + + @items.first.erased_version_of?(other.items.first) + end + # every top-level type has resolved to be fully qualified; see # #all_rooted? to check their subtypes as well def rooted? diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 361fe06bb..39e5e728f 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -194,6 +194,11 @@ def interface? name.start_with?('_') end + # @param other [UniqueType] + def erased_version_of?(other) + return name == other.name && (all_params.empty? || all_params.all?(&:undefined?)) + end + # @param api_map [ApiMap] # @param expected [ComplexType::UniqueType] # @param situation [:method_call, :return_type] diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index c9e308056..14d324273 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -152,6 +152,10 @@ def combine_return_type(other) other.return_type elsif other.return_type.undefined? return_type + elsif return_type.erased_version_of?(other.return_type) + other.return_type + elsif other.return_type.erased_version_of?(return_type) + return_type elsif dodgy_return_type_source? && !other.dodgy_return_type_source? other.return_type elsif other.dodgy_return_type_source? && !dodgy_return_type_source? From 766eecfbca7de598c7badca68186db58c13858e8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 21:45:50 -0400 Subject: [PATCH 035/460] Type annotation improvements --- lib/solargraph/pin/base.rb | 7 +++++++ lib/solargraph/pin/common.rb | 12 ++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 1261963df..c002c65eb 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -61,6 +61,13 @@ def assert_location_provided Solargraph.assert_or_log(:best_location, "Neither location nor type_location provided - #{path} #{source} #{self.class}") end + # @return [Pin::Closure, nil] + def closure + Solargraph.assert_or_log(:closure, "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") unless @closure + # @type [Pin::Closure, nil] + @closure + end + # @param other [self] # @param attrs [Hash{::Symbol => Object}] # diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 6d9260abd..17b056098 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -3,17 +3,13 @@ module Solargraph module Pin module Common + # @!method closure + # @abstract + # @return [Pin::Closure, nil] + # @return [Location] attr_reader :location - # @return [Pin::Closure, nil] - attr_reader :closure - - def closure - Solargraph.assert_or_log(:closure, "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") unless @closure - @closure - end - # @return [String] def name @name ||= '' From ec8eea5e19472d95824fabc6d706e469a9495405 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 22:01:54 -0400 Subject: [PATCH 036/460] Fix tag --- lib/solargraph/type_checker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index ba2284b21..4b37b1054 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -466,7 +466,7 @@ def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kw result end - # @param pin [Pin::Method] + # @param pin [Pin::Method, Pin::Signature] # @return [void] def add_restkwarg_param_tag_details(param_details, pin) # see if we have additional tags to pay attention to from YARD - From e473495ec91ab7da1878e9b96517c581c7dcdefa Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 22:04:50 -0400 Subject: [PATCH 037/460] Type annotation improvements --- .../parser_gem/node_processors/send_node.rb | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index 8a70a0b3f..8b4c378fe 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -8,32 +8,39 @@ class SendNode < Parser::NodeProcessor::Base include ParserGem::NodeMethods def process + # @sg-ignore + # @type [Symbol] + method_name = node.children[1] + unless method_name.instance_of?(Symbol) + Solargraph.assert_or_log(:parser_method_name, "Expected method name to be a Symbol, got #{method_name.class} for node #{node.inspect}") + return process_children + end if node.children[0].nil? - if [:private, :public, :protected].include?(node.children[1]) + if [:private, :public, :protected].include?(method_name) process_visibility - elsif node.children[1] == :module_function + elsif method_name == :module_function process_module_function - elsif [:attr_reader, :attr_writer, :attr_accessor].include?(node.children[1]) + elsif [:attr_reader, :attr_writer, :attr_accessor].include?(method_name) process_attribute - elsif node.children[1] == :include + elsif method_name == :include process_include - elsif node.children[1] == :extend + elsif method_name == :extend process_extend - elsif node.children[1] == :prepend + elsif method_name == :prepend process_prepend - elsif node.children[1] == :require + elsif method_name == :require process_require - elsif node.children[1] == :autoload + elsif method_name == :autoload process_autoload - elsif node.children[1] == :private_constant + elsif method_name == :private_constant process_private_constant - elsif node.children[1] == :alias_method && node.children[2] && node.children[2] && node.children[2].type == :sym && node.children[3] && node.children[3].type == :sym + elsif method_name == :alias_method && node.children[2] && node.children[2] && node.children[2].type == :sym && node.children[3] && node.children[3].type == :sym process_alias_method - elsif node.children[1] == :private_class_method && node.children[2].is_a?(AST::Node) + elsif method_name == :private_class_method && node.children[2].is_a?(AST::Node) # Processing a private class can potentially handle children on its own return if process_private_class_method end - elsif node.children[1] == :require && node.children[0].to_s == '(const nil :Bundler)' + elsif method_name == :require && node.children[0].to_s == '(const nil :Bundler)' pins.push Pin::Reference::Require.new(Solargraph::Location.new(region.filename, Solargraph::Range.from_to(0, 0, 0, 0)), 'bundler/require', source: :parser) end process_children @@ -48,12 +55,19 @@ def process_visibility if child.is_a?(AST::Node) && (child.type == :sym || child.type == :str) name = child.children[0].to_s matches = pins.select{ |pin| pin.is_a?(Pin::Method) && pin.name == name && pin.namespace == region.closure.full_context.namespace && pin.context.scope == (region.scope || :instance)} + # @sg-ignore + # @type [Symbol] + visibility = node.children[1] + unless visibility.instance_of?(Symbol) + Solargraph.assert_or_log(:parser_visibility, "Expected visibility name to be a Symbol, got #{visibility.class} for node #{node.inspect}") + return process_children + end matches.each do |pin| # @todo Smelly instance variable access - pin.instance_variable_set(:@visibility, node.children[1]) + pin.instance_variable_set(:@visibility, visibility) end else - process_children region.update(visibility: node.children[1]) + process_children region.update(visibility: visibility) end end else From 6ad4805f969c5798c7c0554871de18a9e3d5f235 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 23 Jul 2025 07:05:01 -0400 Subject: [PATCH 038/460] Bugfix --- .../parser_gem/node_processors/send_node.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index 8b4c378fe..199d85784 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -52,22 +52,22 @@ def process def process_visibility if (node.children.length > 2) node.children[2..-1].each do |child| + # @sg-ignore + # @type [Symbol] + visibility = node.children[1] + unless visibility.instance_of?(Symbol) + Solargraph.assert_or_log(:parser_visibility, "Expected visibility name to be a Symbol, got #{visibility.class} for node #{node.inspect}") + return process_children + end if child.is_a?(AST::Node) && (child.type == :sym || child.type == :str) name = child.children[0].to_s matches = pins.select{ |pin| pin.is_a?(Pin::Method) && pin.name == name && pin.namespace == region.closure.full_context.namespace && pin.context.scope == (region.scope || :instance)} - # @sg-ignore - # @type [Symbol] - visibility = node.children[1] - unless visibility.instance_of?(Symbol) - Solargraph.assert_or_log(:parser_visibility, "Expected visibility name to be a Symbol, got #{visibility.class} for node #{node.inspect}") - return process_children - end matches.each do |pin| # @todo Smelly instance variable access pin.instance_variable_set(:@visibility, visibility) end else - process_children region.update(visibility: visibility) + process_children region.update(visibility: node.children[1]) end end else From dae5c53861eeac18db90b1edfe71c22183de2546 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 23 Jul 2025 07:08:22 -0400 Subject: [PATCH 039/460] Fix annotation --- lib/solargraph/type_checker.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 4b37b1054..b0165281b 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -502,7 +502,9 @@ def signature_param_details(pin) # signatures and method pins can help by adding type information # # @param param_details [Hash{String => Hash{Symbol => String, ComplexType}}] + # @param param_names [Array] # @param new_param_details [Hash{String => Hash{Symbol => String, ComplexType}}] + # # @return [void] def add_to_param_details(param_details, param_names, new_param_details) new_param_details.each do |param_name, details| From c919bf7c290781a587fb178c1eac029e11c468b7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 23 Jul 2025 07:12:44 -0400 Subject: [PATCH 040/460] Bugfix --- lib/solargraph/parser/parser_gem/node_processors/send_node.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index 199d85784..57c74a22c 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -67,7 +67,7 @@ def process_visibility pin.instance_variable_set(:@visibility, visibility) end else - process_children region.update(visibility: node.children[1]) + process_children region.update(visibility: visibility) end end else From 9d99e151aac200dbbf3c432b5f11e61009dd2870 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 23 Jul 2025 07:13:52 -0400 Subject: [PATCH 041/460] Add annotation --- lib/solargraph/type_checker.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index b0165281b..4c9fd0729 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -466,6 +466,7 @@ def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kw result end + # @param param_details [Hash{String => Hash{Symbol => String, ComplexType}}] # @param pin [Pin::Method, Pin::Signature] # @return [void] def add_restkwarg_param_tag_details(param_details, pin) From b6b66f397732f43f161c3e7ddc245b5318c34fbe Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 30 Jul 2025 07:59:13 -0400 Subject: [PATCH 042/460] Fix flaky spec --- spec/rbs_map/conversions_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 75ec1c311..d9570ae0f 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -55,7 +55,7 @@ def bar: () -> untyped end context 'with standard loads for solargraph project' do - let(:api_map) { Solargraph::ApiMap.load('.') } + let(:api_map) { Solargraph::ApiMap.load_with_cache('.') } let(:superclass_pin) do api_map.pins.find do |pin| From d506f434e71646b678eb2bfd0a90b637d13e566e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 3 Aug 2025 20:10:19 -0400 Subject: [PATCH 043/460] Linting fixes --- lib/solargraph/api_map.rb | 2 +- lib/solargraph/complex_type.rb | 6 - lib/solargraph/complex_type/type_methods.rb | 7 +- lib/solargraph/complex_type/unique_type.rb | 21 ++- lib/solargraph/type_checker.rb | 5 +- lib/solargraph/type_checker/param_def.rb | 35 ---- spec/complex_type/conforms_to_spec.rb | 179 ++++++++++++-------- spec/source_map/clip_spec.rb | 17 ++ spec/type_checker/levels/alpha_spec.rb | 9 +- spec/type_checker/levels/strict_spec.rb | 2 +- spec/type_checker/levels/strong_spec.rb | 68 +++++--- spec/type_checker/levels/typed_spec.rb | 5 +- 12 files changed, 206 insertions(+), 150 deletions(-) delete mode 100644 lib/solargraph/type_checker/param_def.rb diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 6a0edc5a4..8610e2bc1 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -208,7 +208,7 @@ class << self # @param directory [String] # @param out [IO] The output stream for messages # @return [ApiMap] - def self.load_with_cache directory, out + def self.load_with_cache directory, out = $stdout api_map = load(directory) if api_map.uncached_gemspecs.empty? logger.info { "All gems cached for #{directory}" } diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index a558f3ba5..8167339e0 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -103,12 +103,6 @@ def each_unique_type &block end end - # @param atype [ComplexType] type which may be assigned to this type - # @param api_map [ApiMap] The ApiMap that performs qualification - def can_assign?(api_map, atype) - atype.conforms_to?(api_map, self, :assignment) - end - # @return [Integer] def length @items.length diff --git a/lib/solargraph/complex_type/type_methods.rb b/lib/solargraph/complex_type/type_methods.rb index 791ab80b0..5496f1125 100644 --- a/lib/solargraph/complex_type/type_methods.rb +++ b/lib/solargraph/complex_type/type_methods.rb @@ -73,11 +73,12 @@ def undefined? # @return [Symbol] # @param situation [Symbol] The situation in which the variance is being considered. def erased_variance situation = :method_call - if [:method_call, :return_type, :assignment].include?(situation) - :covariant - else + # :nocov: + unless %i[method_call return_type assignment].include?(situation) raise "Unknown situation: #{situation.inspect}" end + # :nocov: + :covariant end # @param generics_to_erase [Enumerable] diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 39e5e728f..a9f2a899b 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -164,11 +164,11 @@ def ==(other) # # "[Expected] types where neither is possible are INVARIANT" # - # @param situation [:method_call] + # @param _situation [:method_call] # @param default [Symbol] The default variance to return if the type is not one of the special cases # # @return [:invariant, :covariant, :contravariant] - def parameter_variance situation, default = :covariant + def parameter_variance _situation, default = :covariant # @todo RBS can specify variance - maybe we can use that info # and also let folks specify? # @@ -196,7 +196,7 @@ def interface? # @param other [UniqueType] def erased_version_of?(other) - return name == other.name && (all_params.empty? || all_params.all?(&:undefined?)) + name == other.name && (all_params.empty? || all_params.all?(&:undefined?)) end # @param api_map [ApiMap] @@ -257,7 +257,9 @@ def conforms_to_unique_type?(api_map, expected, situation = :method_call, api_map.super_and_sub?(inferred.name, expected.name) || inferred.name == expected.name else + # :nocov: raise "Unknown erased variance: #{erased_variance.inspect}" + # :nocov: end return true if inferred.all_params.empty? && rules.include?(:allow_empty_params) @@ -302,11 +304,8 @@ def conforms_to_unique_type?(api_map, expected, situation = :method_call, # @param situation [:method_call, :assignment, :return] # @param rules [Array<:allow_subtype_skew, :allow_empty_params, :allow_reverse_match, :allow_any_match, :allow_undefined, :allow_unresolved_generic>] # @param variance [:invariant, :covariant, :contravariant] - def conforms_to?(api_map, expected, - situation = :method_call, - rules, + def conforms_to?(api_map, expected, situation, rules, variance:) - return true if undefined? && rules.include?(:allow_undefined) # @todo teach this to validate duck types as inferred type @@ -315,7 +314,11 @@ def conforms_to?(api_map, expected, # complex types as expectations are unions - we only need to # match one of their unique types expected.any? do |expected_unique_type| - raise "Expected type must be a UniqueType, got #{expected_unique_type.class} in #{expected.inspect}" unless expected.is_a?(UniqueType) unless expected_unique_type.instance_of?(UniqueType) + # :nocov: + unless expected_unique_type.instance_of?(UniqueType) + raise "Expected type must be a UniqueType, got #{expected_unique_type.class} in #{expected.inspect}" + end + # :nocov: conforms_to_unique_type?(api_map, expected_unique_type, situation, rules, variance: variance) end @@ -360,7 +363,7 @@ def to_rbs elsif name.downcase == 'nil' 'nil' elsif name == GENERIC_TAG_NAME - all_params.first.name + all_params.first&.name elsif ['Class', 'Module'].include?(name) rbs_name elsif ['Tuple', 'Array'].include?(name) && fixed_parameters? diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 8b86f02df..3b555bae7 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -5,7 +5,6 @@ module Solargraph # class TypeChecker autoload :Problem, 'solargraph/type_checker/problem' - autoload :ParamDef, 'solargraph/type_checker/param_def' autoload :Rules, 'solargraph/type_checker/rules' include Parser::NodeMethods @@ -100,10 +99,10 @@ def load filename, level = :normal # @param code [String] # @param filename [String, nil] # @param level [Symbol] + # @param api_map [Solargraph::ApiMap] # @return [self] - def load_string code, filename = nil, level = :normal + def load_string code, filename = nil, level = :normal, api_map: Solargraph::ApiMap.new source = Solargraph::Source.load_string(code, filename) - api_map = Solargraph::ApiMap.new api_map.map(source) new(filename, api_map: api_map, level: level) end diff --git a/lib/solargraph/type_checker/param_def.rb b/lib/solargraph/type_checker/param_def.rb deleted file mode 100644 index 2c626270a..000000000 --- a/lib/solargraph/type_checker/param_def.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module Solargraph - class TypeChecker - # Data about a method parameter definition. This is the information from - # the args list in the def call, not the `@param` tags. - # - class ParamDef - # @return [String] - attr_reader :name - - # @return [Symbol] - attr_reader :type - - def initialize name, type - @name = name - @type = type - end - - class << self - # Get an array of ParamDefs from a method pin. - # - # @param pin [Solargraph::Pin::Method] - # @return [Array] - def from pin - result = [] - pin.parameters.each do |par| - result.push ParamDef.new(par.name, par.decl) - end - result - end - end - end - end -end diff --git a/spec/complex_type/conforms_to_spec.rb b/spec/complex_type/conforms_to_spec.rb index 5755721b4..581ce1d34 100644 --- a/spec/complex_type/conforms_to_spec.rb +++ b/spec/complex_type/conforms_to_spec.rb @@ -1,20 +1,54 @@ +# frozen_string_literal: true + describe Solargraph::ComplexType do it 'validates simple core types' do api_map = Solargraph::ApiMap.new - exp = Solargraph::ComplexType.parse('String') - inf = Solargraph::ComplexType.parse('String') + exp = described_class.parse('String') + inf = described_class.parse('String') match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end it 'invalidates simple core types' do api_map = Solargraph::ApiMap.new - exp = Solargraph::ComplexType.parse('String') - inf = Solargraph::ComplexType.parse('Integer') + exp = described_class.parse('String') + inf = described_class.parse('Integer') + match = inf.conforms_to?(api_map, exp, :method_call) + expect(match).to be(false) + end + + it 'allows subtype skew if told' do + api_map = Solargraph::ApiMap.new + exp = described_class.parse('Array') + inf = described_class.parse('Array') + match = inf.conforms_to?(api_map, exp, :method_call, [:allow_subtype_skew]) + expect(match).to be(true) + end + + it 'accepts valid tuple conformance' do + api_map = Solargraph::ApiMap.new + exp = described_class.parse('Array(Integer, Integer)') + inf = described_class.parse('Array(Integer, Integer)') + match = inf.conforms_to?(api_map, exp, :method_call) + expect(match).to be(true) + end + + it 'rejects invalid tuple conformance' do + api_map = Solargraph::ApiMap.new + exp = described_class.parse('Array(Integer, Integer)') + inf = described_class.parse('Array(Integer, String)') match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(false) end + it 'allows empty params when specified' do + api_map = Solargraph::ApiMap.new + exp = described_class.parse('Array(Integer, Integer)') + inf = described_class.parse('Array') + match = inf.conforms_to?(api_map, exp, :method_call, [:allow_empty_params]) + expect(match).to be(true) + end + it 'validates expected superclasses' do source = Solargraph::Source.load_string(%( class Sup; end @@ -22,32 +56,32 @@ class Sub < Sup; end )) api_map = Solargraph::ApiMap.new api_map.map source - sup = Solargraph::ComplexType.parse('Sup') - sub = Solargraph::ComplexType.parse('Sub') + sup = described_class.parse('Sup') + sub = described_class.parse('Sub') match = sub.conforms_to?(api_map, sup, :method_call) expect(match).to be(true) end - it 'invalidates inferred superclasses (expected must be super)' do - # @todo This test might be invalid. There are use cases where inheritance - # between inferred and expected classes should be acceptable in either - # direction. - # source = Solargraph::Source.load_string(%( - # class Sup; end - # class Sub < Sup; end - # )) - # api_map = Solargraph::ApiMap.new - # api_map.map source - # sup = Solargraph::ComplexType.parse('Sup') - # sub = Solargraph::ComplexType.parse('Sub') - # match = Solargraph::TypeChecker::Checks.types_match?(api_map, sub, sup) - # expect(match).to be(false) - end + # it 'invalidates inferred superclasses (expected must be super)' do + # # @todo This test might be invalid. There are use cases where inheritance + # # between inferred and expected classes should be acceptable in either + # # direction. + # # source = Solargraph::Source.load_string(%( + # # class Sup; end + # # class Sub < Sup; end + # # )) + # # api_map = Solargraph::ApiMap.new + # # api_map.map source + # # sup = described_class.parse('Sup') + # # sub = described_class.parse('Sub') + # # match = Solargraph::TypeChecker::Checks.types_match?(api_map, sub, sup) + # # expect(match).to be(false) + # end it 'fuzzy matches arrays with parameters' do api_map = Solargraph::ApiMap.new - exp = Solargraph::ComplexType.parse('Array') - inf = Solargraph::ComplexType.parse('Array') + exp = described_class.parse('Array') + inf = described_class.parse('Array') match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end @@ -57,98 +91,107 @@ class Sub < Sup; end source_map = Solargraph::SourceMap.map(source) api_map = Solargraph::ApiMap.new api_map.catalog Solargraph::Bench.new(source_maps: [source_map], external_requires: ['set']) - exp = Solargraph::ComplexType.parse('Set') - inf = Solargraph::ComplexType.parse('Set') + exp = described_class.parse('Set') + inf = described_class.parse('Set') match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end it 'fuzzy matches hashes with parameters' do api_map = Solargraph::ApiMap.new - exp = Solargraph::ComplexType.parse('Hash{ Symbol => String}') - inf = Solargraph::ComplexType.parse('Hash') + exp = described_class.parse('Hash{ Symbol => String}') + inf = described_class.parse('Hash') match = inf.conforms_to?(api_map, exp, :method_call, [:allow_empty_params]) expect(match).to be(true) end it 'matches multiple types' do api_map = Solargraph::ApiMap.new - exp = Solargraph::ComplexType.parse('String, Integer') - inf = Solargraph::ComplexType.parse('String, Integer') + exp = described_class.parse('String, Integer') + inf = described_class.parse('String, Integer') match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end it 'matches multiple types out of order' do api_map = Solargraph::ApiMap.new - exp = Solargraph::ComplexType.parse('String, Integer') - inf = Solargraph::ComplexType.parse('Integer, String') + exp = described_class.parse('String, Integer') + inf = described_class.parse('Integer, String') match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end it 'invalidates inferred types missing from expected' do api_map = Solargraph::ApiMap.new - exp = Solargraph::ComplexType.parse('String') - inf = Solargraph::ComplexType.parse('String, Integer') + exp = described_class.parse('String') + inf = described_class.parse('String, Integer') match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(false) end it 'matches nil' do api_map = Solargraph::ApiMap.new - exp = Solargraph::ComplexType.parse('nil') - inf = Solargraph::ComplexType.parse('nil') + exp = described_class.parse('nil') + inf = described_class.parse('nil') match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end it 'validates classes with expected superclasses' do api_map = Solargraph::ApiMap.new - exp = Solargraph::ComplexType.parse('Class') - inf = Solargraph::ComplexType.parse('Class') + exp = described_class.parse('Class') + inf = described_class.parse('Class') match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end it 'validates generic classes with expected Class' do api_map = Solargraph::ApiMap.new - exp = Solargraph::ComplexType.parse('Class') - inf = Solargraph::ComplexType.parse('Class') - match = inf.conforms_to?(api_map, exp, :method_call, [:allow_empty_params]) - expect(match).to be(true) - end - - it 'validates generic classes with expected Class' do - api_map = Solargraph::ApiMap.new - inf = Solargraph::ComplexType.parse('Class') - exp = Solargraph::ComplexType.parse('Class') + inf = described_class.parse('Class') + exp = described_class.parse('Class') match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end - it 'validates inheritance in both directions' do - source = Solargraph::Source.load_string(%( - class Sup; end - class Sub < Sup; end - )) - api_map = Solargraph::ApiMap.new - api_map.map source - sup = Solargraph::ComplexType.parse('Sup') - sub = Solargraph::ComplexType.parse('Sub') - match = sub.conforms_to?(api_map, sup, :method_call, [:allow_reverse_match]) - expect(match).to be(true) - match = sup.conforms_to?(api_map, sub, :method_call, [:allow_reverse_match]) - expect(match).to be(true) + context 'with an inheritence relationship' do + let(:source) do + Solargraph::Source.load_string(%( + class Sup; end + class Sub < Sup; end + )) + end + let(:sup) { described_class.parse('Sup') } + let(:sub) { described_class.parse('Sub') } + let(:api_map) { Solargraph::ApiMap.new } + + before do + api_map.map source + end + + it 'validates inheritance in one way' do + match = sub.conforms_to?(api_map, sup, :method_call, [:allow_reverse_match]) + expect(match).to be(true) + end + + it 'validates inheritance the other way' do + match = sup.conforms_to?(api_map, sub, :method_call, [:allow_reverse_match]) + expect(match).to be(true) + end end - it 'invalidates inheritance in both directions' do - api_map = Solargraph::ApiMap.new - sup = Solargraph::ComplexType.parse('String') - sub = Solargraph::ComplexType.parse('Array') - match = sub.conforms_to?(api_map, sup, :method_call, [:allow_reverse_match]) - expect(match).to be(false) - match = sup.conforms_to?(api_map, sub, :method_call, [:allow_reverse_match]) - expect(match).to be(false) + context 'with inheritance relationship in allow_reverse_match mode' do + let(:api_map) { Solargraph::ApiMap.new } + let(:sup) { described_class.parse('String') } + let(:sub) { described_class.parse('Array') } + + it 'conforms one way' do + match = sub.conforms_to?(api_map, sup, :method_call, [:allow_reverse_match]) + expect(match).to be(false) + end + + it 'conforms the other way' do + match = sup.conforms_to?(api_map, sub, :method_call, [:allow_reverse_match]) + expect(match).to be(false) + end end end diff --git a/spec/source_map/clip_spec.rb b/spec/source_map/clip_spec.rb index 0f83331ec..801edefa7 100644 --- a/spec/source_map/clip_spec.rb +++ b/spec/source_map/clip_spec.rb @@ -302,6 +302,23 @@ def foo expect(type.tag).to eq('String') end + it 'infers method types from return nodes' do + source = Solargraph::Source.load_string(%( + class Foo + # @return [self] + def foo + bar + end + end + Foo.new.foo + ), 'test.rb') + map = Solargraph::ApiMap.new + map.map source + clip = map.clip_at('test.rb', Solargraph::Position.new(7, 10)) + type = clip.infer + expect(type.tag).to eq('Foo') + end + it 'infers multiple method types from return nodes' do source = Solargraph::Source.load_string(%( def foo diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index d700ea3b7..3ff5aa6c3 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::TypeChecker do - context 'alpha level' do - def type_checker(code) + context 'when at alpha level' do + def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :alpha) end @@ -16,7 +18,8 @@ def bar(b) foo(b) end )) - expect(checker.problems.map(&:message)).to eq(["Wrong argument type for #foo: a expected String, received String, nil"]) + expect(checker.problems.map(&:message)) + .to eq(['Wrong argument type for #foo: a expected String, received String, nil']) end end end diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 7861c8817..9992b43e9 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -59,7 +59,7 @@ def bar(a); end require 'kramdown-parser-gfm' Kramdown::Parser::GFM.undefined_call ), 'test.rb') - api_map = Solargraph::ApiMap.load_with_cache('.', $stdout) + api_map = Solargraph::ApiMap.load '.' api_map.catalog Solargraph::Bench.new(source_maps: [source_map], external_requires: ['kramdown-parser-gfm']) checker = Solargraph::TypeChecker.new('test.rb', api_map: api_map, level: :strict) expect(checker.problems).to be_empty diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 4c45056ea..68a62d395 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -14,7 +14,6 @@ def bar; end expect(checker.problems.first.message).to include('Missing @return tag') end - it 'ignores nilable type issues' do checker = type_checker(%( # @param a [String] @@ -30,6 +29,54 @@ def bar(b) expect(checker.problems.map(&:message)).to eq([]) end + it 'calls out keyword issues even when required arg count matches' do + checker = type_checker(%( + # @param a [String] + # @param b [String] + # @return [void] + def foo(a = 'foo', b:); end + + # @return [void] + def bar + foo('baz') + end + )) + expect(checker.problems.map(&:message)).to include('Call to #foo is missing keyword argument b') + end + + it 'calls out type issues even when keyword issues are there' do + pending('fixes to arg vs param checking algorithm') + + checker = type_checker(%( + # @param a [String] + # @param b [String] + # @return [void] + def foo(a = 'foo', b:); end + + # @return [void] + def bar + foo(123) + end + )) + expect(checker.problems.map(&:message)) + .to include('Wrong argument type for #foo: a expected String, received 123') + end + + it 'calls out keyword issues even when arg type issues are there' do + checker = type_checker(%( + # @param a [String] + # @param b [String] + # @return [void] + def foo(a = 'foo', b:); end + + # @return [void] + def bar + foo(123) + end + )) + expect(checker.problems.map(&:message)).to include('Call to #foo is missing keyword argument b') + end + it 'reports missing param tags' do checker = type_checker(%( class Foo @@ -179,23 +226,6 @@ def baz(bing) expect(checker.problems.map(&:message)).to be_empty end - it 'treats a parameter type of undefined as not provided' do - checker = type_checker(%( - class Foo - # @param foo [Class] - # @return [void] - def bar foo:; end - - # @param bing [Class] - # @return [void] - def baz(bing) - bar(foo: bing) - end - end - )) - expect(checker.problems.map(&:message)).to be_empty - end - it 'ignores generic resolution failures' do checker = type_checker(%( class Foo @@ -252,7 +282,7 @@ def block_pins expect(checker.problems.map(&:message)).to be_empty end - it 'ignores generic resolution failures' do + it 'ignores generic resolution failures with only one arg' do checker = type_checker(%( # @generic T # @param path [String] diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index 6e71ee9ff..681d813d5 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -38,7 +38,7 @@ def bar expect(checker.problems.first.message).to include('does not match') end - it 'reports mismatched key and subtypes ' do + it 'reports mismatched key and subtypes' do checker = type_checker(%( # @return [Hash{String => String}] def foo @@ -207,7 +207,8 @@ def foo # @param bar [String] def foo(bar = 123); end )) - expect(checker.problems.map(&:message)).to eq(['Declared type String does not match inferred type 123 for variable bar']) + expect(checker.problems.map(&:message)) + .to eq(['Declared type String does not match inferred type 123 for variable bar']) end it 'validates string default values of parameters' do From e2fcd121f152183980dcb728bb18284b50017995 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 3 Aug 2025 20:53:12 -0400 Subject: [PATCH 044/460] Add nocov markings for undercover --- lib/solargraph/parser/parser_gem/node_processors/send_node.rb | 4 ++++ lib/solargraph/pin/base.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index 57c74a22c..8490fd9b0 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -11,10 +11,12 @@ def process # @sg-ignore # @type [Symbol] method_name = node.children[1] + # :nocov: unless method_name.instance_of?(Symbol) Solargraph.assert_or_log(:parser_method_name, "Expected method name to be a Symbol, got #{method_name.class} for node #{node.inspect}") return process_children end + # :nocov: if node.children[0].nil? if [:private, :public, :protected].include?(method_name) process_visibility @@ -55,10 +57,12 @@ def process_visibility # @sg-ignore # @type [Symbol] visibility = node.children[1] + # :nocov: unless visibility.instance_of?(Symbol) Solargraph.assert_or_log(:parser_visibility, "Expected visibility name to be a Symbol, got #{visibility.class} for node #{node.inspect}") return process_children end + # :nocov: if child.is_a?(AST::Node) && (child.type == :sym || child.type == :str) name = child.children[0].to_s matches = pins.select{ |pin| pin.is_a?(Pin::Method) && pin.name == name && pin.namespace == region.closure.full_context.namespace && pin.context.scope == (region.scope || :instance)} diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index c002c65eb..c3efd301b 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -279,10 +279,12 @@ def assert_same_count(other, attr) # @sg-ignore # @return [undefined] def assert_same(other, attr) + # :nocov: if other.nil? Solargraph.assert_or_log("combine_with_#{attr}".to_sym, "Sent nil for comparison") return send(attr) end + # :nocov: val1 = send(attr) val2 = other.send(attr) return val1 if val1 == val2 @@ -320,9 +322,11 @@ def choose_pin_attr(other, attr) # @type [Pin::Base, nil] val2 = other.send(attr) if val1.class != val2.class + # :nocov: Solargraph.assert_or_log("combine_with_#{attr}_class".to_sym, "Inconsistent #{attr.inspect} class values between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self.#{attr} = #{val1.inspect}\nother.#{attr} = #{val2.inspect}") return val1 + # :nocov: end # arbitrary way of choosing a pin # @sg-ignore Need _1 support From 8dbe7df207858b9f69b80e95b187adcfd9aaac21 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 3 Aug 2025 21:00:47 -0400 Subject: [PATCH 045/460] Improve a pin combination case around selfy types Also improve type coverage around pin combination --- lib/solargraph/pin/base.rb | 4 +-- spec/pin/combine_with_spec.rb | 67 +++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 spec/pin/combine_with_spec.rb diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index cdd6a5ace..59969f60a 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -158,9 +158,9 @@ def combine_return_type(other) return_type else all_items = return_type.items + other.return_type.items - if all_items.any? { |item| item.selfy? } && all_items.any? { |item| item.rooted_tag == context.rooted_tag } + if all_items.any? { |item| item.selfy? } && all_items.any? { |item| item.rooted_namespace == context.rooted_namespace } # assume this was a declaration that should have said 'self' - all_items.delete_if { |item| item.rooted_tag == context.rooted_tag } + all_items.delete_if { |item| item.rooted_namespace == context.rooted_namespace } end ComplexType.new(all_items) end diff --git a/spec/pin/combine_with_spec.rb b/spec/pin/combine_with_spec.rb new file mode 100644 index 000000000..cc80d76d5 --- /dev/null +++ b/spec/pin/combine_with_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +describe Solargraph::Pin::Base, '#combine_with' do + it 'combines return types with another method pin with same arity' do + pin1 = Solargraph::Pin::Method.new(name: 'foo', parameters: [], comments: '@return [String]') + pin2 = Solargraph::Pin::Method.new(name: 'foo', parameters: [], comments: '@return [Integer]') + combined = pin1.combine_with(pin2) + expect(combined.return_type.to_s).to eq('String, Integer') + end + + it 'combines return types with another method without type parameters' do + pin1 = Solargraph::Pin::Method.new(name: 'foo', parameters: [], comments: '@return [Array]') + pin2 = Solargraph::Pin::Method.new(name: 'foo', parameters: [], comments: '@return [Array]') + combined = pin1.combine_with(pin2) + expect(combined.return_type.to_s).to eq('Array') + end + + context 'with dodgy return types' do + let(:dodgy_location_pin) do + range = Solargraph::Range.new(Solargraph::Position.new(1, 0), Solargraph::Position.new(1, 10)) + location = Solargraph::Location.new('/home/user/.rbenv/versions/3.1.7/lib/ruby/gems/3.1.0/gems' \ + '/activesupport-7.0.8.7/lib/active_support/core_ext/object' \ + '/conversions.rb', + range) + Solargraph::Pin::Method.new(name: 'foo', parameters: [], comments: '@return [Object]', + location: location) + end + + let(:normal_pin) { Solargraph::Pin::Method.new(name: 'foo', parameters: [], comments: '@return [self]') } + + it 'combines a dodgy return type with a valid one' do + combined = dodgy_location_pin.combine_with(normal_pin) + expect(combined.return_type.to_s).to eq('self') + end + + it 'combines a valid return type with a dodgy one' do + combined = normal_pin.combine_with(dodgy_location_pin) + expect(combined.return_type.to_s).to eq('self') + end + end + + context 'with return types that should probably be self' do + let(:closure) do + Solargraph::Pin::Namespace.new( + name: 'Foo', + closure: Solargraph::Pin::ROOT_PIN, + type: :class + ) + end + + let(:likely_selfy_pin) do + Solargraph::Pin::Method.new(name: 'foo', closure: closure, parameters: [], comments: '@return [::Foo]') + end + + let(:selfy_pin) { Solargraph::Pin::Method.new(name: 'foo', closure: closure, parameters: [], comments: '@return [self]') } + + it 'combines a selfy return type with a likely-selfy one' do + combined = likely_selfy_pin.combine_with(selfy_pin) + expect(combined.return_type.to_s).to eq('self') + end + + it 'combines a likely-selfy return type with a selfy one' do + combined = selfy_pin.combine_with(likely_selfy_pin) + expect(combined.return_type.to_s).to eq('self') + end + end +end From fdd3810f34eeaa81e4bbac956d9e8372da1c5c6c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 3 Aug 2025 21:07:30 -0400 Subject: [PATCH 046/460] Linting fix --- lib/solargraph/shell.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 153e77f0e..92f8fed38 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -266,12 +266,8 @@ def method_pin path pins.each do |pin| if options[:typify] || options[:probe] type = ComplexType::UNDEFINED - if options[:typify] - type = pin.typify(api_map) - end - if options[:probe] && type.undefined? - type = pin.probe(api_map) - end + type = pin.typify(api_map) if options[:typify] + type = pin.probe(api_map) if options[:probe] && type.undefined? print_type(type) next end From c4cd5bcc336bd42cec86ac29fe084acb9c68d067 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 3 Aug 2025 21:14:48 -0400 Subject: [PATCH 047/460] Linting fixes --- spec/convention/gemfile_spec.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/spec/convention/gemfile_spec.rb b/spec/convention/gemfile_spec.rb index 62a346ca0..827da7993 100644 --- a/spec/convention/gemfile_spec.rb +++ b/spec/convention/gemfile_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::Convention::Gemfile do describe 'parsing Gemfiles' do - def type_checker(code) + def type_checker code Solargraph::TypeChecker.load_string(code, 'Gemfile', :strong) end @@ -32,8 +34,8 @@ def type_checker(code) )) expect(checker.problems.map(&:message).sort) - .to eq(["Unrecognized keyword argument bad_name to Bundler::Dsl#gemspec", - "Wrong argument type for Bundler::Dsl#source: source expected String, received Class"].sort) + .to eq(['Unrecognized keyword argument bad_name to Bundler::Dsl#gemspec', + 'Wrong argument type for Bundler::Dsl#source: source expected String, received Class'].sort) end it 'finds bad arguments to DSL ruby method' do @@ -44,7 +46,7 @@ def type_checker(code) )) expect(checker.problems.map(&:message)) - .to eq(["Wrong argument type for Bundler::Dsl#ruby: ruby_version expected String, received Integer"]) + .to eq(['Wrong argument type for Bundler::Dsl#ruby: ruby_version expected String, received Integer']) end end end From 8392b41e268955dd97d2748b74e798af092c1b10 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 3 Aug 2025 21:24:02 -0400 Subject: [PATCH 048/460] Move open3 to rbs/fills, as it is not always added as gem --- rbs/fills/open3/0/open3.rbs | 172 ++++++++++++++++++++++++ sig/shims/open3/0/open3.rbs | 28 ---- spec/type_checker/levels/strong_spec.rb | 14 ++ 3 files changed, 186 insertions(+), 28 deletions(-) create mode 100644 rbs/fills/open3/0/open3.rbs delete mode 100644 sig/shims/open3/0/open3.rbs diff --git a/rbs/fills/open3/0/open3.rbs b/rbs/fills/open3/0/open3.rbs new file mode 100644 index 000000000..11d909572 --- /dev/null +++ b/rbs/fills/open3/0/open3.rbs @@ -0,0 +1,172 @@ +# +# Module Open3 supports creating child processes with access to their $stdin, +# $stdout, and $stderr streams. +# +# ## What's Here +# +# Each of these methods executes a given command in a new process or subshell, +# or multiple commands in new processes and/or subshells: +# +# * Each of these methods executes a single command in a process or subshell, +# accepts a string for input to $stdin, and returns string output from +# $stdout, $stderr, or both: +# +# * Open3.capture2: Executes the command; returns the string from $stdout. +# * Open3.capture2e: Executes the command; returns the string from merged +# $stdout and $stderr. +# * Open3.capture3: Executes the command; returns strings from $stdout and +# $stderr. +# +# * Each of these methods executes a single command in a process or subshell, +# and returns pipes for $stdin, $stdout, and/or $stderr: +# +# * Open3.popen2: Executes the command; returns pipes for $stdin and +# $stdout. +# * Open3.popen2e: Executes the command; returns pipes for $stdin and +# merged $stdout and $stderr. +# * Open3.popen3: Executes the command; returns pipes for $stdin, $stdout, +# and $stderr. +# +# * Each of these methods executes one or more commands in processes and/or +# subshells, returns pipes for the first $stdin, the last $stdout, or both: +# +# * Open3.pipeline_r: Returns a pipe for the last $stdout. +# * Open3.pipeline_rw: Returns pipes for the first $stdin and the last +# $stdout. +# * Open3.pipeline_w: Returns a pipe for the first $stdin. +# * Open3.pipeline_start: Does not wait for processes to complete. +# * Open3.pipeline: Waits for processes to complete. +# +# Each of the methods above accepts: +# +# * An optional hash of environment variable names and values; see [Execution +# Environment](rdoc-ref:Process@Execution+Environment). +# * A required string argument that is a `command_line` or `exe_path`; see +# [Argument command_line or +# exe_path](rdoc-ref:Process@Argument+command_line+or+exe_path). +# * An optional hash of execution options; see [Execution +# Options](rdoc-ref:Process@Execution+Options). +# +module Open3 + # + # Basically a wrapper for Open3.popen3 that: + # + # * Creates a child process, by calling Open3.popen3 with the given arguments + # (except for certain entries in hash `options`; see below). + # * Returns as string `stdout_and_stderr_s` the merged standard output and + # standard error of the child process. + # * Returns as `status` a `Process::Status` object that represents the exit + # status of the child process. + # + # Returns the array `[stdout_and_stderr_s, status]`: + # + # stdout_and_stderr_s, status = Open3.capture2e('echo "Foo"') + # # => ["Foo\n", #] + # + # Like Process.spawn, this method has potential security vulnerabilities if + # called with untrusted input; see [Command + # Injection](rdoc-ref:command_injection.rdoc@Command+Injection). + # + # Unlike Process.spawn, this method waits for the child process to exit before + # returning, so the caller need not do so. + # + # If the first argument is a hash, it becomes leading argument `env` in the call + # to Open3.popen3; see [Execution + # Environment](rdoc-ref:Process@Execution+Environment). + # + # If the last argument is a hash, it becomes trailing argument `options` in the + # call to Open3.popen3; see [Execution + # Options](rdoc-ref:Process@Execution+Options). + # + # The hash `options` is given; two options have local effect in method + # Open3.capture2e: + # + # * If entry `options[:stdin_data]` exists, the entry is removed and its + # string value is sent to the command's standard input: + # + # Open3.capture2e('tee', stdin_data: 'Foo') + # # => ["Foo", #] + # + # * If entry `options[:binmode]` exists, the entry is removed and the internal + # streams are set to binary mode. + # + # The single required argument is one of the following: + # + # * `command_line` if it is a string, and if it begins with a shell reserved + # word or special built-in, or if it contains one or more metacharacters. + # * `exe_path` otherwise. + # + # **Argument `command_line`** + # + # String argument `command_line` is a command line to be passed to a shell; it + # must begin with a shell reserved word, begin with a special built-in, or + # contain meta characters: + # + # Open3.capture2e('if true; then echo "Foo"; fi') # Shell reserved word. + # # => ["Foo\n", #] + # Open3.capture2e('echo') # Built-in. + # # => ["\n", #] + # Open3.capture2e('date > date.tmp') # Contains meta character. + # # => ["", #] + # + # The command line may also contain arguments and options for the command: + # + # Open3.capture2e('echo "Foo"') + # # => ["Foo\n", #] + # + # **Argument `exe_path`** + # + # Argument `exe_path` is one of the following: + # + # * The string path to an executable to be called. + # * A 2-element array containing the path to an executable and the string to + # be used as the name of the executing process. + # + # Example: + # + # Open3.capture2e('/usr/bin/date') + # # => ["Sat Sep 30 09:01:46 AM CDT 2023\n", #] + # + # Ruby invokes the executable directly, with no shell and no shell expansion: + # + # Open3.capture2e('doesnt_exist') # Raises Errno::ENOENT + # + # If one or more `args` is given, each is an argument or option to be passed to + # the executable: + # + # Open3.capture2e('echo', 'C #') + # # => ["C #\n", #] + # Open3.capture2e('echo', 'hello', 'world') + # # => ["hello world\n", #] + # + def self.capture2e: (*String, ?stdin_data: String, ?chdir: String, ?binmode: boolish) -> [String, Process::Status] + | (Hash[String, String] env, *String cmds, ?chdir: String, ?stdin_data: String, ?binmode: boolish) -> [String, Process::Status] + + def self.capture2: (?Hash[String, String] env, *String cmds, ?chdir: String) -> [String, Process::Status] + + def self.capture3: (?Hash[String, String] env, *String cmds, ?chdir: String) -> [String, String, Process::Status] + + def self.pipeline: (?Hash[String, String] env, *String cmds, ?chdir: String) -> Array[Process::Status] + + def self.pipeline_r: (?Hash[String, String] env, *String cmds, ?chdir: String) -> [IO, Process::Waiter] + + def self.pipeline_rw: (?Hash[String, String] env, *String cmds, ?chdir: String) -> [IO, IO, Process::Waiter] + + def self.pipeline_start: (?Hash[String, String] env, *String cmds, ?chdir: String) -> Array[Process::Waiter] + + def self.pipeline_w: (?Hash[String, String] env, *String cmds, ?chdir: String) -> [IO, Process::Waiter] + + def self.popen2: (?Hash[String, String] env, *String exe_path_or_cmd_with_args, ?chdir: String) -> [IO, IO, Process::Waiter] + | [U] (?Hash[String, String] env, *String exe_path_or_cmd_with_args, ?chdir: String) { (IO stdin, IO stdout, Process::Waiter wait_thread) -> U } -> U + + def self.popen2e: (?Hash[String, String] env, *String exe_path_or_cmd_with_args, ?chdir: String) -> [IO, IO, Process::Waiter] + | [U] (?Hash[String, String] env, *String exe_path_or_cmd_with_args, ?chdir: String) { (IO stdin, IO stdout_and_stderr, Process::Waiter wait_thread) -> U } -> U + + def self.popen3: (?Hash[String, String] env, *String exe_path_or_cmd_with_args, ?chdir: String) -> [IO, IO, IO, Process::Waiter] + | [U] (?Hash[String, String] env, *String exe_path_or_cmd_with_args, ?chdir: String) { (IO stdin, IO stdout, IO stderr, Process::Waiter wait_thread) -> U } -> U + +end diff --git a/sig/shims/open3/0/open3.rbs b/sig/shims/open3/0/open3.rbs deleted file mode 100644 index d1397e549..000000000 --- a/sig/shims/open3/0/open3.rbs +++ /dev/null @@ -1,28 +0,0 @@ -module Open3 - def self.capture2: (?Hash[String, String] env, *String cmds) -> [String, Process::Status] - - def self.capture2e: (?Hash[String, String] env, *String cmds) -> [String, Process::Status] - - def self.capture3: (?Hash[String, String] env, *String cmds) -> [String, String, Process::Status] - - def self.pipeline: (?Hash[String, String] env, *String cmds) -> Array[Process::Status] - - def self.pipeline_r: (?Hash[String, String] env, *String cmds) -> [IO, Process::Waiter] - - def self.pipeline_rw: (?Hash[String, String] env, *String cmds) -> [IO, IO, Process::Waiter] - - def self.pipeline_start: (?Hash[String, String] env, *String cmds) -> Array[Process::Waiter] - - def self.pipeline_w: (?Hash[String, String] env, *String cmds) -> [IO, Process::Waiter] - - def self.popen2: (?Hash[String, String] env, *String exe_path_or_cmd_with_args) -> [IO, IO, Process::Waiter] - | [U] (?Hash[String, String] env, *String exe_path_or_cmd_with_args) { (IO stdin, IO stdout, Process::Waiter wait_thread) -> U } -> U - - def self.popen2e: (?Hash[String, String] env, *String exe_path_or_cmd_with_args) -> [IO, IO, Process::Waiter] - | [U] (?Hash[String, String] env, *String exe_path_or_cmd_with_args) { (IO stdin, IO stdout_and_stderr, Process::Waiter wait_thread) -> U } -> U - - def self.popen3: (?Hash[String, String] env, *String exe_path_or_cmd_with_args) -> [IO, IO, IO, Process::Waiter] - | [U] (?Hash[String, String] env, *String exe_path_or_cmd_with_args) { (IO stdin, IO stdout, IO stderr, Process::Waiter wait_thread) -> U } -> U - - VERSION: ::String -end diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 12db1e442..5af87a23c 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -141,5 +141,19 @@ def meth arg )) expect(checker.problems).to be_empty end + + it 'understands Open3 methods' do + checker = type_checker(%( + require 'open3' + + # @return [void] + def run_command + # @type [Hash{String => String}] + foo = {'foo' => 'bar'} + Open3.capture2e(foo, 'ls', chdir: '/tmp') + end + )) + expect(checker.problems.map(&:message)).to be_empty + end end end From 30cdfc8b3091ff327c15d06eddb1c96315b2464c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 3 Aug 2025 21:38:42 -0400 Subject: [PATCH 049/460] Add spec --- spec/shell_spec.rb | 114 ++++++++++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 27 +++++++++++ 2 files changed, 141 insertions(+) create mode 100644 spec/shell_spec.rb diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb new file mode 100644 index 000000000..1da2a98a9 --- /dev/null +++ b/spec/shell_spec.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +require 'tmpdir' +require 'open3' + +describe Solargraph::Shell do + let(:shell) { described_class.new } + + # @type cmd [Array] + # @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 'method_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::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.method_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.method_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.method_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]) + capture_both do + shell.options = { stack: true } + shell.method_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.method_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.method_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([]) + + out = capture_both do + shell.options = {} + shell.method_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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index faba8172e..b69e64097 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -19,3 +19,30 @@ 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 From f4abeb4cf28a050269d457d2fbeab1765af9abfa Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 3 Aug 2025 21:43:38 -0400 Subject: [PATCH 050/460] Remove unneeded file --- sig/shims/parser/3.2.0.1/builders.rb | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 sig/shims/parser/3.2.0.1/builders.rb diff --git a/sig/shims/parser/3.2.0.1/builders.rb b/sig/shims/parser/3.2.0.1/builders.rb deleted file mode 100644 index b932108a9..000000000 --- a/sig/shims/parser/3.2.0.1/builders.rb +++ /dev/null @@ -1,2 +0,0 @@ -module Builders -end From a30d7904818b16a1a88eafbe9977b9e8598f534d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 3 Aug 2025 21:53:24 -0400 Subject: [PATCH 051/460] Fix spec --- lib/solargraph/shell.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 92f8fed38..76f711552 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -247,7 +247,7 @@ def list # @param path [String] The path to the method pin, e.g. 'Class#method' or 'Class.method' # @return [void] def method_pin path - api_map = Solargraph::ApiMap.load_with_cache('.', STDERR) + api_map = Solargraph::ApiMap.load_with_cache('.', $stderr) pins = if options[:stack] scope, ns, meth = if path.include? '#' @@ -260,7 +260,7 @@ def method_pin path api_map.get_path_pins path end if pins.empty? - STDERR.puts "Pin not found for path '#{path}'" + $stderr.puts "Pin not found for path '#{path}'" exit 1 end pins.each do |pin| From 592e6bcd89bd0988e1245c6eac8cd14d2f6b621d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 06:55:28 -0400 Subject: [PATCH 052/460] Speed up spec --- spec/rbs_map/conversions_spec.rb | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index d9570ae0f..008432d68 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -55,21 +55,23 @@ def bar: () -> untyped end context 'with standard loads for solargraph project' do - let(:api_map) { Solargraph::ApiMap.load_with_cache('.') } - - let(:superclass_pin) do - api_map.pins.find do |pin| - pin.is_a?(Solargraph::Pin::Reference::Superclass) && pin.context.namespace == 'Parser::AST::Node' - end + before :all do # rubocop:disable RSpec/BeforeAfterAll + @api_map = Solargraph::ApiMap.load_with_cache('.') end - it 'finds a superclass pin for Parser::AST::Node' do - expect(superclass_pin).not_to be_nil - end + let(:api_map) { @api_map } # rubocop:disable RSpec/InstanceVariable - it 'generates a rooted pin for superclass of Parser::AST::Node' do - # rooted! - expect(superclass_pin.name) .to eq('::AST::Node') + context 'with superclass pin for Parser::AST::Node' do + let(:superclass_pin) do + api_map.pins.find do |pin| + pin.is_a?(Solargraph::Pin::Reference::Superclass) && pin.context.namespace == 'Parser::AST::Node' + end + end + + it 'generates a rooted pin' do + # rooted! + expect(superclass_pin&.name).to eq('::AST::Node') + end end end end From 5c24a74c5c60f2297862c01ce464af94aa587b50 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 06:59:53 -0400 Subject: [PATCH 053/460] Add Open3.capture2e chdir spec --- spec/rbs_map/conversions_spec.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 09c203687..220f69190 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -51,4 +51,26 @@ def bar: () -> untyped expect(method_pin.return_type.tag).to eq('undefined') end end + + if Gem::Version.new(RBS::VERSION) >= Gem::Version.new('3.9.1') + context 'with method pin for Open3.capture2e' do + let(:api_map) { Solargraph::ApiMap.load_with_cache('.') } + + let(:method_pin) do + api_map.pins.find do |pin| + pin.is_a?(Solargraph::Pin::Method) && pin.path == 'Open3.capture2e' + end + end + + let(:chdir_param) do + method_pin&.signatures&.flat_map(&:parameters)&.find do |param| + param.name == 'chdir' + end + end + + it 'accepts chdir kwarg' do + expect(chdir_param).not_to be_nil, -> { "Found pin #{method_pin.to_rbs} from #{method_pin.type_location}" } + end + end + end end From 14dacc39eba0f1906d2f7f83019d2de3b7217ffa Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 08:04:09 -0400 Subject: [PATCH 054/460] Linting fix --- lib/solargraph/complex_type.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 8167339e0..6203fb5a9 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -224,7 +224,7 @@ def duck_types_match? api_map, expected, inferred raise ArgumentError, 'Expected type must be duck type' unless expected.duck_type? expected.each do |exp| next unless exp.duck_type? - quack = exp.to_s[1..-1] + quack = exp.to_s[1..] return false if api_map.get_method_stack(inferred.namespace, quack, scope: inferred.scope).empty? end true From 883574e3077c57fff1e58969ef06aa5ff5f21795 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 08:53:48 -0400 Subject: [PATCH 055/460] Add spec --- spec/complex_type/unique_type_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 spec/complex_type/unique_type_spec.rb diff --git a/spec/complex_type/unique_type_spec.rb b/spec/complex_type/unique_type_spec.rb new file mode 100644 index 000000000..2d9812600 --- /dev/null +++ b/spec/complex_type/unique_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +describe Solargraph::ComplexType::UniqueType do + describe '#any?' do + let(:type) { described_class.parse('String') } + + it 'yields one and only one type, itself' do + types_encountered = [] + type.any? { |t| types_encountered << t } + expect(types_encountered).to eq([type]) + end + end +end From 637edd0b37caabe007a9539ef8abd4198c73da68 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 09:31:03 -0400 Subject: [PATCH 056/460] Linting fixes --- spec/type_checker/levels/strong_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 68a62d395..1d343c6d9 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -226,7 +226,7 @@ def baz(bing) expect(checker.problems.map(&:message)).to be_empty end - it 'ignores generic resolution failures' do + it 'ignores generic resolution failure with no generic tag' do checker = type_checker(%( class Foo # @param foo [Class] @@ -262,8 +262,7 @@ def block_pins expect(checker.problems.map(&:message)).to be_empty end - - it 'ignores generic resolution failures' do + it 'ignores generic resolution failures from current Solargraph limitation' do checker = type_checker(%( class Foo # @generic T From 4fc60fb37256fff46151c4b1daea22ac4e4ca32f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 09:31:26 -0400 Subject: [PATCH 057/460] Linting fixes --- spec/type_checker/levels/strong_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 5af87a23c..a0bab2152 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -144,15 +144,15 @@ def meth arg it 'understands Open3 methods' do checker = type_checker(%( - require 'open3' + require 'open3' - # @return [void] - def run_command - # @type [Hash{String => String}] - foo = {'foo' => 'bar'} - Open3.capture2e(foo, 'ls', chdir: '/tmp') - end - )) + # @return [void] + def run_command + # @type [Hash{String => String}] + foo = {'foo' => 'bar'} + Open3.capture2e(foo, 'ls', chdir: '/tmp') + end + )) expect(checker.problems.map(&:message)).to be_empty end end From 0b6e3094715140e54a21ecee73ef62b85f84baa0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 09:33:12 -0400 Subject: [PATCH 058/460] Linting fixes --- spec/rbs_map/conversions_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 220f69190..bb57dfd0b 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -63,7 +63,7 @@ def bar: () -> untyped end let(:chdir_param) do - method_pin&.signatures&.flat_map(&:parameters)&.find do |param| + method_pin&.signatures&.flat_map(&:parameters)&.find do |param| # rubocop:disable Style/SafeNavigationChainLength param.name == 'chdir' end end From e8c2cca9cf8728b9b7db04fca1238f2ecc234363 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 11:41:10 -0400 Subject: [PATCH 059/460] Improve test coverage and fix bug --- lib/solargraph/complex_type/unique_type.rb | 4 +- spec/complex_type/conforms_to_spec.rb | 68 ++++++++++++++++------ 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index a9f2a899b..13fb7129f 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -241,7 +241,7 @@ def conforms_to_unique_type?(api_map, expected, situation = :method_call, if variance == :invariant return false unless inferred.name == expected.name - elsif erased_variance == :covariant + elsif variance == :covariant # covariant: we can pass in a more specific type # we contain the expected mix-in, or we have a more specific type @@ -249,7 +249,7 @@ def conforms_to_unique_type?(api_map, expected, situation = :method_call, api_map.super_and_sub?(expected.name, inferred.name) || inferred.name == expected.name - elsif erased_variance == :contravariant + elsif variance == :contravariant # contravariant: we can pass in a more general type # we contain the expected mix-in, or we have a more general type diff --git a/spec/complex_type/conforms_to_spec.rb b/spec/complex_type/conforms_to_spec.rb index 581ce1d34..bacf19b5e 100644 --- a/spec/complex_type/conforms_to_spec.rb +++ b/spec/complex_type/conforms_to_spec.rb @@ -1,8 +1,11 @@ # frozen_string_literal: true describe Solargraph::ComplexType do + let(:api_map) do + Solargraph::ApiMap.new + end + it 'validates simple core types' do - api_map = Solargraph::ApiMap.new exp = described_class.parse('String') inf = described_class.parse('String') match = inf.conforms_to?(api_map, exp, :method_call) @@ -10,7 +13,6 @@ end it 'invalidates simple core types' do - api_map = Solargraph::ApiMap.new exp = described_class.parse('String') inf = described_class.parse('Integer') match = inf.conforms_to?(api_map, exp, :method_call) @@ -18,7 +20,6 @@ end it 'allows subtype skew if told' do - api_map = Solargraph::ApiMap.new exp = described_class.parse('Array') inf = described_class.parse('Array') match = inf.conforms_to?(api_map, exp, :method_call, [:allow_subtype_skew]) @@ -26,7 +27,6 @@ end it 'accepts valid tuple conformance' do - api_map = Solargraph::ApiMap.new exp = described_class.parse('Array(Integer, Integer)') inf = described_class.parse('Array(Integer, Integer)') match = inf.conforms_to?(api_map, exp, :method_call) @@ -34,7 +34,6 @@ end it 'rejects invalid tuple conformance' do - api_map = Solargraph::ApiMap.new exp = described_class.parse('Array(Integer, Integer)') inf = described_class.parse('Array(Integer, String)') match = inf.conforms_to?(api_map, exp, :method_call) @@ -42,7 +41,6 @@ end it 'allows empty params when specified' do - api_map = Solargraph::ApiMap.new exp = described_class.parse('Array(Integer, Integer)') inf = described_class.parse('Array') match = inf.conforms_to?(api_map, exp, :method_call, [:allow_empty_params]) @@ -54,7 +52,6 @@ class Sup; end class Sub < Sup; end )) - api_map = Solargraph::ApiMap.new api_map.map source sup = described_class.parse('Sup') sub = described_class.parse('Sub') @@ -70,7 +67,6 @@ class Sub < Sup; end # # class Sup; end # # class Sub < Sup; end # # )) - # # api_map = Solargraph::ApiMap.new # # api_map.map source # # sup = described_class.parse('Sup') # # sub = described_class.parse('Sub') @@ -79,7 +75,6 @@ class Sub < Sup; end # end it 'fuzzy matches arrays with parameters' do - api_map = Solargraph::ApiMap.new exp = described_class.parse('Array') inf = described_class.parse('Array') match = inf.conforms_to?(api_map, exp, :method_call) @@ -89,7 +84,6 @@ class Sub < Sup; end it 'fuzzy matches sets with parameters' do source = Solargraph::Source.load_string("require 'set'") source_map = Solargraph::SourceMap.map(source) - api_map = Solargraph::ApiMap.new api_map.catalog Solargraph::Bench.new(source_maps: [source_map], external_requires: ['set']) exp = described_class.parse('Set') inf = described_class.parse('Set') @@ -98,7 +92,6 @@ class Sub < Sup; end end it 'fuzzy matches hashes with parameters' do - api_map = Solargraph::ApiMap.new exp = described_class.parse('Hash{ Symbol => String}') inf = described_class.parse('Hash') match = inf.conforms_to?(api_map, exp, :method_call, [:allow_empty_params]) @@ -106,7 +99,6 @@ class Sub < Sup; end end it 'matches multiple types' do - api_map = Solargraph::ApiMap.new exp = described_class.parse('String, Integer') inf = described_class.parse('String, Integer') match = inf.conforms_to?(api_map, exp, :method_call) @@ -114,7 +106,6 @@ class Sub < Sup; end end it 'matches multiple types out of order' do - api_map = Solargraph::ApiMap.new exp = described_class.parse('String, Integer') inf = described_class.parse('Integer, String') match = inf.conforms_to?(api_map, exp, :method_call) @@ -122,7 +113,6 @@ class Sub < Sup; end end it 'invalidates inferred types missing from expected' do - api_map = Solargraph::ApiMap.new exp = described_class.parse('String') inf = described_class.parse('String, Integer') match = inf.conforms_to?(api_map, exp, :method_call) @@ -130,7 +120,6 @@ class Sub < Sup; end end it 'matches nil' do - api_map = Solargraph::ApiMap.new exp = described_class.parse('nil') inf = described_class.parse('nil') match = inf.conforms_to?(api_map, exp, :method_call) @@ -138,7 +127,6 @@ class Sub < Sup; end end it 'validates classes with expected superclasses' do - api_map = Solargraph::ApiMap.new exp = described_class.parse('Class') inf = described_class.parse('Class') match = inf.conforms_to?(api_map, exp, :method_call) @@ -146,13 +134,58 @@ class Sub < Sup; end end it 'validates generic classes with expected Class' do - api_map = Solargraph::ApiMap.new inf = described_class.parse('Class') exp = described_class.parse('Class') match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end + context 'with invariant matching' do + it 'rejects String matching an Object' do + inf = described_class.parse('String') + exp = described_class.parse('Object') + match = inf.conforms_to?(api_map, exp, :method_call, variance: :invariant) + expect(match).to be(false) + end + + it 'rejects Object matching an String' do + inf = described_class.parse('Object') + exp = described_class.parse('String') + match = inf.conforms_to?(api_map, exp, :method_call, variance: :invariant) + expect(match).to be(false) + end + + it 'accepts String matching a String' do + inf = described_class.parse('String') + exp = described_class.parse('String') + match = inf.conforms_to?(api_map, exp, :method_call, variance: :invariant) + expect(match).to be(true) + end + end + + context 'with contravariant matching' do + it 'rejects String matching an Objet' do + inf = described_class.parse('String') + exp = described_class.parse('Object') + match = inf.conforms_to?(api_map, exp, :method_call, variance: :contravariant) + expect(match).to be(false) + end + + it 'accepts Object matching an String' do + inf = described_class.parse('Object') + exp = described_class.parse('String') + match = inf.conforms_to?(api_map, exp, :method_call, variance: :contravariant) + expect(match).to be(true) + end + + it 'accepts String matching a String' do + inf = described_class.parse('String') + exp = described_class.parse('String') + match = inf.conforms_to?(api_map, exp, :method_call, variance: :contravariant) + expect(match).to be(true) + end + end + context 'with an inheritence relationship' do let(:source) do Solargraph::Source.load_string(%( @@ -162,7 +195,6 @@ class Sub < Sup; end end let(:sup) { described_class.parse('Sup') } let(:sub) { described_class.parse('Sub') } - let(:api_map) { Solargraph::ApiMap.new } before do api_map.map source From 13d87eee2fcda17c0fb92e13d14bf82f3c27236c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 12:16:45 -0400 Subject: [PATCH 060/460] Linting fix --- lib/solargraph/complex_type/unique_type.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 13fb7129f..15b7801b1 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -239,9 +239,10 @@ def conforms_to_unique_type?(api_map, expected, situation = :method_call, return true if inferred == expected - if variance == :invariant + case variance + when :invariant return false unless inferred.name == expected.name - elsif variance == :covariant + when :covariant # covariant: we can pass in a more specific type # we contain the expected mix-in, or we have a more specific type @@ -249,7 +250,7 @@ def conforms_to_unique_type?(api_map, expected, situation = :method_call, api_map.super_and_sub?(expected.name, inferred.name) || inferred.name == expected.name - elsif variance == :contravariant + when :contravariant # contravariant: we can pass in a more general type # we contain the expected mix-in, or we have a more general type From 6f0a151e9f3948679e64abce802806c7061d52bf Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 13:05:00 -0400 Subject: [PATCH 061/460] Extract method --- lib/solargraph/complex_type.rb | 1 + lib/solargraph/complex_type/conformance.rb | 133 +++++++++++++++++++++ lib/solargraph/complex_type/unique_type.rb | 106 +--------------- 3 files changed, 137 insertions(+), 103 deletions(-) create mode 100644 lib/solargraph/complex_type/conformance.rb diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 6203fb5a9..d0f46a28d 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -9,6 +9,7 @@ class ComplexType # include TypeMethods include Equality + autoload :Conformance, 'solargraph/complex_type/conformance' autoload :TypeMethods, 'solargraph/complex_type/type_methods' autoload :UniqueType, 'solargraph/complex_type/unique_type' diff --git a/lib/solargraph/complex_type/conformance.rb b/lib/solargraph/complex_type/conformance.rb new file mode 100644 index 000000000..f4f37bf4f --- /dev/null +++ b/lib/solargraph/complex_type/conformance.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +module Solargraph + class ComplexType + # Checks whether a type can be used in a given situation + class Conformance + # @param api_map [ApiMap] + # @param inferred [ComplexType::UniqueType] + # @param expected [ComplexType::UniqueType] + # @param situation [:method_call, :return_type] + # @param rules [Array<:allow_subtype_skew, :allow_empty_params, :allow_reverse_match, + # :allow_any_match, :allow_undefined, :allow_unresolved_generic, :allow_unmatched_interface>] + # @param variance [:invariant, :covariant, :contravariant] + def initialize api_map, inferred, expected, + situation = :method_call, rules = [], + variance: inferred.erased_variance(situation) + @api_map = api_map + @inferred = inferred + @expected = expected + @situation = situation + @rules = rules + @variance = variance + unless expected.is_a?(UniqueType) + raise "Expected type must be a UniqueType, got #{expected.class} in #{expected.inspect}" + end + return if inferred.is_a?(UniqueType) + raise "Inferred type must be a UniqueType, got #{inferred.class} in #{inferred.inspect}" + end + + def conforms_to_unique_type? + unless expected.is_a?(UniqueType) + raise "Expected type must be a UniqueType, got #{expected.class} in #{expected.inspect}" + end + if inferred.literal? && !expected.literal? + conformance = + self.class.new(api_map, inferred.simplify_literals, expected, situation, rules, + variance: variance) + return conformance.conforms_to_unique_type? + end + return true if expected.any?(&:interface?) && rules.include?(:allow_unmatched_interface) + return true if inferred.interface? && rules.include?(:allow_unmatched_interface) + + if rules.include? :allow_reverse_match + reversed_match = expected.conforms_to?(api_map, inferred, situation, + rules - [:allow_reverse_match], + variance: variance) + return true if reversed_match + end + expected = self.expected.downcast_to_literal_if_possible + inferred = self.inferred.downcast_to_literal_if_possible + + if rules.include? :allow_subtype_skew + # parameters are not considered in this case + expected = expected.erase_parameters + end + + inferred = inferred.erase_parameters if !expected.parameters? && inferred.parameters? + + if expected.parameters? && !inferred.parameters? && rules.include?(:allow_empty_params) + expected = expected.erase_parameters + end + + return true if inferred == expected + + case variance + when :invariant + return false unless inferred.name == expected.name + when :covariant + # covariant: we can pass in a more specific type + + # we contain the expected mix-in, or we have a more specific type + return false unless api_map.type_include?(inferred.name, expected.name) || + api_map.super_and_sub?(expected.name, inferred.name) || + inferred.name == expected.name + + when :contravariant + # contravariant: we can pass in a more general type + + # we contain the expected mix-in, or we have a more general type + return false unless api_map.type_include?(inferred.name, expected.name) || + api_map.super_and_sub?(inferred.name, expected.name) || + inferred.name == expected.name + else + # :nocov: + raise "Unknown variance: #{variance.inspect}" + # :nocov: + end + + return true if inferred.all_params.empty? && rules.include?(:allow_empty_params) + + # at this point we know the erased type is fine - time to look at parameters + + # there's an implicit 'any' on the expectation parameters + # if there are none specified + return true if expected.all_params.empty? + + unless expected.key_types.empty? + return false if inferred.key_types.empty? + + unless ComplexType.new(inferred.key_types).conforms_to?(api_map, + ComplexType.new(expected.key_types), + situation, + rules, + variance: inferred.parameter_variance(situation)) + return false + end + end + + return true if expected.subtypes.empty? + + return true if expected.subtypes.any?(&:undefined?) && rules.include?(:allow_undefined) + + return true if inferred.subtypes.any?(&:undefined?) && rules.include?(:allow_undefined) + + return true if inferred.subtypes.all?(&:generic?) && rules.include?(:allow_unresolved_generic) + + return true if expected.subtypes.all?(&:generic?) && rules.include?(:allow_unresolved_generic) + + return false if inferred.subtypes.empty? + + ComplexType.new(inferred.subtypes).conforms_to?(api_map, + ComplexType.new(expected.subtypes), + situation, + rules, + variance: inferred.parameter_variance(situation)) + end + + private + + attr_reader :api_map, :inferred, :expected, :situation, :rules, :variance + end + end +end diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 15b7801b1..9327d3fc8 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -199,107 +199,6 @@ def erased_version_of?(other) name == other.name && (all_params.empty? || all_params.all?(&:undefined?)) end - # @param api_map [ApiMap] - # @param expected [ComplexType::UniqueType] - # @param situation [:method_call, :return_type] - # @param rules [Array<:allow_subtype_skew, :allow_empty_params, :allow_reverse_match, :allow_any_match, :allow_undefined, :allow_unresolved_generic, :allow_unmatched_interface>] - # @param variance [:invariant, :covariant, :contravariant] - def conforms_to_unique_type?(api_map, expected, situation = :method_call, - rules = [], - variance: erased_variance(situation)) - raise "Expected type must be a UniqueType, got #{expected.class} in #{expected.inspect}" unless expected.is_a?(UniqueType) - if literal? && !expected.literal? - return simplify_literals.conforms_to_unique_type?(api_map, expected, situation, - rules, variance: variance) - end - return true if expected.any?(&:interface?) && rules.include?(:allow_unmatched_interface) - return true if interface? && rules.include?(:allow_unmatched_interface) - - if rules.include? :allow_reverse_match - reversed_match = expected.conforms_to?(api_map, self, situation, - rules - [:allow_reverse_match], - variance: variance) - return true if reversed_match - end - expected = expected.downcast_to_literal_if_possible - inferred = downcast_to_literal_if_possible - - if rules.include? :allow_subtype_skew - # parameters are not considered in this case - expected = expected.erase_parameters - end - - if !expected.parameters? && inferred.parameters? - inferred = inferred.erase_parameters - end - - if expected.parameters? && !inferred.parameters? && rules.include?(:allow_empty_params) - expected = expected.erase_parameters - end - - return true if inferred == expected - - case variance - when :invariant - return false unless inferred.name == expected.name - when :covariant - # covariant: we can pass in a more specific type - - # we contain the expected mix-in, or we have a more specific type - return false unless api_map.type_include?(inferred.name, expected.name) || - api_map.super_and_sub?(expected.name, inferred.name) || - inferred.name == expected.name - - when :contravariant - # contravariant: we can pass in a more general type - - # we contain the expected mix-in, or we have a more general type - return false unless api_map.type_include?(inferred.name, expected.name) || - api_map.super_and_sub?(inferred.name, expected.name) || - inferred.name == expected.name - else - # :nocov: - raise "Unknown erased variance: #{erased_variance.inspect}" - # :nocov: - end - - return true if inferred.all_params.empty? && rules.include?(:allow_empty_params) - - # at this point we know the erased type is fine - time to look at parameters - - # there's an implicit 'any' on the expectation parameters - # if there are none specified - return true if expected.all_params.empty? - - unless expected.key_types.empty? - return false if inferred.key_types.empty? - - return false unless ComplexType.new(inferred.key_types).conforms_to?(api_map, - ComplexType.new(expected.key_types), - situation, - rules, - variance: parameter_variance(situation)) - end - - return true if expected.subtypes.empty? - - return true if expected.subtypes.any?(&:undefined?) && rules.include?(:allow_undefined) - - return true if inferred.subtypes.any?(&:undefined?) && rules.include?(:allow_undefined) - - return true if inferred.subtypes.all?(&:generic?) && rules.include?(:allow_unresolved_generic) - - return true if expected.subtypes.all?(&:generic?) && rules.include?(:allow_unresolved_generic) - - return false if inferred.subtypes.empty? - - ComplexType.new(inferred.subtypes).conforms_to?(api_map, - ComplexType.new(expected.subtypes), - situation, - rules, - variance: parameter_variance(situation)) - end - # @param api_map [ApiMap] # @param expected [ComplexType::UniqueType, ComplexType] # @param situation [:method_call, :assignment, :return] @@ -320,8 +219,9 @@ def conforms_to?(api_map, expected, situation, rules, raise "Expected type must be a UniqueType, got #{expected_unique_type.class} in #{expected.inspect}" end # :nocov: - conforms_to_unique_type?(api_map, expected_unique_type, situation, - rules, variance: variance) + conformance = Conformance.new(api_map, self, expected_unique_type, situation, + rules, variance: variance) + conformance.conforms_to_unique_type? end end From f9a493206a71882eb4603fcdd1055c957cbd91eb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 13:30:57 -0400 Subject: [PATCH 062/460] Refactors --- lib/solargraph/complex_type/conformance.rb | 31 ++++++++++++++-------- lib/solargraph/complex_type/unique_type.rb | 4 +++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/solargraph/complex_type/conformance.rb b/lib/solargraph/complex_type/conformance.rb index f4f37bf4f..a055c297c 100644 --- a/lib/solargraph/complex_type/conformance.rb +++ b/lib/solargraph/complex_type/conformance.rb @@ -31,11 +31,8 @@ def conforms_to_unique_type? unless expected.is_a?(UniqueType) raise "Expected type must be a UniqueType, got #{expected.class} in #{expected.inspect}" end - if inferred.literal? && !expected.literal? - conformance = - self.class.new(api_map, inferred.simplify_literals, expected, situation, rules, - variance: variance) - return conformance.conforms_to_unique_type? + if inferred.simplifyable_literal? && !expected.literal? + return with_new_types(inferred.simplify_literals, expected).conforms_to_unique_type? end return true if expected.any?(&:interface?) && rules.include?(:allow_unmatched_interface) return true if inferred.interface? && rules.include?(:allow_unmatched_interface) @@ -46,18 +43,23 @@ def conforms_to_unique_type? variance: variance) return true if reversed_match end - expected = self.expected.downcast_to_literal_if_possible - inferred = self.inferred.downcast_to_literal_if_possible + if expected != expected.downcast_to_literal_if_possible || + inferred != inferred.downcast_to_literal_if_possible + return with_new_types(inferred.downcast_to_literal_if_possible, + expected.downcast_to_literal_if_possible).conforms_to_unique_type? + end - if rules.include? :allow_subtype_skew + if rules.include?(:allow_subtype_skew) && !expected.parameters.empty? # parameters are not considered in this case - expected = expected.erase_parameters + return with_new_types(inferred, expected.erase_parameters).conforms_to_unique_type? end - inferred = inferred.erase_parameters if !expected.parameters? && inferred.parameters? + if !expected.parameters? && inferred.parameters? + return with_new_types(inferred.erase_parameters, expected).conforms_to_unique_type? + end if expected.parameters? && !inferred.parameters? && rules.include?(:allow_empty_params) - expected = expected.erase_parameters + return with_new_types(inferred, expected.erase_parameters).conforms_to_unique_type? end return true if inferred == expected @@ -127,6 +129,13 @@ def conforms_to_unique_type? private + # @return [self] + # @param inferred [ComplexType::UniqueType] + # @param expected [ComplexType::UniqueType] + def with_new_types inferred, expected + self.class.new(api_map, inferred, expected, situation, rules, variance: variance) + end + attr_reader :api_map, :inferred, :expected, :situation, :rules, :variance end end diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 9327d3fc8..b12ca6332 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -113,6 +113,10 @@ def simplify_literals end end + def simplifyable_literal? + literal? && name != 'nil' + end + def literal? non_literal_name != name end From bc71924f40e706990a2ad90973e13eba78c35342 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 13:32:26 -0400 Subject: [PATCH 063/460] Coverage fixes --- lib/solargraph/complex_type/conformance.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/solargraph/complex_type/conformance.rb b/lib/solargraph/complex_type/conformance.rb index a055c297c..8d4b9ec3b 100644 --- a/lib/solargraph/complex_type/conformance.rb +++ b/lib/solargraph/complex_type/conformance.rb @@ -20,11 +20,14 @@ def initialize api_map, inferred, expected, @situation = situation @rules = rules @variance = variance + # :nocov: unless expected.is_a?(UniqueType) raise "Expected type must be a UniqueType, got #{expected.class} in #{expected.inspect}" end return if inferred.is_a?(UniqueType) + # :nocov: raise "Inferred type must be a UniqueType, got #{inferred.class} in #{inferred.inspect}" + # :nocov: end def conforms_to_unique_type? From 18bbe8a81045138d619d5abda8c4f7ec1477cb9c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 13:33:27 -0400 Subject: [PATCH 064/460] Coverage fixes --- lib/solargraph/complex_type/conformance.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solargraph/complex_type/conformance.rb b/lib/solargraph/complex_type/conformance.rb index 8d4b9ec3b..888d53ec2 100644 --- a/lib/solargraph/complex_type/conformance.rb +++ b/lib/solargraph/complex_type/conformance.rb @@ -31,9 +31,11 @@ def initialize api_map, inferred, expected, end def conforms_to_unique_type? + # :nocov: unless expected.is_a?(UniqueType) raise "Expected type must be a UniqueType, got #{expected.class} in #{expected.inspect}" end + # :nocov: if inferred.simplifyable_literal? && !expected.literal? return with_new_types(inferred.simplify_literals, expected).conforms_to_unique_type? end From dc50122896e1923242487964b4e3b07ec08903a5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 15:04:28 -0400 Subject: [PATCH 065/460] Refactor --- lib/solargraph/complex_type/conformance.rb | 122 +++++++++++++-------- 1 file changed, 78 insertions(+), 44 deletions(-) diff --git a/lib/solargraph/complex_type/conformance.rb b/lib/solargraph/complex_type/conformance.rb index 888d53ec2..6a3d128a7 100644 --- a/lib/solargraph/complex_type/conformance.rb +++ b/lib/solargraph/complex_type/conformance.rb @@ -9,7 +9,8 @@ class Conformance # @param expected [ComplexType::UniqueType] # @param situation [:method_call, :return_type] # @param rules [Array<:allow_subtype_skew, :allow_empty_params, :allow_reverse_match, - # :allow_any_match, :allow_undefined, :allow_unresolved_generic, :allow_unmatched_interface>] + # :allow_any_match, :allow_undefined, :allow_unresolved_generic, + # :allow_unmatched_interface>] # @param variance [:invariant, :covariant, :contravariant] def initialize api_map, inferred, expected, situation = :method_call, rules = [], @@ -33,56 +34,92 @@ def initialize api_map, inferred, expected, def conforms_to_unique_type? # :nocov: unless expected.is_a?(UniqueType) + # :nocov: raise "Expected type must be a UniqueType, got #{expected.class} in #{expected.inspect}" + # :nocov: end - # :nocov: - if inferred.simplifyable_literal? && !expected.literal? + + if use_simplified_inferred_type? return with_new_types(inferred.simplify_literals, expected).conforms_to_unique_type? end - return true if expected.any?(&:interface?) && rules.include?(:allow_unmatched_interface) - return true if inferred.interface? && rules.include?(:allow_unmatched_interface) - - if rules.include? :allow_reverse_match - reversed_match = expected.conforms_to?(api_map, inferred, situation, - rules - [:allow_reverse_match], - variance: variance) - return true if reversed_match - end - if expected != expected.downcast_to_literal_if_possible || - inferred != inferred.downcast_to_literal_if_possible - return with_new_types(inferred.downcast_to_literal_if_possible, - expected.downcast_to_literal_if_possible).conforms_to_unique_type? + return true if ignore_interface? + return true if conforms_via_reverse_match? + + downcast_inferred = inferred.downcast_to_literal_if_possible + downcast_expected = expected.downcast_to_literal_if_possible + if (downcast_inferred.name != inferred.name) || (downcast_expected.name != expected.name) + return with_new_types(downcast_inferred, downcast_expected).conforms_to_unique_type? end - if rules.include?(:allow_subtype_skew) && !expected.parameters.empty? + if rules.include?(:allow_subtype_skew) && !expected.all_params.empty? # parameters are not considered in this case return with_new_types(inferred, expected.erase_parameters).conforms_to_unique_type? end - if !expected.parameters? && inferred.parameters? - return with_new_types(inferred.erase_parameters, expected).conforms_to_unique_type? - end + return with_new_types(inferred.erase_parameters, expected).conforms_to_unique_type? if only_inferred_parameters? - if expected.parameters? && !inferred.parameters? && rules.include?(:allow_empty_params) - return with_new_types(inferred, expected.erase_parameters).conforms_to_unique_type? - end + return conforms_via_stripped_expected_parameters? if can_strip_expected_parameters? return true if inferred == expected + return false unless erased_type_conforms? + + return true if inferred.all_params.empty? && rules.include?(:allow_empty_params) + + # at this point we know the erased type is fine - time to look at parameters + + # there's an implicit 'any' on the expectation parameters + # if there are none specified + return true if expected.all_params.empty? + + return false unless key_types_conform? + + subtypes_conform? + end + + private + + def use_simplified_inferred_type? + inferred.simplifyable_literal? && !expected.literal? + end + + def only_inferred_parameters? + !expected.parameters? && inferred.parameters? + end + + def conforms_via_stripped_expected_parameters? + with_new_types(inferred, expected.erase_parameters).conforms_to_unique_type? + end + + def ignore_interface? + (expected.any?(&:interface?) && rules.include?(:allow_unmatched_interface)) || + (inferred.interface? && rules.include?(:allow_unmatched_interface)) + end + + def can_strip_expected_parameters? + expected.parameters? && !inferred.parameters? && rules.include?(:allow_empty_params) + end + + def conforms_via_reverse_match? + return false unless rules.include? :allow_reverse_match + + expected.conforms_to?(api_map, inferred, situation, + rules - [:allow_reverse_match], + variance: variance) + end + + def erased_type_conforms? case variance when :invariant return false unless inferred.name == expected.name when :covariant # covariant: we can pass in a more specific type - # we contain the expected mix-in, or we have a more specific type return false unless api_map.type_include?(inferred.name, expected.name) || api_map.super_and_sub?(expected.name, inferred.name) || inferred.name == expected.name - when :contravariant # contravariant: we can pass in a more general type - # we contain the expected mix-in, or we have a more general type return false unless api_map.type_include?(inferred.name, expected.name) || api_map.super_and_sub?(inferred.name, expected.name) || @@ -92,27 +129,26 @@ def conforms_to_unique_type? raise "Unknown variance: #{variance.inspect}" # :nocov: end + true + end - return true if inferred.all_params.empty? && rules.include?(:allow_empty_params) - - # at this point we know the erased type is fine - time to look at parameters - - # there's an implicit 'any' on the expectation parameters - # if there are none specified - return true if expected.all_params.empty? + def key_types_conform? + return true if expected.key_types.empty? - unless expected.key_types.empty? - return false if inferred.key_types.empty? + return false if inferred.key_types.empty? - unless ComplexType.new(inferred.key_types).conforms_to?(api_map, - ComplexType.new(expected.key_types), - situation, - rules, - variance: inferred.parameter_variance(situation)) - return false - end + unless ComplexType.new(inferred.key_types).conforms_to?(api_map, + ComplexType.new(expected.key_types), + situation, + rules, + variance: inferred.parameter_variance(situation)) + return false end + true + end + + def subtypes_conform? return true if expected.subtypes.empty? return true if expected.subtypes.any?(&:undefined?) && rules.include?(:allow_undefined) @@ -132,8 +168,6 @@ def conforms_to_unique_type? variance: inferred.parameter_variance(situation)) end - private - # @return [self] # @param inferred [ComplexType::UniqueType] # @param expected [ComplexType::UniqueType] From 7fcd43a9d705cf0e30b4c590575ec29a6f2c4ff5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 15:36:15 -0400 Subject: [PATCH 066/460] Fix nocov markers --- lib/solargraph/complex_type/conformance.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/complex_type/conformance.rb b/lib/solargraph/complex_type/conformance.rb index 6a3d128a7..424413038 100644 --- a/lib/solargraph/complex_type/conformance.rb +++ b/lib/solargraph/complex_type/conformance.rb @@ -25,6 +25,7 @@ def initialize api_map, inferred, expected, unless expected.is_a?(UniqueType) raise "Expected type must be a UniqueType, got #{expected.class} in #{expected.inspect}" end + # :nocov: return if inferred.is_a?(UniqueType) # :nocov: raise "Inferred type must be a UniqueType, got #{inferred.class} in #{inferred.inspect}" @@ -32,7 +33,6 @@ def initialize api_map, inferred, expected, end def conforms_to_unique_type? - # :nocov: unless expected.is_a?(UniqueType) # :nocov: raise "Expected type must be a UniqueType, got #{expected.class} in #{expected.inspect}" From bb77106490afd84e82e5660ec8067f12473f65a8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 16:52:23 -0400 Subject: [PATCH 067/460] Add another spec, fix behavior --- lib/solargraph/complex_type/unique_type.rb | 4 ++-- spec/complex_type/conforms_to_spec.rb | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index b12ca6332..c009a08d9 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -208,8 +208,8 @@ def erased_version_of?(other) # @param situation [:method_call, :assignment, :return] # @param rules [Array<:allow_subtype_skew, :allow_empty_params, :allow_reverse_match, :allow_any_match, :allow_undefined, :allow_unresolved_generic>] # @param variance [:invariant, :covariant, :contravariant] - def conforms_to?(api_map, expected, situation, rules, - variance:) + def conforms_to?(api_map, expected, situation, rules = [], + variance: erased_variance(situation)) return true if undefined? && rules.include?(:allow_undefined) # @todo teach this to validate duck types as inferred type diff --git a/spec/complex_type/conforms_to_spec.rb b/spec/complex_type/conforms_to_spec.rb index bacf19b5e..c1bba05dc 100644 --- a/spec/complex_type/conforms_to_spec.rb +++ b/spec/complex_type/conforms_to_spec.rb @@ -56,6 +56,12 @@ class Sub < Sup; end sup = described_class.parse('Sup') sub = described_class.parse('Sub') match = sub.conforms_to?(api_map, sup, :method_call) + end + + it 'handles singleton types compared against their literals' do + exp = Solargraph::ComplexType::UniqueType.new('nil', rooted: true) + inf = Solargraph::ComplexType::UniqueType.new('NilClass', rooted: true) + match = inf.conforms_to?(api_map, exp, :method_call) expect(match).to be(true) end From ed69326afb742f37e78ce475ed38b40f93ece405 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 16:59:12 -0400 Subject: [PATCH 068/460] Fix merge issue --- spec/complex_type/conforms_to_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/complex_type/conforms_to_spec.rb b/spec/complex_type/conforms_to_spec.rb index c1bba05dc..4ac00e70a 100644 --- a/spec/complex_type/conforms_to_spec.rb +++ b/spec/complex_type/conforms_to_spec.rb @@ -56,6 +56,7 @@ class Sub < Sup; end sup = described_class.parse('Sup') sub = described_class.parse('Sub') match = sub.conforms_to?(api_map, sup, :method_call) + expect(match).to be(true) end it 'handles singleton types compared against their literals' do From 1e7c972f314003184ac0a43828986f27fcc4aa32 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 4 Aug 2025 17:13:30 -0400 Subject: [PATCH 069/460] More specs --- spec/complex_type/conforms_to_spec.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/spec/complex_type/conforms_to_spec.rb b/spec/complex_type/conforms_to_spec.rb index 4ac00e70a..f8a623bf0 100644 --- a/spec/complex_type/conforms_to_spec.rb +++ b/spec/complex_type/conforms_to_spec.rb @@ -26,6 +26,27 @@ expect(match).to be(true) end + it 'allows covariant behavior in parameters of Array' do + exp = described_class.parse('Array') + inf = described_class.parse('Array') + match = inf.conforms_to?(api_map, exp, :method_call) + expect(match).to be(true) + end + + it 'does not allow contravariant behavior in parameters of Array' do + exp = described_class.parse('Array') + inf = described_class.parse('Array') + match = inf.conforms_to?(api_map, exp, :method_call) + expect(match).to be(false) + end + + it 'allows covariant behavior in key types of Hash' do + exp = described_class.parse('Hash{Object => String}') + inf = described_class.parse('Hash{Integer => String}') + match = inf.conforms_to?(api_map, exp, :method_call) + expect(match).to be(true) + end + it 'accepts valid tuple conformance' do exp = described_class.parse('Array(Integer, Integer)') inf = described_class.parse('Array(Integer, Integer)') From 8ad7bddc5d0b17aa128e4f9b0a70b387cbbc7d38 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 12:27:17 -0400 Subject: [PATCH 070/460] Linting fixes --- .rubocop.yml | 2 + .rubocop_todo.yml | 114 ------------------------------- lib/solargraph/type_checker.rb | 2 +- spec/rbs_map/conversions_spec.rb | 13 ++-- 4 files changed, 7 insertions(+), 124 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index a73324db2..29a840b9f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -34,6 +34,8 @@ Metrics/ParameterLists: Max: 7 CountKeywordArgs: false +RSpec/SpecFilePathFormat: + Enabled: false # we tend to use @@ and the risk doesn't seem high Style/ClassVars: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d8283c5c6..ef5838eeb 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -126,13 +126,6 @@ Layout/EmptyLines: - 'spec/pin/symbol_spec.rb' - 'spec/type_checker/levels/strict_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only -Layout/EmptyLinesAroundClassBody: - Exclude: - - 'lib/solargraph/rbs_map/core_map.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines @@ -523,7 +516,6 @@ Lint/DuplicateMethods: - 'lib/solargraph/pin/base.rb' - 'lib/solargraph/pin/signature.rb' - 'lib/solargraph/rbs_map.rb' - - 'lib/solargraph/rbs_map/core_map.rb' - 'lib/solargraph/source/chain/link.rb' # Configuration parameters: AllowComments, AllowEmptyLambdas. @@ -1280,103 +1272,6 @@ RSpec/ScatteredLet: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata. -# Include: **/*_spec.rb -RSpec/SpecFilePathFormat: - Exclude: - - '**/spec/routing/**/*' - - 'spec/api_map/cache_spec.rb' - - 'spec/api_map/config_spec.rb' - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/api_map/store_spec.rb' - - 'spec/api_map_spec.rb' - - 'spec/convention/activesupport_concern_spec.rb' - - 'spec/convention/struct_definition_spec.rb' - - 'spec/convention_spec.rb' - - 'spec/diagnostics/base_spec.rb' - - 'spec/diagnostics/require_not_found_spec.rb' - - 'spec/diagnostics/rubocop_helpers_spec.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/diagnostics/type_check_spec.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/diagnostics_spec.rb' - - 'spec/doc_map_spec.rb' - - 'spec/gem_pins_spec.rb' - - 'spec/language_server/host/diagnoser_spec.rb' - - 'spec/language_server/host/dispatch_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/message/text_document/formatting_spec.rb' - - 'spec/language_server/message/text_document/hover_spec.rb' - - 'spec/language_server/message/text_document/rename_spec.rb' - - 'spec/language_server/message/text_document/type_definition_spec.rb' - - 'spec/language_server/message/workspace/did_change_configuration_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/message_spec.rb' - - 'spec/language_server/transport/adapter_spec.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - - 'spec/language_server/uri_helpers_spec.rb' - - 'spec/library_spec.rb' - - 'spec/logging_spec.rb' - - 'spec/parser/flow_sensitive_typing_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/parser/node_processor_spec.rb' - - 'spec/parser_spec.rb' - - 'spec/pin/base_spec.rb' - - 'spec/pin/base_variable_spec.rb' - - 'spec/pin/block_spec.rb' - - 'spec/pin/constant_spec.rb' - - 'spec/pin/delegated_method_spec.rb' - - 'spec/pin/documenting_spec.rb' - - 'spec/pin/instance_variable_spec.rb' - - 'spec/pin/keyword_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/pin/namespace_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/pin/search_spec.rb' - - 'spec/pin/symbol_spec.rb' - - 'spec/position_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/rbs_map_spec.rb' - - 'spec/shell_spec.rb' - - 'spec/source/chain/array_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/chain/constant_spec.rb' - - 'spec/source/chain/global_variable_spec.rb' - - 'spec/source/chain/head_spec.rb' - - 'spec/source/chain/instance_variable_spec.rb' - - 'spec/source/chain/link_spec.rb' - - 'spec/source/chain/literal_spec.rb' - - 'spec/source/chain/z_super_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source/change_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source/updater_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' - - 'spec/type_checker/checks_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker/levels/strong_spec.rb' - - 'spec/type_checker/levels/typed_spec.rb' - - 'spec/type_checker/rules_spec.rb' - - 'spec/type_checker_spec.rb' - - 'spec/workspace/config_spec.rb' - - 'spec/workspace_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' - - 'spec/yard_map/mapper_spec.rb' - RSpec/StubbedMock: Exclude: - 'spec/language_server/host/message_worker_spec.rb' @@ -1900,7 +1795,6 @@ Style/GuardClause: - 'lib/solargraph/api_map.rb' - 'lib/solargraph/library.rb' - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin_cache.rb' - 'lib/solargraph/range.rb' - 'lib/solargraph/rbs_map/conversions.rb' - 'lib/solargraph/source.rb' @@ -2135,13 +2029,6 @@ Style/NegatedIfElseCondition: - 'lib/solargraph/shell.rb' - 'lib/solargraph/type_checker.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedMethods. -# AllowedMethods: be, be_a, be_an, be_between, be_falsey, be_kind_of, be_instance_of, be_truthy, be_within, eq, eql, end_with, include, match, raise_error, respond_to, start_with -Style/NestedParenthesizedCalls: - Exclude: - - 'lib/solargraph/type_checker.rb' - # This cop supports safe autocorrection (--autocorrect). Style/NestedTernaryOperator: Exclude: @@ -2237,7 +2124,6 @@ Style/RedundantBegin: - 'lib/solargraph/shell.rb' - 'lib/solargraph/source/cursor.rb' - 'lib/solargraph/source/encoding_fixes.rb' - - 'lib/solargraph/type_checker.rb' - 'lib/solargraph/workspace.rb' # This cop supports safe autocorrection (--autocorrect). diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index ff89a74ce..cd37d4d8f 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -523,7 +523,7 @@ def add_to_param_details(param_details, param_names, new_param_details) end # @param signature [Pin::Signature] - # @param pins [Array] + # @param method_pin_stack [Array] # @return [Hash{String => Hash{Symbol => String, ComplexType}}] def param_details_from_stack(signature, method_pin_stack) signature_type = signature.typify(api_map) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index bb57dfd0b..6689bfdca 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -54,21 +54,16 @@ def bar: () -> untyped if Gem::Version.new(RBS::VERSION) >= Gem::Version.new('3.9.1') context 'with method pin for Open3.capture2e' do - let(:api_map) { Solargraph::ApiMap.load_with_cache('.') } + it 'accepts chdir kwarg' do + api_map = Solargraph::ApiMap.load_with_cache('.') - let(:method_pin) do - api_map.pins.find do |pin| + method_pin = api_map.pins.find do |pin| pin.is_a?(Solargraph::Pin::Method) && pin.path == 'Open3.capture2e' end - end - let(:chdir_param) do - method_pin&.signatures&.flat_map(&:parameters)&.find do |param| # rubocop:disable Style/SafeNavigationChainLength + chdir_param = method_pin&.signatures&.flat_map(&:parameters)&.find do |param| # rubocop:disable Style/SafeNavigationChainLength param.name == 'chdir' end - end - - it 'accepts chdir kwarg' do expect(chdir_param).not_to be_nil, -> { "Found pin #{method_pin.to_rbs} from #{method_pin.type_location}" } end end From af232049e380cd8ce1eaa22cce1f7a43a2ca64bf Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 12:30:09 -0400 Subject: [PATCH 071/460] Fix spec --- spec/rbs_map/conversions_spec.rb | 72 ++++++++++++++++---------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 6689bfdca..8c80070fd 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -1,61 +1,63 @@ describe Solargraph::RbsMap::Conversions do - # create a temporary directory with the scope of the spec - around do |example| - require 'tmpdir' - Dir.mktmpdir("rspec-solargraph-") do |dir| - @temp_dir = dir - example.run + context 'with RBS to digest' do + # create a temporary directory with the scope of the spec + around do |example| + require 'tmpdir' + Dir.mktmpdir("rspec-solargraph-") do |dir| + @temp_dir = dir + example.run + end end - end - let(:rbs_repo) do - RBS::Repository.new(no_stdlib: false) - end + let(:rbs_repo) do + RBS::Repository.new(no_stdlib: false) + end - let(:loader) do - RBS::EnvironmentLoader.new(core_root: nil, repository: rbs_repo) - end + let(:loader) do + RBS::EnvironmentLoader.new(core_root: nil, repository: rbs_repo) + end - let(:conversions) do - Solargraph::RbsMap::Conversions.new(loader: loader) - end + let(:conversions) do + Solargraph::RbsMap::Conversions.new(loader: loader) + end - let(:pins) do - conversions.pins - end + let(:pins) do + conversions.pins + end - before do - rbs_file = File.join(temp_dir, 'foo.rbs') - File.write(rbs_file, rbs) - loader.add(path: Pathname(temp_dir)) - end + before do + rbs_file = File.join(temp_dir, 'foo.rbs') + File.write(rbs_file, rbs) + loader.add(path: Pathname(temp_dir)) + end - attr_reader :temp_dir + attr_reader :temp_dir - context 'with untyped response' do - let(:rbs) do - <<~RBS + context 'with untyped response' do + let(:rbs) do + <<~RBS class Foo def bar: () -> untyped end RBS - end + end - subject(:method_pin) { pins.find { |pin| pin.path == 'Foo#bar' } } + subject(:method_pin) { pins.find { |pin| pin.path == 'Foo#bar' } } - it { should_not be_nil } + it { should_not be_nil } - it { should be_a(Solargraph::Pin::Method) } + it { should be_a(Solargraph::Pin::Method) } - it 'maps untyped in RBS to undefined in Solargraph 'do - expect(method_pin.return_type.tag).to eq('undefined') + it 'maps untyped in RBS to undefined in Solargraph 'do + expect(method_pin.return_type.tag).to eq('undefined') + end end end if Gem::Version.new(RBS::VERSION) >= Gem::Version.new('3.9.1') context 'with method pin for Open3.capture2e' do it 'accepts chdir kwarg' do - api_map = Solargraph::ApiMap.load_with_cache('.') + api_map = Solargraph::ApiMap.load_with_cache('.', $stdout) method_pin = api_map.pins.find do |pin| pin.is_a?(Solargraph::Pin::Method) && pin.path == 'Open3.capture2e' From 425c35995ba43e2d138b9cdd01eaa71d2a80ad6b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 12:42:13 -0400 Subject: [PATCH 072/460] Fix merge issues --- lib/solargraph/parser/parser_gem/node_processors/send_node.rb | 2 -- lib/solargraph/pin/local_variable.rb | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index 8490fd9b0..bc03b9293 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -8,7 +8,6 @@ class SendNode < Parser::NodeProcessor::Base include ParserGem::NodeMethods def process - # @sg-ignore # @type [Symbol] method_name = node.children[1] # :nocov: @@ -54,7 +53,6 @@ def process def process_visibility if (node.children.length > 2) node.children[2..-1].each do |child| - # @sg-ignore # @type [Symbol] visibility = node.children[1] # :nocov: diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 36b75773c..78e0b287d 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -26,6 +26,9 @@ def combine_with(other, attrs={}) assignment: assert_same(other, :assignment), presence_certain: assert_same(other, :presence_certain?), }.merge(attrs) + # @sg-ignore Wrong argument type for + # Solargraph::Pin::Base#assert_same: other expected + # Solargraph::Pin::Base, received self new_attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence) super(other, new_attrs) From 6383ad17be5fc5b485a7d97fa2dedf2c0e6425d1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 14:12:12 -0400 Subject: [PATCH 073/460] Ratchet Rubocop todo --- .rubocop_todo.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ef5838eeb..a1aba676c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -71,7 +71,6 @@ Layout/BlockAlignment: Layout/ClosingHeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment. @@ -229,7 +228,6 @@ Layout/HashAlignment: Layout/HeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - 'spec/yard_map/mapper/to_method_spec.rb' # This cop supports safe autocorrection (--autocorrect). From 356d44c705fbabe789286f8aebcbdec85925a4f5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 14:20:21 -0400 Subject: [PATCH 074/460] More merge fixes --- .rubocop_todo.yml | 10 ---------- .../parser/parser_gem/node_processors/send_node.rb | 2 ++ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d8283c5c6..26eb39f0b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -178,7 +178,6 @@ Layout/ExtraSpacing: Exclude: - 'lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb' - 'lib/solargraph/pin/closure.rb' - - 'lib/solargraph/pin/local_variable.rb' - 'lib/solargraph/rbs_map/conversions.rb' - 'lib/solargraph/type_checker.rb' - 'spec/spec_helper.rb' @@ -340,7 +339,6 @@ Layout/SpaceAroundOperators: Exclude: - 'lib/solargraph/library.rb' - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin/local_variable.rb' - 'lib/solargraph/source.rb' - 'lib/solargraph/source/change.rb' - 'lib/solargraph/source/cursor.rb' @@ -2135,13 +2133,6 @@ Style/NegatedIfElseCondition: - 'lib/solargraph/shell.rb' - 'lib/solargraph/type_checker.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedMethods. -# AllowedMethods: be, be_a, be_an, be_between, be_falsey, be_kind_of, be_instance_of, be_truthy, be_within, eq, eql, end_with, include, match, raise_error, respond_to, start_with -Style/NestedParenthesizedCalls: - Exclude: - - 'lib/solargraph/type_checker.rb' - # This cop supports safe autocorrection (--autocorrect). Style/NestedTernaryOperator: Exclude: @@ -2237,7 +2228,6 @@ Style/RedundantBegin: - 'lib/solargraph/shell.rb' - 'lib/solargraph/source/cursor.rb' - 'lib/solargraph/source/encoding_fixes.rb' - - 'lib/solargraph/type_checker.rb' - 'lib/solargraph/workspace.rb' # This cop supports safe autocorrection (--autocorrect). diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index bc03b9293..48184b52b 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -8,6 +8,7 @@ class SendNode < Parser::NodeProcessor::Base include ParserGem::NodeMethods def process + # @sg-ignore Variable type could not be inferred for method_name # @type [Symbol] method_name = node.children[1] # :nocov: @@ -53,6 +54,7 @@ def process def process_visibility if (node.children.length > 2) node.children[2..-1].each do |child| + # @sg-ignore Variable type could not be inferred for method_name # @type [Symbol] visibility = node.children[1] # :nocov: From 370981bc8911e6b7e546fe0ef648a482d9e947d8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 14:31:12 -0400 Subject: [PATCH 075/460] Add @sg-ignores --- lib/solargraph/parser/parser_gem/node_processors/send_node.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index 48184b52b..cedec1b89 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -36,12 +36,15 @@ def process process_autoload elsif method_name == :private_constant process_private_constant + # @sg-ignore elsif method_name == :alias_method && node.children[2] && node.children[2] && node.children[2].type == :sym && node.children[3] && node.children[3].type == :sym process_alias_method + # @sg-ignore elsif method_name == :private_class_method && node.children[2].is_a?(AST::Node) # Processing a private class can potentially handle children on its own return if process_private_class_method end + # @sg-ignore elsif method_name == :require && node.children[0].to_s == '(const nil :Bundler)' pins.push Pin::Reference::Require.new(Solargraph::Location.new(region.filename, Solargraph::Range.from_to(0, 0, 0, 0)), 'bundler/require', source: :parser) end From f5e17c3dc4b47ac2035c8e10325302aeb5f0394b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 14:42:09 -0400 Subject: [PATCH 076/460] Revert harder-to-test things for now and catch up with them in another PR --- lib/solargraph/pin_cache.rb | 10 ++++------ lib/solargraph/shell.rb | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index dbfb9de3f..e0f14f59e 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -164,16 +164,14 @@ def has_rbs_collection?(gemspec, hash) exist?(rbs_collection_path(gemspec, hash)) end - # @param out [IO, nil] # @return [void] - def uncache_core(out: nil) - uncache(core_path, out: out) + def uncache_core + uncache(core_path) end - # @param out [IO, nil] # @return [void] - def uncache_stdlib(out: nil) - uncache(stdlib_path, out: out) + def uncache_stdlib + uncache(stdlib_path) end # @param gemspec [Gem::Specification] diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index d6437fede..afea86a92 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -118,12 +118,12 @@ def uncache *gems raise ArgumentError, 'No gems specified.' if gems.empty? gems.each do |gem| if gem == 'core' - PinCache.uncache_core(out: $stdout) + PinCache.uncache_core next end if gem == 'stdlib' - PinCache.uncache_stdlib(out: $stdout) + PinCache.uncache_stdlib next end From 88a7d1f993ac3c74bfb928ea854b29cc4077700d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 15:56:27 -0400 Subject: [PATCH 077/460] Revert change --- lib/solargraph/type_checker/param_def.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/solargraph/type_checker/param_def.rb b/lib/solargraph/type_checker/param_def.rb index a60448c98..2c626270a 100644 --- a/lib/solargraph/type_checker/param_def.rb +++ b/lib/solargraph/type_checker/param_def.rb @@ -12,8 +12,6 @@ class ParamDef # @return [Symbol] attr_reader :type - # @param name [String] - # @param type [Symbol] def initialize name, type @name = name @type = type From 10eca96027d7b57284620bfbf99931e7227f1b90 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 16:19:02 -0400 Subject: [PATCH 078/460] Revert hard to test change --- .rubocop_todo.yml | 1 + lib/solargraph/pin_cache.rb | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a1aba676c..609233843 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1793,6 +1793,7 @@ Style/GuardClause: - 'lib/solargraph/api_map.rb' - 'lib/solargraph/library.rb' - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' + - 'lib/solargraph/pin_cache.rb' - 'lib/solargraph/range.rb' - 'lib/solargraph/rbs_map/conversions.rb' - 'lib/solargraph/source.rb' diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index e0f14f59e..2a0ec4639 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -225,8 +225,6 @@ def uncache *path_segments, out: nil if File.exist?(path) FileUtils.rm_rf path, secure: true out.puts "Clearing pin cache in #{path}" unless out.nil? - else - out.puts "Pin cache file #{path} does not exist" unless out.nil? end end From e4afaada19c69de9dfc574ae3bcfc66880a44be9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 08:21:34 -0400 Subject: [PATCH 079/460] Allow newer RBS gem versions, exclude incompatible ones (#995) * Allow newer RBS gem versions This allow users to upgrade to recent Tapioca versions. Tapioca now requires newish versions of the spoom gem, which depends on 4.x pre-releases of the rbs gem. * Add RBS version to test matrix * Add exclude rule * Move to 3.6.1 --- .github/workflows/rspec.yml | 10 +++++++++- solargraph.gemspec | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 94ef5771c..35f7a1d13 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -22,7 +22,13 @@ jobs: strategy: matrix: ruby-version: ['3.0', '3.1', '3.2', '3.3', '3.4', 'head'] - + rbs-version: ['3.6.1', '3.9.4', '4.0.0.dev.4'] + # Ruby 3.0 doesn't work with RBS 3.9.4 or 4.0.0.dev.4 + exclude: + - ruby-version: '3.0' + rbs-version: '3.9.4' + - ruby-version: '3.0' + rbs-version: '4.0.0.dev.4' steps: - uses: actions/checkout@v3 - name: Set up Ruby @@ -30,6 +36,8 @@ jobs: with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: false + - name: Set rbs version + run: echo "gem 'rbs', '${{ matrix.rbs-version }}'" >> .Gemfile # /home/runner/.rubies/ruby-head/lib/ruby/gems/3.5.0+2/gems/rbs-3.9.4/lib/rbs.rb:11: # warning: tsort was loaded from the standard library, # but will no longer be part of the default gems diff --git a/solargraph.gemspec b/solargraph.gemspec index e4d30c537..e6bb9394a 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -35,7 +35,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'ostruct', '~> 0.6' s.add_runtime_dependency 'parser', '~> 3.0' s.add_runtime_dependency 'prism', '~> 1.4' - s.add_runtime_dependency 'rbs', '~> 3.6.1' + s.add_runtime_dependency 'rbs', ['>= 3.6.1', '<= 4.0.0.dev.4'] s.add_runtime_dependency 'reverse_markdown', '~> 3.0' s.add_runtime_dependency 'rubocop', '~> 1.76' s.add_runtime_dependency 'thor', '~> 1.0' From a0a78785c23a2fa2f65865192fab961ee8a9a751 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 08:55:48 -0400 Subject: [PATCH 080/460] Look for external requires before cataloging bench (#1021) * Look for external requires before cataloging bench It seems like sync_catalog will go through the motions but not actually load pins from gems here due to passing an empty requires array to ApiMap. I'm sure those requires get pulled in eventually, but we go through at least one catalog cycle without it happening. Found while trying to test a different issue but not being able to get completions from a gem in a spec. * Ensure backport is pre-cached --- lib/solargraph/library.rb | 2 +- spec/library_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 0316a01b0..9eb171879 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -680,8 +680,8 @@ def sync_catalog mutex.synchronize do logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}" - api_map.catalog bench source_map_hash.values.each { |map| find_external_requires(map) } + api_map.catalog bench logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)" logger.info "#{api_map.uncached_yard_gemspecs.length} uncached YARD gemspecs" logger.info "#{api_map.uncached_rbs_collection_gemspecs.length} uncached RBS collection gemspecs" diff --git a/spec/library_spec.rb b/spec/library_spec.rb index bd7cc25a0..bea0f2983 100644 --- a/spec/library_spec.rb +++ b/spec/library_spec.rb @@ -26,6 +26,28 @@ expect(completion.pins.map(&:name)).to include('x') end + context 'with a require from an already-cached external gem' do + before do + Solargraph::Shell.new.gems('backport') + end + + it "returns a Completion" do + library = Solargraph::Library.new(Solargraph::Workspace.new(Dir.pwd, + Solargraph::Workspace::Config.new)) + library.attach Solargraph::Source.load_string(%( + require 'backport' + + # @param adapter [Backport::Adapter] + def foo(adapter) + adapter.remo + end + ), 'file.rb', 0) + completion = library.completions_at('file.rb', 5, 19) + expect(completion).to be_a(Solargraph::SourceMap::Completion) + expect(completion.pins.map(&:name)).to include('remote') + end + end + it "gets definitions from a file" do library = Solargraph::Library.new src = Solargraph::Source.load_string %( From 0e86b883ee16ac764ed41f24e424a85954137736 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Tue, 19 Aug 2025 10:01:27 -0400 Subject: [PATCH 081/460] Remove Library#folding_ranges (#904) --- lib/solargraph/library.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 9eb171879..72224f672 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -437,17 +437,6 @@ def bench ) end - # Get an array of foldable ranges for the specified file. - # - # @deprecated The library should not need to handle folding ranges. The - # source itself has all the information it needs. - # - # @param filename [String] - # @return [Array] - def folding_ranges filename - read(filename).folding_ranges - end - # Create a library from a directory. # # @param directory [String] The path to be used for the workspace From c90f01684972682abfbd21da124f3d6c7b37f9a1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 10:29:27 -0400 Subject: [PATCH 082/460] Complain in strong type-checking if an @sg-ignore line is not needed (#1011) * Complain in strong type-checking if an @sg-ignore line is not needed * Fix return type * Fix spec * Linting/coverage fixes * Coverage fix --- lib/solargraph/api_map.rb | 3 - lib/solargraph/complex_type.rb | 1 - lib/solargraph/complex_type/unique_type.rb | 4 -- lib/solargraph/parser/comment_ripper.rb | 2 +- .../parser/flow_sensitive_typing.rb | 2 - .../parser/parser_gem/class_methods.rb | 2 +- lib/solargraph/pin/base.rb | 2 - lib/solargraph/pin/base_variable.rb | 1 - lib/solargraph/pin/method.rb | 1 - lib/solargraph/source.rb | 3 +- lib/solargraph/source/cursor.rb | 1 - lib/solargraph/type_checker.rb | 64 +++++++++++++++---- lib/solargraph/type_checker/rules.rb | 8 +++ lib/solargraph/workspace/config.rb | 2 - spec/type_checker/levels/strong_spec.rb | 11 ++++ 15 files changed, 75 insertions(+), 32 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 9db37a166..eed02b4ef 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -211,9 +211,6 @@ class << self # any missing gems. # # - # @todo IO::NULL is incorrectly inferred to be a String. - # @sg-ignore - # # @param directory [String] # @param out [IO] The output stream for messages # @return [ApiMap] diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 9e23eb502..ac9599329 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -299,7 +299,6 @@ class << self # # @todo Need ability to use a literal true as a type below # # @param partial [Boolean] True if the string is part of a another type # # @return [Array] - # @sg-ignore # @todo To be able to select the right signature above, # Chain::Call needs to know the decl type (:arg, :optarg, # :kwarg, etc) of the arguments given, instead of just having diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 0f4ec430d..63a6ae15b 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -49,11 +49,7 @@ def self.parse name, substring = '', make_rooted: nil parameters_type = PARAMETERS_TYPE_BY_STARTING_TAG.fetch(substring[0]) if parameters_type == :hash raise ComplexTypeError, "Bad hash type: name=#{name}, substring=#{substring}" unless !subs.is_a?(ComplexType) and subs.length == 2 and !subs[0].is_a?(UniqueType) and !subs[1].is_a?(UniqueType) - # @todo should be able to resolve map; both types have it - # with same return type - # @sg-ignore key_types.concat(subs[0].map { |u| ComplexType.new([u]) }) - # @sg-ignore subtypes.concat(subs[1].map { |u| ComplexType.new([u]) }) elsif parameters_type == :list && name == 'Hash' # Treat Hash as Hash{A => B} diff --git a/lib/solargraph/parser/comment_ripper.rb b/lib/solargraph/parser/comment_ripper.rb index 62a4dacc5..e74fcb259 100644 --- a/lib/solargraph/parser/comment_ripper.rb +++ b/lib/solargraph/parser/comment_ripper.rb @@ -51,7 +51,7 @@ def on_embdoc_end *args result end - # @return [Hash{Integer => String}] + # @return [Hash{Integer => Solargraph::Parser::Snippet}] def parse @comments = {} super diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 58f149d73..308db214b 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -236,8 +236,6 @@ def type_name(node) "#{module_type_name}::#{class_node}" end - # @todo "return type could not be inferred" should not trigger here - # @sg-ignore # @param clause_node [Parser::AST::Node] def always_breaks?(clause_node) clause_node&.type == :break diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index 58ca8056b..ddc742bd8 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -17,7 +17,7 @@ module ParserGem module ClassMethods # @param code [String] # @param filename [String, nil] - # @return [Array(Parser::AST::Node, Hash{Integer => String})] + # @return [Array(Parser::AST::Node, Hash{Integer => Solargraph::Parser::Snippet})] def parse_with_comments code, filename = nil node = parse(code, filename) comments = CommentRipper.new(code, filename, 0).parse diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 6ac2cac52..fb3274dab 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -129,7 +129,6 @@ def choose_longer(other, attr) val2 = other.send(attr) return val1 if val1 == val2 return val2 if val1.nil? - # @sg-ignore val1.length > val2.length ? val1 : val2 end @@ -268,7 +267,6 @@ def assert_same_array_content(other, attr, &block) raise "Expected #{attr} on #{other} to be an Enumerable, got #{arr2.class}" unless arr2.is_a?(::Enumerable) # @type arr2 [::Enumerable] - # @sg-ignore # @type [undefined] values1 = arr1.map(&block) # @type [undefined] diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index cef3f44cb..764c1fb39 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -43,7 +43,6 @@ def return_type @return_type ||= generate_complex_type end - # @sg-ignore def nil_assignment? # this will always be false - should it be return_type == # ComplexType::NIL or somesuch? diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 6302a940a..6309cb55a 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -305,7 +305,6 @@ def typify api_map super end - # @sg-ignore def documentation if @documentation.nil? method_docs ||= super || '' diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 11ab215ed..d2b24cc61 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -30,7 +30,7 @@ def node @node end - # @return [Hash{Integer => Array}] + # @return [Hash{Integer => Solargraph::Parser::Snippet}] def comments finalize @comments @@ -235,6 +235,7 @@ def synchronized? # @return [Hash{Integer => String}] def associated_comments @associated_comments ||= begin + # @type [Hash{Integer => String}] result = {} buffer = String.new('') # @type [Integer, nil] diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 0b95bb9bd..70e4fd47a 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -35,7 +35,6 @@ def word # The part of the word before the current position. Given the text # `foo.bar`, the start_of_word at position(0, 6) is `ba`. # - # @sg-ignore Improve resolution of String#match below # @return [String] def start_of_word @start_of_word ||= begin diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index aa215f97b..e99f99195 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -38,15 +38,20 @@ def source_map @source_map ||= api_map.source_map(filename) end + # @return [Source] + def source + @source_map.source + end + # @return [Array] def problems @problems ||= begin - without_ignored( - method_tag_problems - .concat variable_type_tag_problems - .concat const_problems - .concat call_problems - ) + all = method_tag_problems + .concat(variable_type_tag_problems) + .concat(const_problems) + .concat(call_problems) + unignored = without_ignored(all) + unignored.concat(unneeded_sgignore_problems) end end @@ -140,7 +145,7 @@ def resolved_constant? pin # @param pin [Pin::Base] def virtual_pin? pin - pin.location && source_map.source.comment_at?(pin.location.range.ending) + pin.location && source.comment_at?(pin.location.range.ending) end # @param pin [Pin::Method] @@ -231,7 +236,7 @@ def all_variables def const_problems return [] unless rules.validate_consts? result = [] - Solargraph::Parser::NodeMethods.const_nodes_from(source_map.source.node).each do |const| + Solargraph::Parser::NodeMethods.const_nodes_from(source.node).each do |const| rng = Solargraph::Range.from_node(const) chain = Solargraph::Parser.chain(const, filename) block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column) @@ -249,7 +254,7 @@ def const_problems # @return [Array] def call_problems result = [] - Solargraph::Parser::NodeMethods.call_nodes_from(source_map.source.node).each do |call| + Solargraph::Parser::NodeMethods.call_nodes_from(source.node).each do |call| rng = Solargraph::Range.from_node(call) next if @marked_ranges.any? { |d| d.contain?(rng.start) } chain = Solargraph::Parser.chain(call, filename) @@ -646,7 +651,6 @@ def parameterized_arity_problems_for(pin, parameters, arguments, location) # @param parameters [Enumerable] # @todo need to use generic types in method to choose correct # signature and generate Integer as return type - # @sg-ignore # @return [Integer] def required_param_count(parameters) parameters.sum { |param| %i[arg kwarg].include?(param.decl) ? 1 : 0 } @@ -687,12 +691,48 @@ def fake_args_for(pin) args end + # @return [Set] + def sg_ignore_lines_processed + @sg_ignore_lines_processed ||= Set.new + end + + # @return [Set] + def all_sg_ignore_lines + source.associated_comments.select do |_line, text| + text.include?('@sg-ignore') + end.keys.to_set + end + + # @return [Array] + def unprocessed_sg_ignore_lines + (all_sg_ignore_lines - sg_ignore_lines_processed).to_a.sort + end + + # @return [Array] + def unneeded_sgignore_problems + return [] unless rules.validate_sg_ignores? + + unprocessed_sg_ignore_lines.map do |line| + Problem.new( + Location.new(filename, Range.from_to(line, 0, line, 0)), + 'Unneeded @sg-ignore comment' + ) + end + end + # @param problems [Array] # @return [Array] def without_ignored problems problems.reject do |problem| - node = source_map.source.node_at(problem.location.range.start.line, problem.location.range.start.column) - node && source_map.source.comments_for(node)&.include?('@sg-ignore') + node = source.node_at(problem.location.range.start.line, problem.location.range.start.column) + ignored = node && source.comments_for(node)&.include?('@sg-ignore') + unless !ignored || all_sg_ignore_lines.include?(problem.location.range.start.line) + # :nocov: + Solargraph.assert_or_log(:sg_ignore) { "@sg-ignore accounting issue - node is #{node}" } + # :nocov: + end + sg_ignore_lines_processed.add problem.location.range.start.line if ignored + ignored end end end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 0aad5ed8a..8f15037d5 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -57,6 +57,14 @@ def validate_tags? def require_all_return_types_match_inferred? rank >= LEVELS[:alpha] end + + # We keep this at strong because if you added an @sg-ignore to + # address a strong-level issue, then ran at a lower level, you'd + # get a false positive - we don't run stronger level checks than + # requested for performance reasons + def validate_sg_ignores? + rank >= LEVELS[:strong] + end end end end diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index ce45e5b11..0b2d84a01 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -90,7 +90,6 @@ def reporters # A hash of options supported by the formatter # - # @sg-ignore pending https://github.com/castwide/solargraph/pull/905 # @return [Hash] def formatter raw_data['formatter'] @@ -105,7 +104,6 @@ def plugins # The maximum number of files to parse from the workspace. # - # @sg-ignore pending https://github.com/castwide/solargraph/pull/905 # @return [Integer] def max_files raw_data['max_files'] diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 12db1e442..6fdf84e30 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -4,6 +4,17 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end + it 'reports unneeded @sg-ignore tags' do + checker = type_checker(%( + class Foo + # @sg-ignore + # @return [void] + def bar; end + end + )) + expect(checker.problems.map(&:message)).to eq(['Unneeded @sg-ignore comment']) + end + it 'reports missing return tags' do checker = type_checker(%( class Foo From 8c7a5b8195d7a689b47f89ceae1e4d84098473d9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 21 Aug 2025 08:16:33 -0400 Subject: [PATCH 083/460] Document a log level env variable (#894) * Document a log level env variable * Fix logger reference * Fix env var name * Populate location information from RBS files (#768) * Populate location information from RBS files The 'rbs' gem maps the location of different definitions to the relevant point in the RGS files themselves - this change provides the ability to jump into the right place in those files to see the type definition via the LSP. * Prefer source location in language server * Resolve merge issue * Fix Path vs String type error * Consolidate parameter handling into Pin::Callable (#844) * Consolidate parameter handling into Pin::Closure * Clarify clobbered variable names * Fix bug in to_rbs, add spec, then fix new bug found after running spec * Catch one more Signature.new to translate from strict typechecking * Introduce Pin::Callable * Introduce Pin::Callable * Introduce Pin::Callable * Introduce Pin::Callable * Introduce Pin::Callable * Introduce Pin::Callable * Introduce Pin::Callable * Use Pin::Callable type in args_node.rb * Select String#each_line overload with mandatory vs optional arg info * Adjust local variable presence to start after assignment, not before (#864) * Adjust local variable presence to start after assignment, not before * Add regression test around assignment in return position * Fix assignment visibility code, which relied on bad asgn semantics * Resolve params from ref tags (#872) * Resolve params from ref tags * Resolve ref tags with namespaces * Fix merge issue * RuboCop fixes * Add @sg-ignore * Linting * Linting fix * Linting fix --------- Co-authored-by: Fred Snyder --- README.md | 4 ++++ lib/solargraph/logging.rb | 14 ++++++++++++-- lib/solargraph/source/chain.rb | 6 +++++- spec/spec_helper.rb | 4 +++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b3cb2ef5a..3e94a60b9 100755 --- a/README.md +++ b/README.md @@ -132,6 +132,10 @@ See [https://solargraph.org/guides](https://solargraph.org/guides) for more tips ### Development +To see more logging when typechecking or running specs, set the +`SOLARGRAPH_LOG` environment variable to `debug` or `info`. `warn` is +the default value. + Code contributions are always appreciated. Feel free to fork the repo and submit pull requests. Check for open issues that could use help. Start new issues to discuss changes that have a major impact on the code or require large time commitments. ### Sponsorship and Donation diff --git a/lib/solargraph/logging.rb b/lib/solargraph/logging.rb index a26610fee..a8bc3b3ee 100644 --- a/lib/solargraph/logging.rb +++ b/lib/solargraph/logging.rb @@ -11,8 +11,18 @@ module Logging 'info' => Logger::INFO, 'debug' => Logger::DEBUG } - - @@logger = Logger.new(STDERR, level: DEFAULT_LOG_LEVEL) + configured_level = ENV.fetch('SOLARGRAPH_LOG', nil) + level = if LOG_LEVELS.keys.include?(configured_level) + LOG_LEVELS.fetch(configured_level) + else + if configured_level + warn "Invalid value for SOLARGRAPH_LOG: #{configured_level.inspect} - " \ + "valid values are #{LOG_LEVELS.keys}" + end + DEFAULT_LOG_LEVEL + end + # @sg-ignore Fix cvar issue + @@logger = Logger.new(STDERR, level: level) # @sg-ignore Fix cvar issue @@logger.formatter = proc do |severity, datetime, progname, msg| "[#{severity}] #{msg}\n" diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index 8fdeed228..065c3bf10 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -15,6 +15,7 @@ class Source # class Chain include Equality + include Logging autoload :Link, 'solargraph/source/chain/link' autoload :Call, 'solargraph/source/chain/call' @@ -75,7 +76,9 @@ def base # Determine potential Pins returned by this chain of words # - # @param api_map [ApiMap] @param name_pin [Pin::Base] A pin + # @param api_map [ApiMap] + # + # @param name_pin [Pin::Base] A pin # representing the place in which expression is evaluated (e.g., # a Method pin, or a Module or Class pin if not run within a # method - both in terms of the closure around the chain, as well @@ -192,6 +195,7 @@ def nullable? include Logging + # @return [String] def desc links.map(&:desc).to_s end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b23c21b74..3e2631976 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -27,7 +27,9 @@ end require 'solargraph' # Suppress logger output in specs (if possible) -Solargraph::Logging.logger.reopen(File::NULL) if Solargraph::Logging.logger.respond_to?(:reopen) +if Solargraph::Logging.logger.respond_to?(:reopen) && !ENV.key?('SOLARGRAPH_LOG') + Solargraph::Logging.logger.reopen(File::NULL) +end # @param name [String] # @param value [String] From fba485e78664078a23f2f23f5f5dbc8d4be48e56 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 24 Aug 2025 10:44:31 -0400 Subject: [PATCH 084/460] Fix hole in type checking evaluation (#1009) * Fix hole in type checking evaluation The call to `bar()` in `[ bar('string') ].compact` is not currently type-checked due to a missed spot in `NodeMethods#call_nodes_from(node)` * Avoid over-reporting call issues * Fix annotation * Add nocov markers around unreachable area * Fix a coverage issue * Cover more paths in type checking * Fix a code coverage issue * Drop blank line * Ratchet Rubocop todo * Fix missing coverage --- .rubocop_todo.yml | 11 - .../parser/parser_gem/node_methods.rb | 1 + lib/solargraph/type_checker.rb | 223 +++++++++--------- spec/parser/node_methods_spec.rb | 40 ++++ spec/type_checker/levels/strict_spec.rb | 35 ++- spec/type_checker/levels/strong_spec.rb | 120 ++++++++++ 6 files changed, 311 insertions(+), 119 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d8283c5c6..fe8ab7c48 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -99,7 +99,6 @@ Layout/ElseAlignment: - 'lib/solargraph/source/chain/call.rb' - 'lib/solargraph/source_map/clip.rb' - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker.rb' - 'lib/solargraph/type_checker/rules.rb' - 'lib/solargraph/yard_map/mapper.rb' @@ -159,7 +158,6 @@ Layout/EndAlignment: - 'lib/solargraph/source/chain/call.rb' - 'lib/solargraph/source_map/clip.rb' - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker.rb' - 'lib/solargraph/type_checker/rules.rb' - 'lib/solargraph/yard_map/mapper.rb' @@ -778,7 +776,6 @@ Metrics/MethodLength: - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/source/chain/call.rb' - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker.rb' # Configuration parameters: CountComments, Max, CountAsOne. Metrics/ModuleLength: @@ -2135,13 +2132,6 @@ Style/NegatedIfElseCondition: - 'lib/solargraph/shell.rb' - 'lib/solargraph/type_checker.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedMethods. -# AllowedMethods: be, be_a, be_an, be_between, be_falsey, be_kind_of, be_instance_of, be_truthy, be_within, eq, eql, end_with, include, match, raise_error, respond_to, start_with -Style/NestedParenthesizedCalls: - Exclude: - - 'lib/solargraph/type_checker.rb' - # This cop supports safe autocorrection (--autocorrect). Style/NestedTernaryOperator: Exclude: @@ -2237,7 +2227,6 @@ Style/RedundantBegin: - 'lib/solargraph/shell.rb' - 'lib/solargraph/source/cursor.rb' - 'lib/solargraph/source/encoding_fixes.rb' - - 'lib/solargraph/type_checker.rb' - 'lib/solargraph/workspace.rb' # This cop supports safe autocorrection (--autocorrect). diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index b716b352d..af5c62cca 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -179,6 +179,7 @@ def call_nodes_from node node.children[1..-1].each { |child| result.concat call_nodes_from(child) } elsif node.type == :send result.push node + result.concat call_nodes_from(node.children.first) node.children[2..-1].each { |child| result.concat call_nodes_from(child) } elsif [:super, :zsuper].include?(node.type) result.push node diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index e99f99195..55bf55745 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -289,127 +289,136 @@ def call_problems # @param chain [Solargraph::Source::Chain] # @param api_map [Solargraph::ApiMap] - # @param block_pin [Solargraph::Pin::Base] + # @param closure_pin [Solargraph::Pin::Closure] # @param locals [Array] # @param location [Solargraph::Location] # @return [Array] - def argument_problems_for chain, api_map, block_pin, locals, location + def argument_problems_for chain, api_map, closure_pin, locals, location result = [] base = chain - until base.links.length == 1 && base.undefined? - last_base_link = base.links.last - break unless last_base_link.is_a?(Solargraph::Source::Chain::Call) - - arguments = last_base_link.arguments - - pins = base.define(api_map, block_pin, locals) - - first_pin = pins.first - if first_pin.is_a?(Pin::DelegatedMethod) && !first_pin.resolvable?(api_map) - # Do nothing, as we can't find the actual method implementation - elsif first_pin.is_a?(Pin::Method) - # @type [Pin::Method] - pin = first_pin - ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper) - arity_problems_for(pin, fake_args_for(block_pin), location) - elsif pin.path == 'Class#new' - fqns = if base.links.one? - block_pin.namespace + # @type last_base_link [Solargraph::Source::Chain::Call] + last_base_link = base.links.last + return [] unless last_base_link.is_a?(Solargraph::Source::Chain::Call) + + arguments = last_base_link.arguments + + pins = base.define(api_map, closure_pin, locals) + + first_pin = pins.first + unresolvable = first_pin.is_a?(Pin::DelegatedMethod) && !first_pin.resolvable?(api_map) + if !unresolvable && first_pin.is_a?(Pin::Method) + # @type [Pin::Method] + pin = first_pin + ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper) + arity_problems_for(pin, fake_args_for(closure_pin), location) + elsif pin.path == 'Class#new' + fqns = if base.links.one? + closure_pin.namespace + else + base.base.infer(api_map, closure_pin, locals).namespace + end + init = api_map.get_method_stack(fqns, 'initialize').first + + init ? arity_problems_for(init, arguments, location) : [] + else + arity_problems_for(pin, arguments, location) + end + return ap unless ap.empty? + return [] if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper) + + params = first_param_hash(pins) + + all_errors = [] + pin.signatures.sort { |sig| sig.parameters.length }.each do |sig| + signature_errors = signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin + if signature_errors.empty? + # we found a signature that works - meaning errors from + # other signatures don't matter. + return [] + end + all_errors.concat signature_errors + end + result.concat all_errors + end + result + end + + # @param location [Location] + # @param locals [Array] + # @param closure_pin [Pin::Closure] + # @param params [Hash{String => Hash{Symbol => String, Solargraph::ComplexType}}] + # @param arguments [Array] + # @param sig [Pin::Signature] + # @param pin [Pin::Method] + # @param pins [Array] + # + # @return [Array] + def signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin + errors = [] + # @todo add logic mapping up restarg parameters with + # arguments (including restarg arguments). Use tuples + # when possible, and when not, ensure provably + # incorrect situations are detected. + sig.parameters.each_with_index do |par, idx| + return errors if par.decl == :restarg # bail out and assume the rest is valid pending better arg processing + argchain = arguments[idx] + if argchain.nil? + if par.decl == :arg + final_arg = arguments.last + if final_arg && final_arg.node.type == :splat + argchain = final_arg + return errors else - base.base.infer(api_map, block_pin, locals).namespace + errors.push Problem.new(location, "Not enough arguments to #{pin.path}") end - init = api_map.get_method_stack(fqns, 'initialize').first - init ? arity_problems_for(init, arguments, location) : [] else - arity_problems_for(pin, arguments, location) - end - unless ap.empty? - result.concat ap - break + final_arg = arguments.last + argchain = final_arg if final_arg && [:kwsplat, :hash].include?(final_arg.node.type) end - break if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper) - - params = first_param_hash(pins) - - all_errors = [] - pin.signatures.sort { |sig| sig.parameters.length }.each do |sig| - errors = [] - sig.parameters.each_with_index do |par, idx| - # @todo add logic mapping up restarg parameters with - # arguments (including restarg arguments). Use tuples - # when possible, and when not, ensure provably - # incorrect situations are detected. - break if par.decl == :restarg # bail out pending better arg processing - argchain = arguments[idx] - if argchain.nil? - if par.decl == :arg - final_arg = arguments.last - if final_arg && final_arg.node.type == :splat - argchain = final_arg - next # don't try to apply the type of the splat - unlikely to be specific enough - else - errors.push Problem.new(location, "Not enough arguments to #{pin.path}") - next - end - else - final_arg = arguments.last - argchain = final_arg if final_arg && [:kwsplat, :hash].include?(final_arg.node.type) - end - end - if argchain - if par.decl != :arg - errors.concat kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, params, idx - next - else - if argchain.node.type == :splat && argchain == arguments.last - final_arg = argchain - end - if (final_arg && final_arg.node.type == :splat) - # The final argument given has been seen and was a - # splat, which doesn't give us useful types or - # arities against positional parameters, so let's - # continue on in case there are any required - # kwargs we should warn about - next - end - - if argchain.node.type == :splat && par != sig.parameters.last - # we have been given a splat and there are more - # arguments to come. - - # @todo Improve this so that we can skip past the - # rest of the positional parameters here but still - # process the kwargs - break - end - ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED - ptype = ptype.self_to_type(par.context) - if ptype.nil? - # @todo Some level (strong, I guess) should require the param here - else - argtype = argchain.infer(api_map, block_pin, locals) - if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype) - errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") - next - end - end - end - elsif par.decl == :kwarg - errors.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}") - next - end + end + if argchain + if par.decl != :arg + errors.concat kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pin, params, idx + next + else + if argchain.node.type == :splat && argchain == arguments.last + final_arg = argchain + end + if (final_arg && final_arg.node.type == :splat) + # The final argument given has been seen and was a + # splat, which doesn't give us useful types or + # arities against positional parameters, so let's + # continue on in case there are any required + # kwargs we should warn about + next end - if errors.empty? - all_errors.clear - break + if argchain.node.type == :splat && par != sig.parameters.last + # we have been given a splat and there are more + # arguments to come. + + # @todo Improve this so that we can skip past the + # rest of the positional parameters here but still + # process the kwargs + return errors + end + ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED + ptype = ptype.self_to_type(par.context) + if ptype.nil? + # @todo Some level (strong, I guess) should require the param here + else + argtype = argchain.infer(api_map, closure_pin, locals) + if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype) + errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") + return errors + end end - all_errors.concat errors end - result.concat all_errors + elsif par.decl == :kwarg + errors.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}") + next end - base = base.base end - result + errors end # @param sig [Pin::Signature] diff --git a/spec/parser/node_methods_spec.rb b/spec/parser/node_methods_spec.rb index eb026725b..f9504b584 100644 --- a/spec/parser/node_methods_spec.rb +++ b/spec/parser/node_methods_spec.rb @@ -440,5 +440,45 @@ def super_with_block calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) expect(calls).to be_one end + + it 'handles chained calls' do + source = Solargraph::Source.load_string(%( + Foo.new.bar('string') + )) + calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + expect(calls.length).to eq(2) + end + + it 'handles calls from inside array literals' do + source = Solargraph::Source.load_string(%( + [ Foo.new.bar('string') ] + )) + calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + expect(calls.length).to eq(2) + end + + it 'handles calls from inside array literals that are chained' do + source = Solargraph::Source.load_string(%( + [ Foo.new.bar('string') ].compact + )) + calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + expect(calls.length).to eq(3) + end + + it 'does not over-report calls' do + source = Solargraph::Source.load_string(%( + class Foo + def something + end + end + class Bar < Foo + def something + super(1) + 2 + end + end + )) + calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + expect(calls.length).to eq(2) + end end end diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index b198cec89..25890683b 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -115,6 +115,39 @@ def bar(baz); end expect(checker.problems.first.message).to include('Wrong argument type') end + it 'reports mismatched argument types in chained calls' do + checker = type_checker(%( + # @param baz [Integer] + # @return [String] + def bar(baz); "foo"; end + bar('string').upcase + )) + expect(checker.problems).to be_one + expect(checker.problems.first.message).to include('Wrong argument type') + end + + it 'reports mismatched argument types in calls inside array literals' do + checker = type_checker(%( + # @param baz [Integer] + # @return [String] + def bar(baz); "foo"; end + [ bar('string') ] + )) + expect(checker.problems).to be_one + expect(checker.problems.first.message).to include('Wrong argument type') + end + + it 'reports mismatched argument types in calls inside array literals used in a chain' do + checker = type_checker(%( + # @param baz [Integer] + # @return [String] + def bar(baz); "foo"; end + [ bar('string') ].compact + )) + expect(checker.problems).to be_one + expect(checker.problems.first.message).to include('Wrong argument type') + end + xit 'complains about calling a private method from an illegal place' xit 'complains about calling a non-existent method' @@ -126,7 +159,7 @@ def foo(a) a[0] = :something end )) - expect(checker.problems.map(&:problems)).to eq(['Wrong argument type']) + expect(checker.problems.map(&:message)).to eq(['Wrong argument type']) end it 'complains about dereferencing a non-existent tuple slot' diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 6fdf84e30..a03e6eb5d 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -4,6 +4,48 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end + it 'does not complain on array dereference' do + checker = type_checker(%( + # @param idx [Integer, nil] an index + # @param arr [Array] an array of integers + # + # @return [void] + def foo(idx, arr) + arr[idx] + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'complains on bad @type assignment' do + checker = type_checker(%( + # @type [Integer] + c = Class.new + )) + expect(checker.problems.map(&:message)) + .to eq ['Declared type Integer does not match inferred type Class for variable c'] + end + + it 'does not complain on another variant of Class.new' do + checker = type_checker(%( + class Class + # @return [self] + def self.blah + new + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'does not complain on indirect Class.new', skip: 'hangs in a loop currently' do + checker = type_checker(%( + class Foo < Class; end + Foo.new + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'reports unneeded @sg-ignore tags' do checker = type_checker(%( class Foo @@ -25,6 +67,84 @@ def bar; end expect(checker.problems.first.message).to include('Missing @return tag') end + it 'ignores nilable type issues' do + checker = type_checker(%( + # @param a [String] + # @return [void] + def foo(a); end + + # @param b [String, nil] + # @return [void] + def bar(b) + foo(b) + end + )) + expect(checker.problems.map(&:message)).to eq([]) + end + + it 'calls out keyword issues even when required arg count matches' do + checker = type_checker(%( + # @param a [String] + # @param b [String] + # @return [void] + def foo(a = 'foo', b:); end + + # @return [void] + def bar + foo('baz') + end + )) + expect(checker.problems.map(&:message)).to include('Call to #foo is missing keyword argument b') + end + + it 'calls out type issues even when keyword issues are there' do + pending('fixes to arg vs param checking algorithm') + + checker = type_checker(%( + # @param a [String] + # @param b [String] + # @return [void] + def foo(a = 'foo', b:); end + + # @return [void] + def bar + foo(123) + end + )) + expect(checker.problems.map(&:message)) + .to include('Wrong argument type for #foo: a expected String, received 123') + end + + it 'calls out keyword issues even when arg type issues are there' do + checker = type_checker(%( + # @param a [String] + # @param b [String] + # @return [void] + def foo(a = 'foo', b:); end + + # @return [void] + def bar + foo(123) + end + )) + expect(checker.problems.map(&:message)).to include('Call to #foo is missing keyword argument b') + end + + it 'calls out missing args after a defaulted param' do + checker = type_checker(%( + # @param a [String] + # @param b [String] + # @return [void] + def foo(a = 'foo', b); end + + # @return [void] + def bar + foo(123) + end + )) + expect(checker.problems.map(&:message)).to include('Not enough arguments to #foo') + end + it 'reports missing param tags' do checker = type_checker(%( class Foo From f61b1b6a94439c21b0eb0a986053b5d88276316f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 24 Aug 2025 10:47:12 -0400 Subject: [PATCH 085/460] Improve typechecking error message (#1014) If we know the target of an unresolved method call, include it in the error message. --- lib/solargraph/type_checker.rb | 6 +++++- spec/type_checker/levels/strict_spec.rb | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 55bf55745..e0b23f56b 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -277,7 +277,11 @@ def call_problems # @todo remove the internal_or_core? check at a higher-than-strict level if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) unless closest.generic? || ignored_pins.include?(found) - result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}") + if closest.defined? + result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest}") + else + result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}") + end @marked_ranges.push rng end end diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 25890683b..0e2159d95 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -40,6 +40,7 @@ def bar(a); end )) expect(checker.problems).to be_one expect(checker.problems.first.message).to include('Unresolved call') + expect(checker.problems.first.message).not_to include('undefined') end it 'reports undefined method calls with defined roots' do @@ -48,6 +49,7 @@ def bar(a); end )) expect(checker.problems).to be_one expect(checker.problems.first.message).to include('Unresolved call') + expect(checker.problems.first.message).to include('String') expect(checker.problems.first.message).to include('not_a_method') end From 9d4c711bed1af477224bae346b98e93a2d2e732e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 24 Aug 2025 10:48:37 -0400 Subject: [PATCH 086/460] Internal strict type-checking fixes (#1013) * Internal strict type-checking fixes * Add annotation * Add annotation * Add @sg-ignores for now --- lib/solargraph/api_map/cache.rb | 5 +++-- lib/solargraph/api_map/index.rb | 8 ++++---- lib/solargraph/api_map/store.rb | 1 + lib/solargraph/complex_type/unique_type.rb | 4 ++-- lib/solargraph/diagnostics/rubocop_helpers.rb | 1 - lib/solargraph/library.rb | 3 ++- lib/solargraph/parser/node_processor/base.rb | 2 +- .../parser/parser_gem/node_processors/block_node.rb | 5 +++-- .../parser/parser_gem/node_processors/if_node.rb | 2 ++ lib/solargraph/rbs_map/conversions.rb | 2 +- lib/solargraph/source.rb | 2 +- lib/solargraph/source_map/clip.rb | 2 +- lib/solargraph/yard_map/mapper/to_method.rb | 2 +- 13 files changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/solargraph/api_map/cache.rb b/lib/solargraph/api_map/cache.rb index 329a1e5e1..0052d91ea 100644 --- a/lib/solargraph/api_map/cache.rb +++ b/lib/solargraph/api_map/cache.rb @@ -4,9 +4,9 @@ module Solargraph class ApiMap class Cache def initialize - # @type [Hash{Array => Array}] + # @type [Hash{String => Array}] @methods = {} - # @type [Hash{(String, Array) => Array}] + # @type [Hash{String, Array => Array}] @constants = {} # @type [Hash{String => String}] @qualified_namespaces = {} @@ -101,6 +101,7 @@ def empty? private + # @return [Array] def all_caches [@methods, @constants, @qualified_namespaces, @receiver_definitions, @clips] end diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index ea358297e..5237a3802 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -42,22 +42,22 @@ def pins_by_class klass @pin_select_cache[klass] ||= pin_class_hash.each_with_object(s) { |(key, o), n| n.merge(o) if key <= klass } end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def include_references @include_references ||= Hash.new { |h, k| h[k] = [] } end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def extend_references @extend_references ||= Hash.new { |h, k| h[k] = [] } end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def prepend_references @prepend_references ||= Hash.new { |h, k| h[k] = [] } end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def superclass_references @superclass_references ||= Hash.new { |h, k| h[k] = [] } end diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index d41a2a0ae..87f053596 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -210,6 +210,7 @@ def index # @return [Boolean] def catalog pinsets @pinsets = pinsets + # @type [Array] @indexes = [] pinsets.each do |pins| if @indexes.last && pins.empty? diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 63a6ae15b..a782656f0 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -349,9 +349,9 @@ def to_a # @param new_name [String, nil] # @param make_rooted [Boolean, nil] - # @param new_key_types [Array, nil] + # @param new_key_types [Array, nil] # @param rooted [Boolean, nil] - # @param new_subtypes [Array, nil] + # @param new_subtypes [Array, nil] # @return [self] def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: nil) raise "Please remove leading :: and set rooted instead - #{new_name}" if new_name&.start_with?('::') diff --git a/lib/solargraph/diagnostics/rubocop_helpers.rb b/lib/solargraph/diagnostics/rubocop_helpers.rb index 4eb2c711d..bfae43822 100644 --- a/lib/solargraph/diagnostics/rubocop_helpers.rb +++ b/lib/solargraph/diagnostics/rubocop_helpers.rb @@ -19,7 +19,6 @@ def require_rubocop(version = nil) gem_path = Gem::Specification.find_by_name('rubocop', version).full_gem_path gem_lib_path = File.join(gem_path, 'lib') $LOAD_PATH.unshift(gem_lib_path) unless $LOAD_PATH.include?(gem_lib_path) - # @todo Gem::MissingSpecVersionError is undocumented for some reason # @sg-ignore rescue Gem::MissingSpecVersionError => e raise InvalidRubocopVersionError, diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 72224f672..b4da03b2e 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -500,7 +500,7 @@ def external_requires private - # @return [Hash{String => Set}] + # @return [Hash{String => Array}] def source_map_external_require_hash @source_map_external_require_hash ||= {} end @@ -508,6 +508,7 @@ def source_map_external_require_hash # @param source_map [SourceMap] # @return [void] def find_external_requires source_map + # @type [Set] new_set = source_map.requires.map(&:name).to_set # return if new_set == source_map_external_require_hash[source_map.filename] _filenames = nil diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index d87268a91..fad31e95b 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -13,7 +13,7 @@ class Base # @return [Array] attr_reader :pins - # @return [Array] + # @return [Array] attr_reader :locals # @param node [Parser::AST::Node] diff --git a/lib/solargraph/parser/parser_gem/node_processors/block_node.rb b/lib/solargraph/parser/parser_gem/node_processors/block_node.rb index 70a2d9e68..d773e8e50 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/block_node.rb @@ -19,7 +19,7 @@ def process else region.closure end - pins.push Solargraph::Pin::Block.new( + block_pin = Solargraph::Pin::Block.new( location: location, closure: parent, node: node, @@ -28,7 +28,8 @@ def process scope: region.scope || region.closure.context.scope, source: :parser ) - process_children region.update(closure: pins.last) + pins.push block_pin + process_children region.update(closure: block_pin) end private diff --git a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb index 5784afcbe..2452b9cc5 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb @@ -11,6 +11,8 @@ def process process_children position = get_node_start_position(node) + # @sg-ignore + # @type [Solargraph::Pin::Breakable, nil] enclosing_breakable_pin = pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location.range.contain?(position)}.last FlowSensitiveTyping.new(locals, enclosing_breakable_pin).process_if(node) end diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 6e50c022a..f8deec251 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -345,7 +345,7 @@ def global_decl_to_pin decl } # @param decl [RBS::AST::Members::MethodDefinition, RBS::AST::Members::AttrReader, RBS::AST::Members::AttrAccessor] - # @param closure [Pin::Namespace] + # @param closure [Pin::Closure] # @param context [Context] # @param scope [Symbol] :instance or :class # @param name [String] The name of the method diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index d2b24cc61..ee8baa768 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -318,7 +318,7 @@ def string_nodes @string_nodes ||= string_nodes_in(node) end - # @return [Array] + # @return [Array] def comment_ranges @comment_ranges ||= comments.values.map(&:range) end diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index ba69b1b93..16a4ec845 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -65,7 +65,7 @@ def infer # position. Locals can be local variables, method parameters, or block # parameters. The array starts with the nearest local pin. # - # @return [::Array] + # @return [::Array] def locals @locals ||= source_map.locals_at(location) end diff --git a/lib/solargraph/yard_map/mapper/to_method.rb b/lib/solargraph/yard_map/mapper/to_method.rb index df431bb3c..6bb4fa261 100644 --- a/lib/solargraph/yard_map/mapper/to_method.rb +++ b/lib/solargraph/yard_map/mapper/to_method.rb @@ -27,7 +27,7 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = final_scope = scope || code_object.scope override_key = [closure.path, final_scope, name] final_visibility = VISIBILITY_OVERRIDE[override_key] - final_visibility ||= VISIBILITY_OVERRIDE[override_key[0..-2]] + final_visibility ||= VISIBILITY_OVERRIDE[[closure.path, final_scope]] final_visibility ||= :private if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(name) final_visibility ||= visibility final_visibility ||= :private if code_object.module_function? && final_scope == :instance From 3bcbf85d5a5e7445754b1a0392e39238f8a681c3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 24 Aug 2025 10:50:13 -0400 Subject: [PATCH 087/460] Reproduce and fix a ||= (or-asgn) evaluation issue (#1017) * Reproduce and fix a ||= (or-asgn) evaluation issue * Fix linting issue --- .../parser/parser_gem/node_chainer.rb | 3 ++- spec/type_checker/levels/strict_spec.rb | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index 646e753d5..d8d46319b 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -99,7 +99,8 @@ def generate_links n elsif [:gvar, :gvasgn].include?(n.type) result.push Chain::GlobalVariable.new(n.children[0].to_s) elsif n.type == :or_asgn - result.concat generate_links n.children[1] + new_node = n.updated(n.children[0].type, n.children[0].children + [n.children[1]]) + result.concat generate_links new_node elsif [:class, :module, :def, :defs].include?(n.type) # @todo Undefined or what? result.push Chain::UNDEFINED_CALL diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 0e2159d95..7e57cb7cf 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -976,5 +976,22 @@ def bar(a) )) expect(checker.problems.map(&:message)).to eq([]) end + + it 'does not complain on defaulted reader with detailed expression' do + checker = type_checker(%( + class Foo + # @return [Integer, nil] + def bar + @bar ||= + if rand + 123 + elsif rand + 456 + end + end + end + )) + expect(checker.problems.map(&:message)).to eq([]) + end end end From 25557b42fea981ddf8d5a01042204e55c71fdab5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 24 Aug 2025 10:51:19 -0400 Subject: [PATCH 088/460] Define closure for Pin::Symbol, for completeness (#1027) This isn't used anywhere to my knowledge, but it makes sense to think of symbols as being in the global namespace, helps guarantee that closure is always available on a pin, and of course keeps the assert happy ;) --- lib/solargraph/pin/symbol.rb | 4 ++++ spec/pin/symbol_spec.rb | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/pin/symbol.rb b/lib/solargraph/pin/symbol.rb index 9e11c3d7d..9c59155a1 100644 --- a/lib/solargraph/pin/symbol.rb +++ b/lib/solargraph/pin/symbol.rb @@ -20,6 +20,10 @@ def path '' end + def closure + @closure ||= Pin::ROOT_PIN + end + def completion_item_kind Solargraph::LanguageServer::CompletionItemKinds::KEYWORD end diff --git a/spec/pin/symbol_spec.rb b/spec/pin/symbol_spec.rb index 98d88137e..16961cadc 100644 --- a/spec/pin/symbol_spec.rb +++ b/spec/pin/symbol_spec.rb @@ -1,10 +1,15 @@ describe Solargraph::Pin::Symbol do context "as an unquoted literal" do - it "is a kind of keyword" do + it "is a kind of keyword to the LSP" do pin = Solargraph::Pin::Symbol.new(nil, ':symbol') expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end + it "has global closure" do + pin = Solargraph::Pin::Symbol.new(nil, ':symbol') + expect(pin.closure).to eq(Solargraph::Pin::ROOT_PIN) + end + it "has a Symbol return type" do pin = Solargraph::Pin::Symbol.new(nil, ':symbol') expect(pin.return_type.tag).to eq('Symbol') From 32565d4488765c6a4ebfc302ee24bb123dd5c5af Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 24 Aug 2025 10:52:07 -0400 Subject: [PATCH 089/460] Fix 'all!' config to reporters (#1018) * Fix 'all!' config to reporters Solargraph found the type error here! * Linting fixes --- lib/solargraph/library.rb | 4 ++-- spec/library_spec.rb | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index b4da03b2e..9d5162431 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -402,8 +402,8 @@ def diagnose filename repargs = {} workspace.config.reporters.each do |line| if line == 'all!' - Diagnostics.reporters.each do |reporter| - repargs[reporter] ||= [] + Diagnostics.reporters.each do |reporter_name| + repargs[Diagnostics.reporter(reporter_name)] ||= [] end else args = line.split(':').map(&:strip) diff --git a/spec/library_spec.rb b/spec/library_spec.rb index bea0f2983..34de9e1f0 100644 --- a/spec/library_spec.rb +++ b/spec/library_spec.rb @@ -132,6 +132,20 @@ def bar baz, key: '' # @todo More tests end + it 'diagnoses using all reporters' do + directory = '' + config = instance_double(Solargraph::Workspace::Config) + allow(config).to receive_messages(plugins: [], required: [], reporters: ['all!']) + workspace = Solargraph::Workspace.new directory, config + library = Solargraph::Library.new workspace + src = Solargraph::Source.load_string(%( + puts 'hello' + ), 'file.rb', 0) + library.attach src + result = library.diagnose 'file.rb' + expect(result.to_s).to include('rubocop') + end + it "documents symbols" do library = Solargraph::Library.new src = Solargraph::Source.load_string(%( From 3946cc481c12c704bd9265b87516079d179ce5a2 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 24 Aug 2025 11:54:48 -0400 Subject: [PATCH 090/460] Fix DocMap.all_rbs_collection_gems_in_memory return type (#1037) --- lib/solargraph/doc_map.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 43c8768b0..56f51973f 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -133,7 +133,7 @@ def self.all_yard_gems_in_memory @yard_gems_in_memory ||= {} end - # @return [Hash{String => Array}] stored by RBS collection path + # @return [Hash{String => Hash{Array(String, String) => Array}}] stored by RBS collection path def self.all_rbs_collection_gems_in_memory @rbs_collection_gems_in_memory ||= {} end From 43caccadaaf71bbd51acc85d74d437fd061875f1 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 24 Aug 2025 12:40:56 -0400 Subject: [PATCH 091/460] Fix RuboCop linting errors in regular expressions (#1038) * Fix RuboCop linting errors in regular expressions * Continue on rubocop_todo errors * Move configuration * Continue on undercover errors --- .github/workflows/linting.yml | 1 + .github/workflows/rspec.yml | 1 + lib/solargraph/parser/parser_gem/node_methods.rb | 2 +- lib/solargraph/pin/method.rb | 4 ++-- lib/solargraph/pin/parameter.rb | 2 +- lib/solargraph/source/change.rb | 4 ++-- lib/solargraph/source/cursor.rb | 4 ++-- lib/solargraph/source/source_chainer.rb | 2 +- lib/solargraph/source_map/mapper.rb | 4 ++-- 9 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 2a5968351..8abbf51ef 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -113,6 +113,7 @@ jobs: run: bundle exec rubocop -c .rubocop.yml - name: Run RuboCop against todo file + continue-on-error: true run: | bundle exec rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp if [ -n "$(git status --porcelain)" ] diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 35f7a1d13..ecc3d9771 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -69,3 +69,4 @@ jobs: run: bundle exec rake spec - name: Check PR coverage run: bundle exec rake undercover + continue-on-error: true diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index af5c62cca..1397f9583 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -243,7 +243,7 @@ def find_recipient_node cursor if source.synchronized? return node if source.code[0..offset-1] =~ /\(\s*\z/ && source.code[offset..-1] =~ /^\s*\)/ else - return node if source.code[0..offset-1] =~ /\([^\(]*\z/ + return node if source.code[0..offset-1] =~ /\([^(]*\z/ end end end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 6309cb55a..0482b2b54 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -428,7 +428,7 @@ def resolve_ref_tag api_map @resolved_ref_tag = true return self unless docstring.ref_tags.any? docstring.ref_tags.each do |tag| - ref = if tag.owner.to_s.start_with?(/[#\.]/) + ref = if tag.owner.to_s.start_with?(/[#.]/) api_map.get_methods(namespace) .select { |pin| pin.path.end_with?(tag.owner.to_s) } .first @@ -552,7 +552,7 @@ def typify_from_super api_map # @param api_map [ApiMap] # @return [ComplexType, nil] def resolve_reference ref, api_map - parts = ref.split(/[\.#]/) + parts = ref.split(/[.#]/) if parts.first.empty? || parts.one? path = "#{namespace}#{ref}" else diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index e298ba20a..512ee0ead 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -247,7 +247,7 @@ def see_reference heredoc, api_map, skip = [] def resolve_reference ref, api_map, skip return nil if skip.include?(ref) skip.push ref - parts = ref.split(/[\.#]/) + parts = ref.split(/[.#]/) if parts.first.empty? path = "#{namespace}#{ref}" else diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index 72a99b6a6..65c47c7e0 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -28,7 +28,7 @@ def initialize range, new_text # syntax errors will be repaired. # @return [String] The updated text. def write text, nullable = false - if nullable and !range.nil? and new_text.match(/[\.\[\{\(@\$:]$/) + if nullable and !range.nil? and new_text.match(/[.\[{(@$:]$/) [':', '@'].each do |dupable| next unless new_text == dupable offset = Position.to_offset(text, range.start) @@ -59,7 +59,7 @@ def repair text else result = commit text, fixed off = Position.to_offset(text, range.start) - match = result[0, off].match(/[\.:]+\z/) + match = result[0, off].match(/[.:]+\z/) if match result = result[0, off].sub(/#{match[0]}\z/, ' ' * match[0].length) + result[off..-1] end diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 70e4fd47a..a8226eb07 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -124,7 +124,7 @@ def node def node_position @node_position ||= begin if start_of_word.empty? - match = source.code[0, offset].match(/[\s]*(\.|:+)[\s]*$/) + match = source.code[0, offset].match(/\s*(\.|:+)\s*$/) if match Position.from_offset(source.code, offset - match[0].length) else @@ -159,7 +159,7 @@ def start_word_pattern # # @return [Regexp] def end_word_pattern - /^([a-z0-9_]|[^\u0000-\u007F])*[\?\!]?/i + /^([a-z0-9_]|[^\u0000-\u007F])*[?!]?/i end end end diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index e79d85b7e..5758a9d35 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -97,7 +97,7 @@ def fixed_position # @return [String] def end_of_phrase @end_of_phrase ||= begin - match = phrase.match(/[\s]*(\.{1}|::)[\s]*$/) + match = phrase.match(/\s*(\.{1}|::)\s*$/) if match match[0] else diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index d53fd49a0..5fdcb9fe6 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -163,7 +163,7 @@ def process_directive source_position, comment_position, directive end end when 'visibility' - begin + kind = directive.tag.text&.to_sym return unless [:private, :protected, :public].include?(kind) @@ -182,7 +182,7 @@ def process_directive source_position, comment_position, directive pin.instance_variable_set(:@visibility, kind) end end - end + when 'parse' begin ns = closure_at(source_position) From d3bdfea12869252296c8b2cf9ca1ce2186f86321 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 24 Aug 2025 13:33:58 -0400 Subject: [PATCH 092/460] Resolve class aliases via Constant pins (#1029) * Resolve class aliases via Constant pins This also eliminates the need for Parser::NodeMethods as a searately defined class. * Resolve merge issues * Resolve Solargraph strong complaint * Add @sg-ignore * Fix RuboCop issues * Drop unused method * Ratchet .rubocop_todo.yml --- .rubocop_todo.yml | 11 +- lib/solargraph.rb | 18 +- lib/solargraph/api_map.rb | 118 +++++++++++--- lib/solargraph/api_map/store.rb | 9 +- lib/solargraph/complex_type.rb | 3 + lib/solargraph/complex_type/type_methods.rb | 12 +- lib/solargraph/complex_type/unique_type.rb | 5 +- lib/solargraph/parser/node_methods.rb | 97 ----------- .../parser/parser_gem/node_methods.rb | 2 +- lib/solargraph/pin/base.rb | 2 +- lib/solargraph/rbs_map/conversions.rb | 2 +- spec/api_map/api_map_method_spec.rb | 154 ++++++++++++++++++ spec/api_map_spec.rb | 59 +++++++ spec/convention/struct_definition_spec.rb | 4 +- 14 files changed, 355 insertions(+), 141 deletions(-) delete mode 100644 lib/solargraph/parser/node_methods.rb create mode 100644 spec/api_map/api_map_method_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index fe8ab7c48..14cc0ca5d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -397,7 +397,6 @@ Layout/SpaceBeforeComma: # SupportedStylesForEmptyBraces: space, no_space Layout/SpaceInsideBlockBraces: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/diagnostics/update_errors.rb' - 'lib/solargraph/language_server/host.rb' @@ -569,7 +568,6 @@ Lint/NonAtomicFileOperation: # This cop supports safe autocorrection (--autocorrect). Lint/ParenthesesAsGroupedExpression: Exclude: - - 'lib/solargraph.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'spec/language_server/host_spec.rb' - 'spec/source_map/clip_spec.rb' @@ -672,7 +670,6 @@ Lint/UselessAccessModifier: # Configuration parameters: AutoCorrect. Lint/UselessAssignment: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - 'lib/solargraph/language_server/message/extended/document_gems.rb' @@ -757,6 +754,7 @@ Metrics/ClassLength: # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/CyclomaticComplexity: Exclude: + - 'lib/solargraph/api_map.rb' - 'lib/solargraph/api_map/source_to_yard.rb' - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' @@ -1057,7 +1055,6 @@ RSpec/ExampleLength: # DisallowedExamples: works RSpec/ExampleWording: Exclude: - - 'spec/convention/struct_definition_spec.rb' - 'spec/pin/base_spec.rb' - 'spec/pin/method_spec.rb' @@ -1098,7 +1095,6 @@ RSpec/ImplicitExpect: RSpec/InstanceVariable: Exclude: - 'spec/api_map/config_spec.rb' - - 'spec/api_map_spec.rb' - 'spec/diagnostics/require_not_found_spec.rb' - 'spec/language_server/host/dispatch_spec.rb' - 'spec/language_server/host_spec.rb' @@ -1282,6 +1278,7 @@ RSpec/ScatteredLet: RSpec/SpecFilePathFormat: Exclude: - '**/spec/routing/**/*' + - 'spec/api_map/api_map_method_spec.rb' - 'spec/api_map/cache_spec.rb' - 'spec/api_map/config_spec.rb' - 'spec/api_map/source_to_yard_spec.rb' @@ -1622,7 +1619,6 @@ Style/Documentation: - 'lib/solargraph/parser.rb' - 'lib/solargraph/parser/comment_ripper.rb' - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/parser/node_methods.rb' - 'lib/solargraph/parser/node_processor/base.rb' - 'lib/solargraph/parser/parser_gem.rb' - 'lib/solargraph/parser/parser_gem/class_methods.rb' @@ -1763,7 +1759,6 @@ Style/FrozenStringLiteralComment: - 'lib/solargraph/parser.rb' - 'lib/solargraph/parser/comment_ripper.rb' - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/parser/node_methods.rb' - 'lib/solargraph/parser/parser_gem.rb' - 'lib/solargraph/parser/snippet.rb' - 'lib/solargraph/pin/breakable.rb' @@ -2037,7 +2032,6 @@ Style/MethodDefParentheses: - 'lib/solargraph/location.rb' - 'lib/solargraph/parser/comment_ripper.rb' - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/parser/node_methods.rb' - 'lib/solargraph/parser/node_processor/base.rb' - 'lib/solargraph/parser/parser_gem/flawed_builder.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' @@ -2249,7 +2243,6 @@ Style/RedundantInitialize: # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: - - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/source_map/mapper.rb' diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 038e7bccf..8520e3a93 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -72,7 +72,23 @@ def self.asserts_on?(type) # @param block [Proc] A block that returns a message to log # @return [void] def self.assert_or_log(type, msg = nil, &block) - raise (msg || block.call) if asserts_on?(type) && ![:combine_with_visibility].include?(type) + if asserts_on? type + # @type [String, nil] + msg ||= block.call + + raise "No message given for #{type.inspect}" if msg.nil? + + # @todo :combine_with_visibility is not ready for prime time - + # lots of disagreements found in practice that heuristics need + # to be created for and/or debugging needs to resolve in pin + # generation. + # @todo :api_map_namespace_pin_stack triggers in a badly handled + # self type case - 'keeps track of self type in method + # parameters in subclass' in call_spec.rb + return if %i[api_map_namespace_pin_stack combine_with_visibility].include?(type) + + raise msg + end logger.info msg, &block end diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index eed02b4ef..9db21128f 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -308,12 +308,11 @@ def qualify tag, context_tag = '' return unless type return tag if type.literal? - context_type = ComplexType.try_parse(context_tag) - return unless context_type - fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace) return unless fqns + return fqns if %w[Class Module].include? type + fqns + type.substring end @@ -406,16 +405,18 @@ def get_block_pins # @param deep [Boolean] True to include superclasses, mixins, etc. # @return [Array] def get_methods rooted_tag, scope: :instance, visibility: [:public], deep: true + rooted_tag = qualify(rooted_tag, '') + return [] unless rooted_tag if rooted_tag.start_with? 'Array(' # Array() are really tuples - use our fill, as the RBS repo # does not give us definitions for it rooted_tag = "Solargraph::Fills::Tuple(#{rooted_tag[6..-2]})" end - rooted_type = ComplexType.try_parse(rooted_tag) - fqns = rooted_type.namespace - namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first cached = cache.get_methods(rooted_tag, scope, visibility, deep) return cached.clone unless cached.nil? + rooted_type = ComplexType.try_parse(rooted_tag) + fqns = rooted_type.namespace + namespace_pin = get_namespace_pin(fqns) # @type [Array] result = [] skip = Set.new @@ -535,10 +536,20 @@ def get_complex_type_methods complex_type, context = '', internal = false # @param visibility [Array] :public, :protected, and/or :private # @param preserve_generics [Boolean] # @return [Array] - def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private, :protected, :public], preserve_generics: false - rooted_type = ComplexType.parse(rooted_tag) + def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private, :protected, :public], + preserve_generics: false + rooted_tag = qualify(rooted_tag, '') + return [] unless rooted_tag + rooted_type = ComplexType.try_parse(rooted_tag) + return [] if rooted_type.nil? fqns = rooted_type.namespace - namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first + namespace_pin = get_namespace_pin(fqns) + if namespace_pin.nil? + # :nocov: + Solargraph.assert_or_log(:api_map_namespace_pin_stack, "Could not find namespace pin for #{fqns} while looking for method #{name}") + return [] + # :nocov: + end methods = get_methods(rooted_tag, scope: scope, visibility: visibility).select { |p| p.name == name } methods = erase_generics(namespace_pin, rooted_type, methods) unless preserve_generics methods @@ -695,7 +706,7 @@ def resolve_method_aliases pins, visibility = [:public, :private, :protected] # @param skip [Set] # @param no_core [Boolean] Skip core classes if true # @return [Array] - def inner_get_methods_from_reference(fq_reference_tag, namespace_pin, type, scope, visibility, deep, skip, no_core) + def inner_get_methods_from_reference fq_reference_tag, namespace_pin, type, scope, visibility, deep, skip, no_core logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) starting" } # Ensure the types returned by the methods in the referenced @@ -709,7 +720,7 @@ def inner_get_methods_from_reference(fq_reference_tag, namespace_pin, type, scop # @todo Can inner_get_methods be cached? Lots of lookups of base types going on. methods = inner_get_methods(resolved_reference_type.tag, scope, visibility, deep, skip, no_core) if namespace_pin && !resolved_reference_type.all_params.empty? - reference_pin = store.get_path_pins(resolved_reference_type.name).select { |p| p.is_a?(Pin::Namespace) }.first + reference_pin = get_namespace_pin(resolved_reference_type.namespace) # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) - resolving generics with #{reference_pin.generics}, #{resolved_reference_type.rooted_tags}" } methods = methods.map do |method_pin| method_pin.resolve_generics(reference_pin, resolved_reference_type) @@ -734,6 +745,13 @@ def store # @return [Solargraph::ApiMap::Cache] attr_reader :cache + # @param fqns [String] + # @return [Pin::Namespace, nil] + def get_namespace_pin fqns + # fqns = ComplexType.parse(fqns).namespace + store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first + end + # @param rooted_tag [String] A fully qualified namespace, with # generic parameter values if applicable # @param scope [Symbol] :class or :instance @@ -743,11 +761,20 @@ def store # @param no_core [Boolean] Skip core classes if true # @return [Array] def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false + rooted_tag = qualify(rooted_tag, '') + return [] if rooted_tag.nil? + return [] unless rooted_tag rooted_type = ComplexType.parse(rooted_tag).force_rooted fqns = rooted_type.namespace - fqns_generic_params = rooted_type.all_params - namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first + namespace_pin = get_namespace_pin(fqns) + if namespace_pin.nil? + # :nocov: + Solargraph.assert_or_log(:api_map_namespace_pin_inner, "Could not find namespace pin for #{fqns}") + return [] + # :nocov: + end return [] if no_core && fqns =~ /^(Object|BasicObject|Class|Module)$/ + # @todo should this by by rooted_tag_? reqstr = "#{fqns}|#{scope}|#{visibility.sort}|#{deep}" return [] if skip.include?(reqstr) skip.add reqstr @@ -770,7 +797,10 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if scope == :instance store.get_includes(fqns).reverse.each do |include_tag| rooted_include_tag = qualify(include_tag, rooted_tag) - result.concat inner_get_methods_from_reference(rooted_include_tag, namespace_pin, rooted_type, scope, visibility, deep, skip, true) + if rooted_include_tag + result.concat inner_get_methods_from_reference(rooted_include_tag, namespace_pin, rooted_type, scope, + visibility, deep, skip, true) + end end rooted_sc_tag = qualify_superclass(rooted_tag) unless rooted_sc_tag.nil? @@ -864,16 +894,21 @@ def inner_qualify name, root, skip if root == '' return '' else + root = root[2..-1] if root&.start_with?('::') return inner_qualify(root, '', skip) end else - return name if root == '' && store.namespace_exists?(name) roots = root.to_s.split('::') while roots.length > 0 - fqns = roots.join('::') + '::' + name - return fqns if store.namespace_exists?(fqns) - incs = store.get_includes(roots.join('::')) + potential_root = roots.join('::') + potential_root = potential_root[2..-1] if potential_root.start_with?('::') + potential_fqns = potential_root + '::' + name + potential_fqns = potential_fqns[2..-1] if potential_fqns.start_with?('::') + fqns = resolve_fqns(potential_fqns) + return fqns if fqns + incs = store.get_includes(potential_root) incs.each do |inc| + next if potential_root == root && inc == name foundinc = inner_qualify(name, inc, skip) possibles.push foundinc unless foundinc.nil? end @@ -886,11 +921,54 @@ def inner_qualify name, root, skip possibles.push foundinc unless foundinc.nil? end end - return name if store.namespace_exists?(name) + resolved_fqns = resolve_fqns(name) + return resolved_fqns if resolved_fqns + return possibles.last end end + # @param fqns [String] + # @return [String, nil] + def resolve_fqns fqns + return fqns if store.namespace_exists?(fqns) + + constant_namespace = nil + constant = store.constant_pins.find do |c| + constant_fqns = if c.namespace.empty? + c.name + else + c.namespace + '::' + c.name + end + constant_namespace = c.namespace + constant_fqns == fqns + end + return nil unless constant + + return constant.return_type.namespace if constant.return_type.defined? + + assignment = constant.assignment + + # @sg-ignore Wrong argument type for Solargraph::ApiMap#resolve_trivial_constant: node expected AST::Node, received Parser::AST::Node, nil + target_ns = resolve_trivial_constant(assignment) if assignment + return nil unless target_ns + qualify_namespace target_ns, constant_namespace + end + + # @param node [AST::Node] + # @return [String, nil] + def resolve_trivial_constant node + return nil unless node.is_a?(::Parser::AST::Node) + return nil unless node.type == :const + return nil if node.children.empty? + prefix_node = node.children[0] + prefix = '' + prefix = resolve_trivial_constant(prefix_node) + '::' unless prefix_node.nil? || prefix_node.children.empty? + const_name = node.children[1].to_s + return nil if const_name.empty? + return prefix + const_name + end + # Get the namespace's type (Class or Module). # # @param fqns [String] A fully qualified namespace @@ -898,7 +976,7 @@ def inner_qualify name, root, skip def get_namespace_type fqns return nil if fqns.nil? # @type [Pin::Namespace, nil] - pin = store.get_path_pins(fqns).select{|p| p.is_a?(Pin::Namespace)}.first + pin = get_namespace_pin(fqns) return nil if pin.nil? pin.type end diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 87f053596..3b3fffd69 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -73,13 +73,13 @@ def get_methods fqns, scope: :instance, visibility: [:public] def get_superclass fq_tag raise "Do not prefix fully qualified tags with '::' - #{fq_tag.inspect}" if fq_tag.start_with?('::') sub = ComplexType.parse(fq_tag) + return sub.simplify_literals.name if sub.literal? + return 'Boolean' if %w[TrueClass FalseClass].include?(fq_tag) fqns = sub.namespace return superclass_references[fq_tag].first if superclass_references.key?(fq_tag) return superclass_references[fqns].first if superclass_references.key?(fqns) return 'Object' if fqns != 'BasicObject' && namespace_exists?(fqns) return 'Object' if fqns == 'Boolean' - simplified_literal_name = ComplexType.parse("#{fqns}").simplify_literals.name - return simplified_literal_name if simplified_literal_name != fqns nil end @@ -143,6 +143,11 @@ def namespace_pins pins_by_class(Solargraph::Pin::Namespace) end + # @return [Enumerable] + def constant_pins + pins_by_class(Solargraph::Pin::Constant) + end + # @return [Enumerable] def method_pins pins_by_class(Solargraph::Pin::Method) diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index ac9599329..00dda2d3e 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -17,10 +17,13 @@ def initialize types = [UniqueType::UNDEFINED] # @todo @items here should not need an annotation # @type [Array] items = types.flat_map(&:items).uniq(&:to_s) + + # Canonicalize 'true, false' to the non-runtime-type 'Boolean' if items.any? { |i| i.name == 'false' } && items.any? { |i| i.name == 'true' } items.delete_if { |i| i.name == 'false' || i.name == 'true' } items.unshift(ComplexType::BOOLEAN) end + items = [UniqueType::UNDEFINED] if items.any?(&:undefined?) @items = items end diff --git a/lib/solargraph/complex_type/type_methods.rb b/lib/solargraph/complex_type/type_methods.rb index e6d596244..6bf383a1a 100644 --- a/lib/solargraph/complex_type/type_methods.rb +++ b/lib/solargraph/complex_type/type_methods.rb @@ -10,11 +10,7 @@ class ComplexType # @name: String # @subtypes: Array # @rooted: boolish - # methods: - # transform() - # all_params() - # rooted?() - # can_root_name?() + # methods: (see @!method declarations below) module TypeMethods # @!method transform(new_name = nil, &transform_type) # @param new_name [String, nil] @@ -24,6 +20,9 @@ module TypeMethods # @!method all_params # @return [Array] # @!method rooted? + # @!method literal? + # @!method simplify_literals + # @return [ComplexType::UniqueType, ComplexType] # @!method can_root_name?(name_to_check = nil) # @param name_to_check [String, nil] @@ -124,7 +123,8 @@ def key_types def namespace # if priority higher than ||=, old implements cause unnecessary check @namespace ||= lambda do - return 'Object' if duck_type? + return simplify_literals.namespace if literal? + return 'Object' if duck_type? || name == 'Boolean' return 'NilClass' if nil_type? return (name == 'Class' || name == 'Module') && !subtypes.empty? ? subtypes.first.name : name end.call diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index a782656f0..86a69fe0f 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -74,6 +74,8 @@ def initialize(name, key_types = [], subtypes = [], rooted:, parameters_type: ni if parameters_type.nil? raise "You must supply parameters_type if you provide parameters" unless key_types.empty? && subtypes.empty? end + + raise "name must be a String" unless name.is_a?(String) raise "Please remove leading :: and set rooted instead - #{name.inspect}" if name.start_with?('::') @name = name @parameters_type = parameters_type @@ -126,7 +128,8 @@ def determine_non_literal_name # | `false` return name if name.empty? return 'NilClass' if name == 'nil' - return 'Boolean' if ['true', 'false'].include?(name) + return 'TrueClass' if name == 'true' + return 'FalseClass' if name == 'false' return 'Symbol' if name[0] == ':' return 'String' if ['"', "'"].include?(name[0]) return 'Integer' if name.match?(/^-?\d+$/) diff --git a/lib/solargraph/parser/node_methods.rb b/lib/solargraph/parser/node_methods.rb deleted file mode 100644 index 5d3d1079a..000000000 --- a/lib/solargraph/parser/node_methods.rb +++ /dev/null @@ -1,97 +0,0 @@ -module Solargraph - module Parser - module NodeMethods - module_function - - # @abstract - # @param node [Parser::AST::Node] - # @return [String] - def unpack_name node - raise NotImplementedError - end - - # @abstract - # @todo Temporarily here for testing. Move to Solargraph::Parser. - # @param node [Parser::AST::Node] - # @return [Array] - def call_nodes_from node - raise NotImplementedError - end - - # Find all the nodes within the provided node that potentially return a - # value. - # - # The node parameter typically represents a method's logic, e.g., the - # second child (after the :args node) of a :def node. A simple one-line - # method would typically return itself, while a node with conditions - # would return the resulting node from each conditional branch. Nodes - # that follow a :return node are assumed to be unreachable. Nil values - # are converted to nil node types. - # - # @abstract - # @param node [Parser::AST::Node] - # @return [Array] - def returns_from_method_body node - raise NotImplementedError - end - - # @abstract - # @param node [Parser::AST::Node] - # - # @return [Array] - def const_nodes_from node - raise NotImplementedError - end - - # @abstract - # @param cursor [Solargraph::Source::Cursor] - # @return [Parser::AST::Node, nil] - def find_recipient_node cursor - raise NotImplementedError - end - - # @abstract - # @param node [Parser::AST::Node] - # @return [Array] low-level value nodes in - # value position. Does not include explicit return - # statements - def value_position_nodes_only(node) - raise NotImplementedError - end - - # @abstract - # @param nodes [Enumerable] - def any_splatted_call?(nodes) - raise NotImplementedError - end - - # @abstract - # @param node [Parser::AST::Node] - # @return [void] - def process node - raise NotImplementedError - end - - # @abstract - # @param node [Parser::AST::Node] - # @return [Hash{Parser::AST::Node => Source::Chain}] - def convert_hash node - raise NotImplementedError - end - - # @abstract - # @param node [Parser::AST::Node] - # @return [Position] - def get_node_start_position(node) - raise NotImplementedError - end - - # @abstract - # @param node [Parser::AST::Node] - # @return [Position] - def get_node_end_position(node) - raise NotImplementedError - end - end - end -end diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 1397f9583..5b1c47996 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -120,7 +120,7 @@ def drill_signature node, signature end # @param node [Parser::AST::Node] - # @return [Hash{Parser::AST::Node => Chain}] + # @return [Hash{Parser::AST::Node, Symbol => Source::Chain}] def convert_hash node return {} unless Parser.is_ast_node?(node) return convert_hash(node.children[0]) if node.type == :kwsplat diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index fb3274dab..020d92def 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -300,8 +300,8 @@ def assert_same_count(other, attr) # # @return [Object, nil] def assert_same(other, attr) - return false if other.nil? val1 = send(attr) + return val1 if other.nil? val2 = other.send(attr) return val1 if val1 == val2 Solargraph.assert_or_log("combine_with_#{attr}".to_sym, diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index f8deec251..657ea982f 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -240,6 +240,7 @@ def module_decl_to_pin decl # # @return [Solargraph::Pin::Constant] def create_constant(name, tag, comments, decl, base = nil) + tag = "#{base}<#{tag}>" if base parts = name.split('::') if parts.length > 1 name = parts.last @@ -255,7 +256,6 @@ def create_constant(name, tag, comments, decl, base = nil) comments: comments, source: :rbs ) - tag = "#{base}<#{tag}>" if base rooted_tag = ComplexType.parse(tag).force_rooted.rooted_tags constant_pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) constant_pin diff --git a/spec/api_map/api_map_method_spec.rb b/spec/api_map/api_map_method_spec.rb new file mode 100644 index 000000000..a3adc9b94 --- /dev/null +++ b/spec/api_map/api_map_method_spec.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +describe Solargraph::ApiMap do + let(:api_map) { described_class.new } + let(:bench) do + Solargraph::Bench.new(external_requires: external_requires, workspace: Solargraph::Workspace.new('.')) + end + let(:external_requires) { [] } + + before do + api_map.catalog bench + end + + describe '#qualify' do + let(:external_requires) { ['yaml'] } + + it 'resolves YAML to Psych' do + expect(api_map.qualify('YAML', '')).to eq('Psych') + end + + it 'resolves constants used to alias namespaces' do + map = Solargraph::SourceMap.load_string(%( + class Foo + def bing; end + end + + module Bar + Baz = ::Foo + end + )) + api_map.index map.pins + fqns = api_map.qualify('Bar::Baz') + expect(fqns).to eq('Foo') + end + + it 'understands alias namespaces resolving types' do + source = Solargraph::Source.load_string(%( + class Foo + # @return [Symbol] + def bing; end + end + + module Bar + Baz = ::Foo + end + + a = Bar::Baz.new.bing + a + Bar::Baz + ), 'test.rb') + + api_map = described_class.new.map(source) + + clip = api_map.clip_at('test.rb', [11, 8]) + expect(clip.infer.to_s).to eq('Symbol') + end + + it 'understands nested alias namespaces to nested classes resolving types' do + source = Solargraph::Source.load_string(%( + module A + class Foo + # @return [Symbol] + def bing; end + end + end + + module Bar + Baz = A::Foo + end + + a = Bar::Baz.new.bing + a + ), 'test.rb') + + api_map = described_class.new.map(source) + + clip = api_map.clip_at('test.rb', [13, 8]) + expect(clip.infer.to_s).to eq('Symbol') + end + + it 'understands nested alias namespaces resolving types' do + source = Solargraph::Source.load_string(%( + module Bar + module A + class Foo + # @return [Symbol] + def bing; :bingo; end + end + end + end + + module Bar + Foo = A::Foo + end + + a = Bar::Foo.new.bing + a + ), 'test.rb') + + api_map = described_class.new.map(source) + + clip = api_map.clip_at('test.rb', [15, 8]) + expect(clip.infer.to_s).to eq('Symbol') + end + + it 'understands includes using nested alias namespaces resolving types' do + source = Solargraph::Source.load_string(%( + module Foo + # @return [Symbol] + def bing; :yay; end + end + + module Bar + Baz = Foo + end + + class B + include Foo + end + + a = B.new.bing + a + ), 'test.rb') + + api_map = described_class.new.map(source) + + clip = api_map.clip_at('test.rb', [15, 8]) + expect(clip.infer.to_s).to eq('Symbol') + end + end + + describe '#get_method_stack' do + let(:out) { StringIO.new } + let(:api_map) { described_class.load_with_cache(Dir.pwd, out) } + + context 'with stdlib that has vital dependencies' do + let(:external_requires) { ['yaml'] } + let(:method_stack) { api_map.get_method_stack('YAML', 'safe_load', scope: :class) } + + it 'handles the YAML gem aliased to Psych' do + expect(method_stack).not_to be_empty + end + end + + context 'with thor' do + let(:external_requires) { ['thor'] } + let(:method_stack) { api_map.get_method_stack('Thor', 'desc', scope: :class) } + + it 'handles finding Thor.desc' do + expect(method_stack).not_to be_empty + end + end + end +end diff --git a/spec/api_map_spec.rb b/spec/api_map_spec.rb index c95d4d8ec..494f9e156 100755 --- a/spec/api_map_spec.rb +++ b/spec/api_map_spec.rb @@ -1,6 +1,7 @@ require 'tmpdir' describe Solargraph::ApiMap do + # rubocop:disable RSpec/InstanceVariable before :all do @api_map = Solargraph::ApiMap.new end @@ -817,4 +818,62 @@ def baz clip = api_map.clip_at('test.rb', [11, 10]) expect(clip.infer.to_s).to eq('Symbol') end + + it 'resolves aliases in identically named deeply nested classes' do + source = Solargraph::Source.load_string(%( + module A + module Bar + # @return [Integer] + def quux; 123; end + end + + Baz = Bar + + class Foo + include Baz + end + end + + def c + b = A::Foo.new.quux + b + end + ), 'test.rb') + + api_map = described_class.new.map(source) + + clip = api_map.clip_at('test.rb', [16, 4]) + expect(clip.infer.to_s).to eq('Integer') + end + + it 'resolves aliases in nested classes' do + source = Solargraph::Source.load_string(%( + module A + module Bar + class Baz + # @return [Integer] + def quux; 123; end + end + end + + Baz = Bar::Baz + + class Foo + include Baz + end + end + + def c + b = A::Foo.new.quux + b + end + ), 'test.rb') + + api_map = described_class.new.map(source) + + clip = api_map.clip_at('test.rb', [18, 4]) + expect(clip.infer.to_s).to eq('Integer') + end + + # rubocop:enable RSpec/InstanceVariable end diff --git a/spec/convention/struct_definition_spec.rb b/spec/convention/struct_definition_spec.rb index fe317a42b..5c3fc5211 100644 --- a/spec/convention/struct_definition_spec.rb +++ b/spec/convention/struct_definition_spec.rb @@ -21,7 +21,7 @@ expect(param_baz.return_type.tag).to eql('Integer') end - it 'should set closure to method on assignment operator parameters' do + it 'sets closure to method on assignment operator parameters' do source = Solargraph::SourceMap.load_string(%( # @param bar [String] # @param baz [Integer] @@ -140,7 +140,7 @@ def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end - it 'should not crash' do + it "doesn't crash" do checker = type_checker(%( Foo = Struct.new(:bar, :baz) )) From 4a10b44b3802ea9bd077b7aaab0238b865363e82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lek=C3=AB=20Mula?= Date: Sun, 24 Aug 2025 19:46:19 +0200 Subject: [PATCH 093/460] Speed-up LSP completion response times (#1035) * Improve performance of resolve_method_aliases method - Add indexed lookups for methods and aliases by name - Cache ancestor traversal to avoid repeated computations - Separate regular pins from aliases for more efficient processing - Replace linear search with direct indexed method lookup - Add fast path for creating resolved alias pins without individual lookups Generated with Claude Code * Update .rubocop_todo.yml * Fix typechecking - get_method_stack order `get_method_stack` returns the following order for `Enumerable#select`: - master branch: => ["Enumerable#select", "Kernel#select"] - current branch: => ["Kernel#select", "Enumerable#select"] * Avoid redundant indexing methods_by_name loop through ancestors and rely on store.get_path_pins --- .rubocop_todo.yml | 2 - lib/solargraph/api_map.rb | 91 +++++++++++++++++++++------------ lib/solargraph/api_map/store.rb | 38 ++++++++++++++ spec/api_map_spec.rb | 12 ++--- 4 files changed, 103 insertions(+), 40 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 14cc0ca5d..f400dcfaf 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -77,7 +77,6 @@ Layout/ClosingHeredocIndentation: # Configuration parameters: AllowForAlignment. Layout/CommentIndentation: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/parser/parser_gem/node_methods.rb' - 'lib/solargraph/source_map/mapper.rb' @@ -2656,7 +2655,6 @@ YARD/MismatchName: - 'lib/solargraph/pin/until.rb' - 'lib/solargraph/pin/while.rb' - 'lib/solargraph/pin_cache.rb' - - 'lib/solargraph/source/chain.rb' - 'lib/solargraph/source/chain/call.rb' - 'lib/solargraph/source/chain/z_super.rb' - 'lib/solargraph/type_checker.rb' diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 9db21128f..89ed3a308 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -26,7 +26,6 @@ class ApiMap def initialize pins: [] @source_map_hash = {} @cache = Cache.new - @method_alias_stack = [] index pins end @@ -687,6 +686,7 @@ def type_include?(host_ns, module_ns) # @return [Array] def resolve_method_aliases pins, visibility = [:public, :private, :protected] with_resolved_aliases = pins.map do |pin| + next pin unless pin.is_a?(Pin::MethodAlias) resolved = resolve_method_alias(pin) next nil if resolved.respond_to?(:visibility) && !visibility.include?(resolved.visibility) resolved @@ -998,49 +998,76 @@ def prefer_non_nil_variables pins result + nil_pins end - # @param pin [Pin::MethodAlias, Pin::Base] - # @return [Pin::Method] - def resolve_method_alias pin - return pin unless pin.is_a?(Pin::MethodAlias) - return nil if @method_alias_stack.include?(pin.path) - @method_alias_stack.push pin.path - origin = get_method_stack(pin.full_context.tag, pin.original, scope: pin.scope, preserve_generics: true).first - @method_alias_stack.pop - return nil if origin.nil? + include Logging + + private + + # @param alias_pin [Pin::MethodAlias] + # @return [Pin::Method, nil] + def resolve_method_alias(alias_pin) + ancestors = store.get_ancestors(alias_pin.full_context.tag) + original = nil + + # Search each ancestor for the original method + ancestors.each do |ancestor_fqns| + ancestor_fqns = ComplexType.parse(ancestor_fqns).force_rooted.namespace + ancestor_method_path = "#{ancestor_fqns}#{alias_pin.scope == :instance ? '#' : '.'}#{alias_pin.original}" + + # Search for the original method in the ancestor + original = store.get_path_pins(ancestor_method_path).find do |candidate_pin| + if candidate_pin.is_a?(Pin::MethodAlias) + # recursively resolve method aliases + resolved = resolve_method_alias(candidate_pin) + break resolved if resolved + end + + candidate_pin.is_a?(Pin::Method) && candidate_pin.scope == alias_pin.scope + end + + break if original + end + + # @sg-ignore ignore `received nil` for original + create_resolved_alias_pin(alias_pin, original) if original + end + + # Fast path for creating resolved alias pins without individual method stack lookups + # @param alias_pin [Pin::MethodAlias] The alias pin to resolve + # @param original [Pin::Method] The original method pin that was already found + # @return [Pin::Method] The resolved method pin + def create_resolved_alias_pin(alias_pin, original) + # Build the resolved method pin directly (same logic as resolve_method_alias but without lookup) args = { - location: pin.location, - type_location: origin.type_location, - closure: pin.closure, - name: pin.name, - comments: origin.comments, - scope: origin.scope, -# context: pin.context, - visibility: origin.visibility, - signatures: origin.signatures.map(&:clone).freeze, - attribute: origin.attribute?, - generics: origin.generics.clone, - return_type: origin.return_type, + location: alias_pin.location, + type_location: original.type_location, + closure: alias_pin.closure, + name: alias_pin.name, + comments: original.comments, + scope: original.scope, + visibility: original.visibility, + signatures: original.signatures.map(&:clone).freeze, + attribute: original.attribute?, + generics: original.generics.clone, + return_type: original.return_type, source: :resolve_method_alias } - out = Pin::Method.new **args - out.signatures.each do |sig| + resolved_pin = Pin::Method.new **args + + # Clone signatures and parameters + resolved_pin.signatures.each do |sig| sig.parameters = sig.parameters.map(&:clone).freeze sig.source = :resolve_method_alias sig.parameters.each do |param| - param.closure = out + param.closure = resolved_pin param.source = :resolve_method_alias param.reset_generated! end - sig.closure = out + sig.closure = resolved_pin sig.reset_generated! end - logger.debug { "ApiMap#resolve_method_alias(pin=#{pin}) - returning #{out} from #{origin}" } - out - end - include Logging - - private + resolved_pin + end # @param namespace_pin [Pin::Namespace] # @param rooted_type [ComplexType] diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 3b3fffd69..6479e6039 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -204,6 +204,44 @@ def fqns_pins fqns fqns_pins_map[[base, name]] end + # Get all ancestors (superclasses, includes, prepends, extends) for a namespace + # @param fqns [String] The fully qualified namespace + # @return [Array] Array of ancestor namespaces including the original + def get_ancestors(fqns) + return [] if fqns.nil? || fqns.empty? + + ancestors = [fqns] + visited = Set.new + queue = [fqns] + + until queue.empty? + current = queue.shift + next if current.nil? || current.empty? || visited.include?(current) + visited.add(current) + + current = current.gsub(/^::/, '') + + # Add superclass + superclass = get_superclass(current) + if superclass && !superclass.empty? && !visited.include?(superclass) + ancestors << superclass + queue << superclass + end + + # Add includes, prepends, and extends + [get_includes(current), get_prepends(current), get_extends(current)].each do |refs| + next if refs.nil? + refs.each do |ref| + next if ref.nil? || ref.empty? || visited.include?(ref) + ancestors << ref + queue << ref + end + end + end + + ancestors.compact.uniq + end + private # @return [Index] diff --git a/spec/api_map_spec.rb b/spec/api_map_spec.rb index 494f9e156..b447e3fd3 100755 --- a/spec/api_map_spec.rb +++ b/spec/api_map_spec.rb @@ -210,19 +210,19 @@ def prot it 'finds stacks of methods' do map = Solargraph::SourceMap.load_string(%( module Mixin - def meth; end + def select; end end - class Foo + class Foo < Array include Mixin - def meth; end + def select; end end class Bar < Foo - def meth; end + def select; end end )) @api_map.index map.pins - pins = @api_map.get_method_stack('Bar', 'meth') - expect(pins.map(&:path)).to eq(['Bar#meth', 'Foo#meth', 'Mixin#meth']) + pins = @api_map.get_method_stack('Bar', 'select') + expect(pins.map(&:path)).to eq(['Bar#select', 'Foo#select', 'Mixin#select', 'Array#select', 'Enumerable#select', 'Kernel#select']) end it 'finds symbols' do From 6c557803da5b37cee2ecdb5b14de5804e47f8fa5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 13:02:35 -0400 Subject: [PATCH 094/460] Fix merge issue --- lib/solargraph/gem_pins.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index 877eeb15d..43422505b 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -11,29 +11,20 @@ class << self include Logging end - # @param gemspec [Gem::Specification] + # @param pins [Array] # @return [Array] - def self.build_yard_pins(gemspec) - Yardoc.cache(gemspec) unless Yardoc.cached?(gemspec) - yardoc = Yardoc.load!(gemspec) - YardMap::Mapper.new(yardoc, gemspec).map - end - - # @param pins [Array] - # @return [Array] def self.combine_method_pins_by_path(pins) method_pins, alias_pins = pins.partition { |pin| pin.class == Pin::Method } by_path = method_pins.group_by(&:path) - combined_by_path = by_path.transform_values do |pins| + by_path.transform_values! do |pins| GemPins.combine_method_pins(*pins) end - combined_by_path.values + alias_pins + by_path.values + alias_pins end # @param pins [Pin::Base] # @return [Pin::Base, nil] def self.combine_method_pins(*pins) - # @type [Pin::Method, nil] out = pins.reduce(nil) do |memo, pin| next pin if memo.nil? if memo == pin && memo.source != :combined @@ -48,6 +39,15 @@ def self.combine_method_pins(*pins) out end + # @param yard_plugins [Array] The names of YARD plugins to use. + # @param gemspec [Gem::Specification] + # @return [Array] + def self.build_yard_pins(yard_plugins, gemspec) + Yardoc.cache(yard_plugins, gemspec) unless Yardoc.cached?(gemspec) + yardoc = Yardoc.load!(gemspec) + YardMap::Mapper.new(yardoc, gemspec).map + end + # @param yard_pins [Array] # @param rbs_map [RbsMap] # @return [Array] From df63da2ed6437b2eb0413b781d58d24bfa010eee Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 13:05:30 -0400 Subject: [PATCH 095/460] Deal with rubocop spec filename expectation issue --- .rubocop.yml | 4 ++ .rubocop_todo.yml | 132 ++++------------------------------------------ 2 files changed, 15 insertions(+), 121 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index a73324db2..35c76994e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,6 +21,10 @@ AllCops: - "vendor/**/.*" TargetRubyVersion: 3.0 +# we don't have a spec/solargraph subdirectory like this one wants +RSpec/SpecFilePathFormat: + Enabled: false + Style/MethodDefParentheses: EnforcedStyle: require_no_parentheses diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f400dcfaf..3b9a3b4b5 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,51 +1,44 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.79.2. +# using RuboCop version 1.80.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Include. -# Include: **/*.gemspec Gemspec/AddRuntimeDependency: Exclude: - 'solargraph.gemspec' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Severity, Include. -# Include: **/*.gemspec +# Configuration parameters: Severity. Gemspec/DeprecatedAttributeAssignment: Exclude: - 'solargraph.gemspec' - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' -# Configuration parameters: EnforcedStyle, AllowedGems, Include. +# Configuration parameters: EnforcedStyle, AllowedGems. # SupportedStyles: Gemfile, gems.rb, gemspec -# Include: **/*.gemspec, **/Gemfile, **/gems.rb Gemspec/DevelopmentDependencies: Exclude: - 'solargraph.gemspec' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include. -# Include: **/*.gemspec +# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation. Gemspec/OrderedDependencies: Exclude: - 'solargraph.gemspec' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Severity, Include. -# Include: **/*.gemspec +# 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' -# Configuration parameters: Severity, Include. -# Include: **/*.gemspec +# Configuration parameters: Severity. Gemspec/RequiredRubyVersion: Exclude: - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' @@ -622,7 +615,7 @@ Lint/UnmodifiedReduceAccumulator: - 'lib/solargraph/pin/method.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: Exclude: - 'lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb' @@ -630,7 +623,7 @@ Lint/UnusedBlockArgument: - 'spec/language_server/transport/data_reader_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions. +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions. # NotImplementedExceptions: NotImplementedError Lint/UnusedMethodArgument: Exclude: @@ -660,13 +653,12 @@ Lint/UnusedMethodArgument: - 'spec/doc_map_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, ContextCreatingMethods, MethodCreatingMethods. +# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. Lint/UselessAccessModifier: Exclude: - 'lib/solargraph/api_map.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect. Lint/UselessAssignment: Exclude: - 'lib/solargraph/doc_map.rb' @@ -697,7 +689,6 @@ Lint/UselessConstantScoping: - 'lib/solargraph/rbs_map/conversions.rb' # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AutoCorrect. Lint/UselessMethodDefinition: Exclude: - 'lib/solargraph/pin/signature.rb' @@ -1009,7 +1000,6 @@ RSpec/DescribedClass: - 'spec/yard_map/mapper_spec.rb' # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AutoCorrect. RSpec/EmptyExampleGroup: Exclude: - 'spec/convention_spec.rb' @@ -1109,7 +1099,6 @@ RSpec/LeakyConstantDeclaration: - 'spec/complex_type_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect. RSpec/LetBeforeExamples: Exclude: - 'spec/complex_type_spec.rb' @@ -1267,109 +1256,10 @@ RSpec/RepeatedExample: - 'spec/type_checker/levels/strict_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect. RSpec/ScatteredLet: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata. -# Include: **/*_spec.rb -RSpec/SpecFilePathFormat: - Exclude: - - '**/spec/routing/**/*' - - 'spec/api_map/api_map_method_spec.rb' - - 'spec/api_map/cache_spec.rb' - - 'spec/api_map/config_spec.rb' - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/api_map/store_spec.rb' - - 'spec/api_map_spec.rb' - - 'spec/convention/activesupport_concern_spec.rb' - - 'spec/convention/struct_definition_spec.rb' - - 'spec/convention_spec.rb' - - 'spec/diagnostics/base_spec.rb' - - 'spec/diagnostics/require_not_found_spec.rb' - - 'spec/diagnostics/rubocop_helpers_spec.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/diagnostics/type_check_spec.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/diagnostics_spec.rb' - - 'spec/doc_map_spec.rb' - - 'spec/gem_pins_spec.rb' - - 'spec/language_server/host/diagnoser_spec.rb' - - 'spec/language_server/host/dispatch_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/message/text_document/formatting_spec.rb' - - 'spec/language_server/message/text_document/hover_spec.rb' - - 'spec/language_server/message/text_document/rename_spec.rb' - - 'spec/language_server/message/text_document/type_definition_spec.rb' - - 'spec/language_server/message/workspace/did_change_configuration_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/message_spec.rb' - - 'spec/language_server/transport/adapter_spec.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - - 'spec/language_server/uri_helpers_spec.rb' - - 'spec/library_spec.rb' - - 'spec/logging_spec.rb' - - 'spec/parser/flow_sensitive_typing_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/parser/node_processor_spec.rb' - - 'spec/parser_spec.rb' - - 'spec/pin/base_spec.rb' - - 'spec/pin/base_variable_spec.rb' - - 'spec/pin/block_spec.rb' - - 'spec/pin/constant_spec.rb' - - 'spec/pin/delegated_method_spec.rb' - - 'spec/pin/documenting_spec.rb' - - 'spec/pin/instance_variable_spec.rb' - - 'spec/pin/keyword_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/pin/namespace_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/pin/search_spec.rb' - - 'spec/pin/symbol_spec.rb' - - 'spec/position_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/rbs_map_spec.rb' - - 'spec/shell_spec.rb' - - 'spec/source/chain/array_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/chain/constant_spec.rb' - - 'spec/source/chain/global_variable_spec.rb' - - 'spec/source/chain/head_spec.rb' - - 'spec/source/chain/instance_variable_spec.rb' - - 'spec/source/chain/link_spec.rb' - - 'spec/source/chain/literal_spec.rb' - - 'spec/source/chain/z_super_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source/change_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source/updater_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' - - 'spec/type_checker/checks_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker/levels/strong_spec.rb' - - 'spec/type_checker/levels/typed_spec.rb' - - 'spec/type_checker/rules_spec.rb' - - 'spec/type_checker_spec.rb' - - 'spec/workspace/config_spec.rb' - - 'spec/workspace_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' - - 'spec/yard_map/mapper_spec.rb' - RSpec/StubbedMock: Exclude: - 'spec/language_server/host/message_worker_spec.rb' @@ -1703,7 +1593,7 @@ Style/EmptyLambdaParameter: - 'spec/rbs_map/core_map_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, EnforcedStyle. +# Configuration parameters: EnforcedStyle. # SupportedStyles: compact, expanded Style/EmptyMethod: Exclude: @@ -2234,7 +2124,7 @@ Style/RedundantFreeze: - 'lib/solargraph/source_map/mapper.rb' # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AutoCorrect, AllowComments. +# Configuration parameters: AllowComments. Style/RedundantInitialize: Exclude: - 'lib/solargraph/rbs_map/core_map.rb' From d1e6b128e289ff8ef4ed8bd4c563ec297fcea486 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 13:18:41 -0400 Subject: [PATCH 096/460] RuboCop todo update --- .rubocop_todo.yml | 43 +++++++++++---------------------------- lib/solargraph/api_map.rb | 2 +- lib/solargraph/shell.rb | 5 +++++ spec/shell_spec.rb | 8 +++++--- spec/spec_helper.rb | 1 - 5 files changed, 23 insertions(+), 36 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f400dcfaf..d32ff61cb 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,51 +1,44 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.79.2. +# using RuboCop version 1.80.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Include. -# Include: **/*.gemspec Gemspec/AddRuntimeDependency: Exclude: - 'solargraph.gemspec' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Severity, Include. -# Include: **/*.gemspec +# Configuration parameters: Severity. Gemspec/DeprecatedAttributeAssignment: Exclude: - 'solargraph.gemspec' - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' -# Configuration parameters: EnforcedStyle, AllowedGems, Include. +# Configuration parameters: EnforcedStyle, AllowedGems. # SupportedStyles: Gemfile, gems.rb, gemspec -# Include: **/*.gemspec, **/Gemfile, **/gems.rb Gemspec/DevelopmentDependencies: Exclude: - 'solargraph.gemspec' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include. -# Include: **/*.gemspec +# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation. Gemspec/OrderedDependencies: Exclude: - 'solargraph.gemspec' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Severity, Include. -# Include: **/*.gemspec +# 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' -# Configuration parameters: Severity, Include. -# Include: **/*.gemspec +# Configuration parameters: Severity. Gemspec/RequiredRubyVersion: Exclude: - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' @@ -622,7 +615,7 @@ Lint/UnmodifiedReduceAccumulator: - 'lib/solargraph/pin/method.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: Exclude: - 'lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb' @@ -630,7 +623,7 @@ Lint/UnusedBlockArgument: - 'spec/language_server/transport/data_reader_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions. +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions. # NotImplementedExceptions: NotImplementedError Lint/UnusedMethodArgument: Exclude: @@ -660,13 +653,12 @@ Lint/UnusedMethodArgument: - 'spec/doc_map_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, ContextCreatingMethods, MethodCreatingMethods. +# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. Lint/UselessAccessModifier: Exclude: - 'lib/solargraph/api_map.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect. Lint/UselessAssignment: Exclude: - 'lib/solargraph/doc_map.rb' @@ -697,7 +689,6 @@ Lint/UselessConstantScoping: - 'lib/solargraph/rbs_map/conversions.rb' # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AutoCorrect. Lint/UselessMethodDefinition: Exclude: - 'lib/solargraph/pin/signature.rb' @@ -1009,7 +1000,6 @@ RSpec/DescribedClass: - 'spec/yard_map/mapper_spec.rb' # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AutoCorrect. RSpec/EmptyExampleGroup: Exclude: - 'spec/convention_spec.rb' @@ -1109,16 +1099,10 @@ RSpec/LeakyConstantDeclaration: - 'spec/complex_type_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect. RSpec/LetBeforeExamples: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: . -# SupportedStyles: have_received, receive -RSpec/MessageSpies: - EnforcedStyle: receive - RSpec/MissingExampleGroupArgument: Exclude: - 'spec/diagnostics/rubocop_helpers_spec.rb' @@ -1267,13 +1251,11 @@ RSpec/RepeatedExample: - 'spec/type_checker/levels/strict_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect. RSpec/ScatteredLet: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata. -# Include: **/*_spec.rb +# Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata. RSpec/SpecFilePathFormat: Exclude: - '**/spec/routing/**/*' @@ -1703,7 +1685,7 @@ Style/EmptyLambdaParameter: - 'spec/rbs_map/core_map_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, EnforcedStyle. +# Configuration parameters: EnforcedStyle. # SupportedStyles: compact, expanded Style/EmptyMethod: Exclude: @@ -1842,7 +1824,6 @@ Style/FrozenStringLiteralComment: - 'spec/rbs_map/core_map_spec.rb' - 'spec/rbs_map/stdlib_map_spec.rb' - 'spec/rbs_map_spec.rb' - - 'spec/shell_spec.rb' - 'spec/source/chain/array_spec.rb' - 'spec/source/chain/call_spec.rb' - 'spec/source/chain/class_variable_spec.rb' @@ -2234,7 +2215,7 @@ Style/RedundantFreeze: - 'lib/solargraph/source_map/mapper.rb' # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AutoCorrect, AllowComments. +# Configuration parameters: AllowComments. Style/RedundantInitialize: Exclude: - 'lib/solargraph/rbs_map/core_map.rb' diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 89ed3a308..9231e6a59 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -568,7 +568,7 @@ def get_path_suggestions path # Get an array of pins that match the specified path. # # @param path [String] - # @return [Enumerable] + # @return [Array] def get_path_pins path get_path_suggestions(path) end diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 48d636bc2..40f5f4323 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -249,12 +249,17 @@ def list def method_pin path api_map = Solargraph::ApiMap.load_with_cache('.', $stderr) + # @type [Array] pins = if 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 api_map.get_method_stack(ns, meth, scope: scope) else api_map.get_path_pins path diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index f2eba6a83..cdc0d09fc 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -5,7 +5,7 @@ describe Solargraph::Shell do let(:shell) { described_class.new } - + let(:temp_dir) { Dir.mktmpdir } before do @@ -42,7 +42,9 @@ def bundle_exec(*cmd) it "uncaches without erroring out" do output = bundle_exec("solargraph", "uncache", "solargraph") - expect(output).to include('Clearing pin cache in') + expect(output).to include('Clearing pin cache in') + end + end # @type cmd [Array] # @return [String] @@ -95,7 +97,7 @@ def bundle_exec(*cmd) shell.options = { stack: true } shell.method_pin('String#to_s') end - expect(api_map).to have_received(:get_method_stack).with('String', 'to_s', scope: :instance) + expect(api_map).to haveo_received(:get_method_stack).with('String', 'to_s', scope: :instance) end it 'prints a static pin using stack results' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3e2631976..59d107aa3 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -44,7 +44,6 @@ def with_env_var(name, value) end end - def capture_stdout &block original_stdout = $stdout $stdout = StringIO.new From 5b612ddee00a78208206095ec32c4bc661820df8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 13:21:21 -0400 Subject: [PATCH 097/460] Try rbs collection update before specs --- .github/workflows/rspec.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index ecc3d9771..9c0fe219e 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -48,6 +48,9 @@ jobs: run: | bundle install bundle update rbs # use latest available for this Ruby version + - name: Update types + run: | + bundle update rbs collection update - name: Run tests run: bundle exec rake spec undercover: From 809ad2711ecbbfef05941192d6a7380fd8e53076 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 13:24:26 -0400 Subject: [PATCH 098/460] Fix merge issue --- lib/solargraph/type_checker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 2b791e425..0b43c44fe 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -441,7 +441,7 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum # @todo Some level (strong, I guess) should require the param here else argtype = argchain.infer(api_map, closure_pin, locals) - if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype) + if argtype.defined? && ptype.defined? && !arg_conforms_to?(argtype, ptype) errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") return errors end From f2291f4eadf8cf69b4f9f6baa18908a71e40cbbe Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 16:14:24 -0400 Subject: [PATCH 099/460] RuboCop fixes --- spec/doc_map_spec.rb | 3 ++- spec/language_server/host/diagnoser_spec.rb | 3 ++- spec/language_server/host/message_worker_spec.rb | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index b03e573f0..a13308bf6 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -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 diff --git a/spec/language_server/host/diagnoser_spec.rb b/spec/language_server/host/diagnoser_spec.rb index d59a843f1..69ee0b866 100644 --- a/spec/language_server/host/diagnoser_spec.rb +++ b/spec/language_server/host/diagnoser_spec.rb @@ -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 diff --git a/spec/language_server/host/message_worker_spec.rb b/spec/language_server/host/message_worker_spec.rb index b9ce2a41f..5e5bef481 100644 --- a/spec/language_server/host/message_worker_spec.rb +++ b/spec/language_server/host/message_worker_spec.rb @@ -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 From ed1c54e12abcf12fc9ab2ccf74d948c6984ecf0f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 16:21:27 -0400 Subject: [PATCH 100/460] Rebaseline Rubocop todo --- .rubocop.yml | 3 + .rubocop_todo.yml | 283 ++++++++++++++-------------------------------- 2 files changed, 86 insertions(+), 200 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index a73324db2..c17a56410 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,6 +21,9 @@ AllCops: - "vendor/**/.*" TargetRubyVersion: 3.0 +RSpec/SpecFilePathFormat: + Enabled: false + Style/MethodDefParentheses: EnforcedStyle: require_no_parentheses diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f400dcfaf..4bdf2c073 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,51 +1,44 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.79.2. +# using RuboCop version 1.80.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Include. -# Include: **/*.gemspec Gemspec/AddRuntimeDependency: Exclude: - 'solargraph.gemspec' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Severity, Include. -# Include: **/*.gemspec +# Configuration parameters: Severity. Gemspec/DeprecatedAttributeAssignment: Exclude: - 'solargraph.gemspec' - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' -# Configuration parameters: EnforcedStyle, AllowedGems, Include. +# Configuration parameters: EnforcedStyle, AllowedGems. # SupportedStyles: Gemfile, gems.rb, gemspec -# Include: **/*.gemspec, **/Gemfile, **/gems.rb Gemspec/DevelopmentDependencies: Exclude: - 'solargraph.gemspec' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include. -# Include: **/*.gemspec +# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation. Gemspec/OrderedDependencies: Exclude: - 'solargraph.gemspec' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Severity, Include. -# Include: **/*.gemspec +# 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' -# Configuration parameters: Severity, Include. -# Include: **/*.gemspec +# Configuration parameters: Severity. Gemspec/RequiredRubyVersion: Exclude: - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' @@ -71,7 +64,6 @@ Layout/BlockAlignment: Layout/ClosingHeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment. @@ -233,7 +225,6 @@ Layout/HashAlignment: Layout/HeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - 'spec/yard_map/mapper/to_method_spec.rb' # This cop supports safe autocorrection (--autocorrect). @@ -277,6 +268,77 @@ Layout/LineContinuationSpacing: Exclude: - 'lib/solargraph/diagnostics/rubocop_helpers.rb' +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. +# URISchemes: http, https +Layout/LineLength: + Exclude: + - 'lib/solargraph/api_map.rb' + - 'lib/solargraph/api_map/source_to_yard.rb' + - 'lib/solargraph/api_map/store.rb' + - 'lib/solargraph/complex_type.rb' + - 'lib/solargraph/complex_type/unique_type.rb' + - 'lib/solargraph/convention/data_definition.rb' + - 'lib/solargraph/doc_map.rb' + - 'lib/solargraph/gem_pins.rb' + - 'lib/solargraph/language_server/host.rb' + - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' + - 'lib/solargraph/language_server/message/extended/download_core.rb' + - 'lib/solargraph/language_server/message/initialize.rb' + - 'lib/solargraph/language_server/message/text_document/completion.rb' + - 'lib/solargraph/language_server/message/text_document/definition.rb' + - 'lib/solargraph/language_server/message/text_document/document_highlight.rb' + - 'lib/solargraph/language_server/message/text_document/prepare_rename.rb' + - 'lib/solargraph/language_server/message/text_document/references.rb' + - 'lib/solargraph/language_server/message/text_document/rename.rb' + - 'lib/solargraph/language_server/message/workspace/did_change_watched_files.rb' + - 'lib/solargraph/library.rb' + - 'lib/solargraph/parser/comment_ripper.rb' + - 'lib/solargraph/parser/flow_sensitive_typing.rb' + - 'lib/solargraph/parser/parser_gem/node_chainer.rb' + - 'lib/solargraph/parser/parser_gem/node_methods.rb' + - 'lib/solargraph/parser/parser_gem/node_processors/and_node.rb' + - 'lib/solargraph/parser/parser_gem/node_processors/if_node.rb' + - 'lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb' + - 'lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb' + - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' + - 'lib/solargraph/pin/base.rb' + - 'lib/solargraph/pin/callable.rb' + - 'lib/solargraph/pin/common.rb' + - 'lib/solargraph/pin/documenting.rb' + - 'lib/solargraph/pin/method.rb' + - 'lib/solargraph/pin/parameter.rb' + - 'lib/solargraph/rbs_map/conversions.rb' + - 'lib/solargraph/rbs_map/core_fills.rb' + - 'lib/solargraph/shell.rb' + - 'lib/solargraph/source.rb' + - 'lib/solargraph/source/chain.rb' + - 'lib/solargraph/source/chain/call.rb' + - 'lib/solargraph/source/chain/if.rb' + - 'lib/solargraph/source/chain/instance_variable.rb' + - 'lib/solargraph/source/chain/variable.rb' + - 'lib/solargraph/source/cursor.rb' + - 'lib/solargraph/source/encoding_fixes.rb' + - 'lib/solargraph/source/source_chainer.rb' + - 'lib/solargraph/source_map.rb' + - 'lib/solargraph/source_map/clip.rb' + - 'lib/solargraph/source_map/mapper.rb' + - 'lib/solargraph/type_checker.rb' + - 'lib/solargraph/workspace.rb' + - 'lib/solargraph/workspace/config.rb' + - 'lib/solargraph/yard_map/mapper/to_method.rb' + - 'spec/api_map_spec.rb' + - 'spec/complex_type_spec.rb' + - 'spec/language_server/message/completion_item/resolve_spec.rb' + - 'spec/language_server/message/extended/check_gem_version_spec.rb' + - 'spec/language_server/message/text_document/definition_spec.rb' + - 'spec/language_server/protocol_spec.rb' + - 'spec/pin/parameter_spec.rb' + - 'spec/source/chain_spec.rb' + - 'spec/source_map/clip_spec.rb' + - 'spec/source_map_spec.rb' + - 'spec/workspace_spec.rb' + # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: symmetrical, new_line, same_line @@ -622,7 +684,7 @@ Lint/UnmodifiedReduceAccumulator: - 'lib/solargraph/pin/method.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: Exclude: - 'lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb' @@ -630,7 +692,7 @@ Lint/UnusedBlockArgument: - 'spec/language_server/transport/data_reader_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions. +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions. # NotImplementedExceptions: NotImplementedError Lint/UnusedMethodArgument: Exclude: @@ -660,13 +722,12 @@ Lint/UnusedMethodArgument: - 'spec/doc_map_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, ContextCreatingMethods, MethodCreatingMethods. +# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. Lint/UselessAccessModifier: Exclude: - 'lib/solargraph/api_map.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect. Lint/UselessAssignment: Exclude: - 'lib/solargraph/doc_map.rb' @@ -697,7 +758,6 @@ Lint/UselessConstantScoping: - 'lib/solargraph/rbs_map/conversions.rb' # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AutoCorrect. Lint/UselessMethodDefinition: Exclude: - 'lib/solargraph/pin/signature.rb' @@ -998,7 +1058,6 @@ RSpec/DescribedClass: - 'spec/source_map/mapper_spec.rb' - 'spec/source_map_spec.rb' - 'spec/source_spec.rb' - - 'spec/type_checker/checks_spec.rb' - 'spec/type_checker/levels/normal_spec.rb' - 'spec/type_checker/levels/strict_spec.rb' - 'spec/type_checker/rules_spec.rb' @@ -1009,7 +1068,6 @@ RSpec/DescribedClass: - 'spec/yard_map/mapper_spec.rb' # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AutoCorrect. RSpec/EmptyExampleGroup: Exclude: - 'spec/convention_spec.rb' @@ -1109,7 +1167,6 @@ RSpec/LeakyConstantDeclaration: - 'spec/complex_type_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect. RSpec/LetBeforeExamples: Exclude: - 'spec/complex_type_spec.rb' @@ -1179,7 +1236,6 @@ RSpec/MultipleExpectations: - 'spec/source_map/node_processor_spec.rb' - 'spec/source_map_spec.rb' - 'spec/source_spec.rb' - - 'spec/type_checker/checks_spec.rb' - 'spec/type_checker/levels/normal_spec.rb' - 'spec/type_checker/levels/strict_spec.rb' - 'spec/type_checker/levels/strong_spec.rb' @@ -1204,7 +1260,6 @@ RSpec/NoExpectationExample: - 'spec/pin/block_spec.rb' - 'spec/pin/method_spec.rb' - 'spec/source/chain/call_spec.rb' - - 'spec/type_checker/checks_spec.rb' - 'spec/type_checker/levels/typed_spec.rb' # This cop supports safe autocorrection (--autocorrect). @@ -1267,109 +1322,10 @@ RSpec/RepeatedExample: - 'spec/type_checker/levels/strict_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect. RSpec/ScatteredLet: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata. -# Include: **/*_spec.rb -RSpec/SpecFilePathFormat: - Exclude: - - '**/spec/routing/**/*' - - 'spec/api_map/api_map_method_spec.rb' - - 'spec/api_map/cache_spec.rb' - - 'spec/api_map/config_spec.rb' - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/api_map/store_spec.rb' - - 'spec/api_map_spec.rb' - - 'spec/convention/activesupport_concern_spec.rb' - - 'spec/convention/struct_definition_spec.rb' - - 'spec/convention_spec.rb' - - 'spec/diagnostics/base_spec.rb' - - 'spec/diagnostics/require_not_found_spec.rb' - - 'spec/diagnostics/rubocop_helpers_spec.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/diagnostics/type_check_spec.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/diagnostics_spec.rb' - - 'spec/doc_map_spec.rb' - - 'spec/gem_pins_spec.rb' - - 'spec/language_server/host/diagnoser_spec.rb' - - 'spec/language_server/host/dispatch_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/message/text_document/formatting_spec.rb' - - 'spec/language_server/message/text_document/hover_spec.rb' - - 'spec/language_server/message/text_document/rename_spec.rb' - - 'spec/language_server/message/text_document/type_definition_spec.rb' - - 'spec/language_server/message/workspace/did_change_configuration_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/message_spec.rb' - - 'spec/language_server/transport/adapter_spec.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - - 'spec/language_server/uri_helpers_spec.rb' - - 'spec/library_spec.rb' - - 'spec/logging_spec.rb' - - 'spec/parser/flow_sensitive_typing_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/parser/node_processor_spec.rb' - - 'spec/parser_spec.rb' - - 'spec/pin/base_spec.rb' - - 'spec/pin/base_variable_spec.rb' - - 'spec/pin/block_spec.rb' - - 'spec/pin/constant_spec.rb' - - 'spec/pin/delegated_method_spec.rb' - - 'spec/pin/documenting_spec.rb' - - 'spec/pin/instance_variable_spec.rb' - - 'spec/pin/keyword_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/pin/namespace_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/pin/search_spec.rb' - - 'spec/pin/symbol_spec.rb' - - 'spec/position_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/rbs_map_spec.rb' - - 'spec/shell_spec.rb' - - 'spec/source/chain/array_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/chain/constant_spec.rb' - - 'spec/source/chain/global_variable_spec.rb' - - 'spec/source/chain/head_spec.rb' - - 'spec/source/chain/instance_variable_spec.rb' - - 'spec/source/chain/link_spec.rb' - - 'spec/source/chain/literal_spec.rb' - - 'spec/source/chain/z_super_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source/change_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source/updater_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' - - 'spec/type_checker/checks_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker/levels/strong_spec.rb' - - 'spec/type_checker/levels/typed_spec.rb' - - 'spec/type_checker/rules_spec.rb' - - 'spec/type_checker_spec.rb' - - 'spec/workspace/config_spec.rb' - - 'spec/workspace_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' - - 'spec/yard_map/mapper_spec.rb' - RSpec/StubbedMock: Exclude: - 'spec/language_server/host/message_worker_spec.rb' @@ -1703,7 +1659,7 @@ Style/EmptyLambdaParameter: - 'spec/rbs_map/core_map_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AutoCorrect, EnforcedStyle. +# Configuration parameters: EnforcedStyle. # SupportedStyles: compact, expanded Style/EmptyMethod: Exclude: @@ -1864,7 +1820,6 @@ Style/FrozenStringLiteralComment: - 'spec/source_map_spec.rb' - 'spec/source_spec.rb' - 'spec/spec_helper.rb' - - 'spec/type_checker/checks_spec.rb' - 'spec/type_checker/levels/normal_spec.rb' - 'spec/type_checker/levels/strict_spec.rb' - 'spec/type_checker/levels/strong_spec.rb' @@ -1989,7 +1944,6 @@ Style/MapIntoArray: Exclude: - 'lib/solargraph/diagnostics/update_errors.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/type_checker/param_def.rb' # This cop supports unsafe autocorrection (--autocorrect-all). Style/MapToHash: @@ -2056,7 +2010,6 @@ Style/MethodDefParentheses: - 'lib/solargraph/source_map.rb' - 'lib/solargraph/source_map/mapper.rb' - 'lib/solargraph/type_checker.rb' - - 'lib/solargraph/type_checker/checks.rb' - 'lib/solargraph/yard_map/helpers.rb' - 'lib/solargraph/yardoc.rb' - 'spec/doc_map_spec.rb' @@ -2139,7 +2092,6 @@ Style/Next: - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - 'lib/solargraph/pin/signature.rb' - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/type_checker/checks.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MinDigits, Strict, AllowedNumbers, AllowedPatterns. @@ -2234,7 +2186,7 @@ Style/RedundantFreeze: - 'lib/solargraph/source_map/mapper.rb' # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AutoCorrect, AllowComments. +# Configuration parameters: AllowComments. Style/RedundantInitialize: Exclude: - 'lib/solargraph/rbs_map/core_map.rb' @@ -2355,7 +2307,6 @@ Style/SlicingWithRange: - 'lib/solargraph/source/cursor.rb' - 'lib/solargraph/source/source_chainer.rb' - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker/checks.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowModifier. @@ -2661,77 +2612,9 @@ YARD/MismatchName: YARD/TagTypeSyntax: Exclude: - - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/parser/comment_ripper.rb' - - 'lib/solargraph/type_checker.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. -# URISchemes: http, https -Layout/LineLength: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/source_to_yard.rb' - - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/complex_type.rb' + - 'lib/solargraph/complex_type/conformance.rb' - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/convention/data_definition.rb' - - 'lib/solargraph/doc_map.rb' - - 'lib/solargraph/gem_pins.rb' - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - - 'lib/solargraph/language_server/message/extended/download_core.rb' - - 'lib/solargraph/language_server/message/initialize.rb' - - 'lib/solargraph/language_server/message/text_document/completion.rb' - - 'lib/solargraph/language_server/message/text_document/definition.rb' - - 'lib/solargraph/language_server/message/text_document/document_highlight.rb' - - 'lib/solargraph/language_server/message/text_document/prepare_rename.rb' - - 'lib/solargraph/language_server/message/text_document/references.rb' - - 'lib/solargraph/language_server/message/text_document/rename.rb' - - 'lib/solargraph/language_server/message/workspace/did_change_watched_files.rb' - - 'lib/solargraph/library.rb' - 'lib/solargraph/parser/comment_ripper.rb' - - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/and_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/if_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/common.rb' - - 'lib/solargraph/pin/documenting.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/rbs_map/core_fills.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source/chain.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/chain/if.rb' - - 'lib/solargraph/source/chain/instance_variable.rb' - - 'lib/solargraph/source/chain/variable.rb' - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/encoding_fixes.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source_map.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/source_map/mapper.rb' - 'lib/solargraph/type_checker.rb' - - 'lib/solargraph/workspace.rb' - - 'lib/solargraph/workspace/config.rb' - - 'lib/solargraph/yard_map/mapper/to_method.rb' - - 'spec/api_map_spec.rb' - - 'spec/complex_type_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/workspace_spec.rb' From 260f2270bc743c7e3b1d25183a8f706dc8463e8e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 16:21:48 -0400 Subject: [PATCH 101/460] Fix RuboCop issue --- lib/solargraph/rbs_map/core_map.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/rbs_map/core_map.rb b/lib/solargraph/rbs_map/core_map.rb index 5e030d9f6..bff943764 100644 --- a/lib/solargraph/rbs_map/core_map.rb +++ b/lib/solargraph/rbs_map/core_map.rb @@ -28,7 +28,7 @@ def pins @pins.concat RbsMap::CoreFills::ALL # process overrides, then remove any which couldn't be resolved processed = ApiMap::Store.new(@pins).pins.reject { |p| p.is_a?(Solargraph::Pin::Reference::Override) } - STDOUT.puts "RBS core pins cache size: #{@pins.size}" + puts "RBS core pins cache size: #{@pins.size}" @pins.replace processed PinCache.serialize_core @pins From be46aa30a4951813535e34f3b540fde75a5fb12f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 16:26:40 -0400 Subject: [PATCH 102/460] Add @sg-ignores --- lib/solargraph/shell.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 40f5f4323..0200cc624 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -239,16 +239,22 @@ def list puts "#{workspace.filenames.length} files total." end + # @sg-ignore Unresolved call to desc desc 'method_pin [PATH]', 'Describe a method pin' + # @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 :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 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 method_pin path api_map = Solargraph::ApiMap.load_with_cache('.', $stderr) + # @sg-ignore Unresolved call to options # @type [Array] pins = if options[:stack] scope, ns, meth = if path.include? '#' @@ -269,9 +275,12 @@ def method_pin path exit 1 end pins.each do |pin| + # @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) next @@ -311,6 +320,7 @@ def do_cache gemspec, api_map # @param type [ComplexType] # @return [void] def print_type(type) + # @sg-ignore Unresolved call to options if options[:rbs] puts type.to_rbs else @@ -321,6 +331,7 @@ def print_type(type) # @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 From 0927309cf2d4ff598feff0058fc04bcd9e44bad0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 17:11:04 -0400 Subject: [PATCH 103/460] Fix typo --- .github/workflows/rspec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 9c0fe219e..8f6dac24d 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -50,7 +50,7 @@ jobs: bundle update rbs # use latest available for this Ruby version - name: Update types run: | - bundle update rbs collection update + bundle exec rbs collection update - name: Run tests run: bundle exec rake spec undercover: From 78995508e6bf35ce1e8c9b2ee4693e71bec52981 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 17:15:16 -0400 Subject: [PATCH 104/460] Exclude problematic combinations on Ruby head --- .github/workflows/rspec.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 8f6dac24d..b06f1425e 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -29,7 +29,15 @@ jobs: rbs-version: '3.9.4' - ruby-version: '3.0' rbs-version: '4.0.0.dev.4' - steps: + # 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 uses: ruby/setup-ruby@v1 From d5d6c5fb74415521d5daab6e91aa9ff4e19ada57 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 17:20:49 -0400 Subject: [PATCH 105/460] Fix indentation --- .github/workflows/rspec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index b06f1425e..7e2c4b9c9 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -37,7 +37,7 @@ jobs: rbs-version: '3.9.4' - ruby-version: 'head' rbs-version: '3.6.1' - steps: + steps: - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 From 2c6cacd9a2e9c41bdf6a5085e4d263017a392498 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 17:28:18 -0400 Subject: [PATCH 106/460] method_pin -> pin, hide command --- lib/solargraph/shell.rb | 6 +++--- spec/shell_spec.rb | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 0200cc624..21a53172f 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -240,7 +240,7 @@ def list end # @sg-ignore Unresolved call to desc - desc 'method_pin [PATH]', 'Describe a method pin' + 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 @@ -248,10 +248,10 @@ def list # @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 by including definitions in superclasses', default: false + 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 method_pin path + def pin path api_map = Solargraph::ApiMap.load_with_cache('.', $stderr) # @sg-ignore Unresolved call to options diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index cdc0d09fc..e3c85c6e0 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -57,7 +57,7 @@ def bundle_exec(*cmd) end end - describe 'method_pin' do + 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')) } @@ -70,7 +70,7 @@ def bundle_exec(*cmd) it 'prints a pin' do allow(to_s_pin).to receive(:inspect).and_return('pin inspect result') - out = capture_both { shell.method_pin('String#to_s') } + out = capture_both { shell.pin('String#to_s') } expect(out).to eq("pin inspect result\n") end @@ -82,7 +82,7 @@ def bundle_exec(*cmd) out = capture_both do shell.options = { rbs: true } - shell.method_pin('String#to_s') + shell.pin('String#to_s') end expect(out).to eq("pin RBS result\n") end @@ -95,9 +95,9 @@ def bundle_exec(*cmd) allow(api_map).to receive(:get_method_stack).and_return([to_s_pin]) capture_both do shell.options = { stack: true } - shell.method_pin('String#to_s') + shell.pin('String#to_s') end - expect(api_map).to haveo_received(:get_method_stack).with('String', 'to_s', scope: :instance) + 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 @@ -107,7 +107,7 @@ def bundle_exec(*cmd) allow(api_map).to receive(:get_method_stack).with('String', 'new', scope: :class).and_return([string_new_pin]) capture_both do shell.options = { stack: true } - shell.method_pin('String.new') + shell.pin('String.new') end expect(api_map).to have_received(:get_method_stack).with('String', 'new', scope: :class) end @@ -119,7 +119,7 @@ def bundle_exec(*cmd) out = capture_both do shell.options = { typify: true } - shell.method_pin('String#to_s') + shell.pin('String#to_s') end expect(out).to eq("::String\n") end @@ -131,7 +131,7 @@ def bundle_exec(*cmd) out = capture_both do shell.options = { typify: true, rbs: true } - shell.method_pin('String#to_s') + shell.pin('String#to_s') end expect(out).to eq("::String\n") end @@ -143,7 +143,7 @@ def bundle_exec(*cmd) out = capture_both do shell.options = {} - shell.method_pin('Not#found') + shell.pin('Not#found') rescue SystemExit # Ignore the SystemExit raised by the shell when no pin is found end From ce2a106e0be8e7b50ed9ebfd95918d2a03ed6799 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 17:37:26 -0400 Subject: [PATCH 107/460] Drop one of two contradictory strategies --- lib/solargraph/complex_type/conformance.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/solargraph/complex_type/conformance.rb b/lib/solargraph/complex_type/conformance.rb index 424413038..63abad967 100644 --- a/lib/solargraph/complex_type/conformance.rb +++ b/lib/solargraph/complex_type/conformance.rb @@ -39,9 +39,6 @@ def conforms_to_unique_type? # :nocov: end - if use_simplified_inferred_type? - return with_new_types(inferred.simplify_literals, expected).conforms_to_unique_type? - end return true if ignore_interface? return true if conforms_via_reverse_match? @@ -79,10 +76,6 @@ def conforms_to_unique_type? private - def use_simplified_inferred_type? - inferred.simplifyable_literal? && !expected.literal? - end - def only_inferred_parameters? !expected.parameters? && inferred.parameters? end From 56636ae7022c3cc9c47b729e25c42d816243bdb9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 17:40:15 -0400 Subject: [PATCH 108/460] Fix type --- lib/solargraph/api_map.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 9231e6a59..4e8332080 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -559,7 +559,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] + # @return [Array] def get_path_suggestions path return [] if path.nil? resolve_method_aliases store.get_path_pins(path) From f7e9e606757bbeac8047f154e5b2d63b6884e74c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 17:47:51 -0400 Subject: [PATCH 109/460] Type fixes --- lib/solargraph/api_map/store.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 30ef9bdfc..2ac7fdf95 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -283,17 +283,17 @@ def superclass_references index.superclass_references end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def include_references index.include_references end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def prepend_references index.prepend_references end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def extend_references index.extend_references end From c96bee732931300a33b986ae158e6afe8b31cfc5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 17:59:32 -0400 Subject: [PATCH 110/460] Fix type issue --- lib/solargraph/doc_map.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index e8619b49e..b39bc72bc 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -138,7 +138,7 @@ def self.all_rbs_collection_gems_in_memory @rbs_collection_gems_in_memory ||= {} end - # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version + # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version def yard_pins_in_memory self.class.all_yard_gems_in_memory end From 5996f75e34a2ce28026c6b3d81574de10e2ff768 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 28 Aug 2025 12:14:24 -0400 Subject: [PATCH 111/460] Fix crash while generating activesupport pins Fixes https://github.com/castwide/solargraph/issues/1042 --- lib/solargraph/api_map.rb | 3 +- lib/solargraph/api_map/store.rb | 3 +- lib/solargraph/rbs_map/conversions.rb | 4 +- spec/api_map/api_map_method_spec.rb | 39 +++++++++++++++++++ spec/rbs_map/conversions_spec.rb | 55 ++++++++++++++++++++------- 5 files changed, 86 insertions(+), 18 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 89ed3a308..c141e0ace 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -1010,7 +1010,8 @@ def resolve_method_alias(alias_pin) # Search each ancestor for the original method ancestors.each do |ancestor_fqns| - ancestor_fqns = ComplexType.parse(ancestor_fqns).force_rooted.namespace + ancestor_fqns = ComplexType.try_parse(ancestor_fqns).force_rooted.namespace + next if ancestor_fqns.nil? ancestor_method_path = "#{ancestor_fqns}#{alias_pin.scope == :instance ? '#' : '.'}#{alias_pin.original}" # Search for the original method in the ancestor diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 6479e6039..fa9a7a118 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -72,7 +72,8 @@ def get_methods fqns, scope: :instance, visibility: [:public] # @return [String, nil] def get_superclass fq_tag raise "Do not prefix fully qualified tags with '::' - #{fq_tag.inspect}" if fq_tag.start_with?('::') - sub = ComplexType.parse(fq_tag) + sub = ComplexType.try_parse(fq_tag) + return nil if sub.nil? return sub.simplify_literals.name if sub.literal? return 'Boolean' if %w[TrueClass FalseClass].include?(fq_tag) fqns = sub.namespace diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 657ea982f..78c819d84 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -699,13 +699,13 @@ def method_type_to_tag type # @return [ComplexType::UniqueType] def build_type(type_name, type_args = []) base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s - params = type_args.map { |a| other_type_to_tag(a) }.reject { |t| t == 'undefined' }.map do |t| + params = type_args.map { |a| other_type_to_tag(a) }.map do |t| ComplexType.try_parse(t).force_rooted end if base == 'Hash' && params.length == 2 ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) else - ComplexType::UniqueType.new(base, [], params, rooted: true, parameters_type: :list) + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) end end diff --git a/spec/api_map/api_map_method_spec.rb b/spec/api_map/api_map_method_spec.rb index a3adc9b94..9eac7f754 100644 --- a/spec/api_map/api_map_method_spec.rb +++ b/spec/api_map/api_map_method_spec.rb @@ -150,5 +150,44 @@ class B expect(method_stack).not_to be_empty end end + + context 'with alias to invalid type type' do + before do + sub_pin = Solargraph::Pin::Namespace.new( + type: :class, + name: 'Sub', + closure: Solargraph::Pin::ROOT_PIN, + source: :spec + ) + + superclass_ref_pin = Solargraph::Pin::Reference::Superclass.new( + closure: sub_pin, + name: 'Hash', + source: :spec + ) + + method_alias_pin = Solargraph::Pin::MethodAlias.new( + name: 'meth_alias', + original: '[]', + closure: sub_pin, + scope: :instance, + source: :spec + ) + + api_map.index [sub_pin, method_alias_pin, superclass_ref_pin] + end + + it 'does not crash looking at superclass method' do + expect { api_map.get_method_stack('Hash', '[]', scope: :instance) }.not_to raise_error + end + + it 'does not crash looking at subclass method' do + expect { api_map.get_method_stack('Sub', '[]', scope: :instance) }.not_to raise_error + end + + it 'does not crash looking at subclass alias' do + expect { api_map.get_method_stack('Sub', 'meth_alias', scope: :instance) }.not_to raise_error + end + end end end diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 09c203687..ce800c29f 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -8,26 +8,15 @@ end end - let(:rbs_repo) do - RBS::Repository.new(no_stdlib: false) - end - - let(:loader) do - RBS::EnvironmentLoader.new(core_root: nil, repository: rbs_repo) - end - let(:conversions) do + loader = RBS::EnvironmentLoader.new(core_root: nil, repository: RBS::Repository.new(no_stdlib: false)) + loader.add(path: Pathname(temp_dir)) Solargraph::RbsMap::Conversions.new(loader: loader) end - let(:pins) do - conversions.pins - end - before do rbs_file = File.join(temp_dir, 'foo.rbs') File.write(rbs_file, rbs) - loader.add(path: Pathname(temp_dir)) end attr_reader :temp_dir @@ -41,7 +30,7 @@ def bar: () -> untyped RBS end - subject(:method_pin) { pins.find { |pin| pin.path == 'Foo#bar' } } + subject(:method_pin) { conversions.pins.find { |pin| pin.path == 'Foo#bar' } } it { should_not be_nil } @@ -51,4 +40,42 @@ def bar: () -> untyped expect(method_pin.return_type.tag).to eq('undefined') end end + + # https://github.com/castwide/solargraph/issues/1042 + context 'with Hash superclass with untyped value and alias' do + let(:rbs) do + <<~RBS + class Sub < Hash[Symbol, untyped] + alias meth_alias [] + end + RBS + end + + let(:api_map) { Solargraph::ApiMap.new } + + let(:sup_method_stack) { api_map.get_method_stack('Hash{Symbol => undefined}', '[]', scope: :instance) } + + let(:sub_alias_stack) { api_map.get_method_stack('Sub', 'meth_alias', scope: :instance) } + + before do + api_map.index conversions.pins + end + + it 'does not crash looking at superclass method' do + expect { sup_method_stack }.not_to raise_error + end + + it 'does not crash looking at alias' do + expect { sub_alias_stack }.not_to raise_error + end + + it 'finds superclass method pin return type' do + expect(sup_method_stack.map(&:return_type).map(&:rooted_tags).uniq).to eq(['undefined']) + end + + it 'finds superclass method pin parameter type' do + expect(sup_method_stack.flat_map(&:signatures).flat_map(&:parameters).map(&:return_type).map(&:rooted_tags) + .uniq).to eq(['Symbol']) + end + end end From ab40b7ee9722ce24dd225ba9b0a731a2484cb289 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 28 Aug 2025 12:53:35 -0400 Subject: [PATCH 112/460] Mark existing not-working case as pending in specs --- spec/pin/combine_with_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/pin/combine_with_spec.rb b/spec/pin/combine_with_spec.rb index cc80d76d5..38d45a3e1 100644 --- a/spec/pin/combine_with_spec.rb +++ b/spec/pin/combine_with_spec.rb @@ -9,6 +9,7 @@ end it 'combines return types with another method without type parameters' do + pending('logic being added to handle this case') pin1 = Solargraph::Pin::Method.new(name: 'foo', parameters: [], comments: '@return [Array]') pin2 = Solargraph::Pin::Method.new(name: 'foo', parameters: [], comments: '@return [Array]') combined = pin1.combine_with(pin2) From b0310efd5ec42c7d46b4575e75f48586ed85b105 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 28 Aug 2025 14:28:31 -0400 Subject: [PATCH 113/460] Drop @sg-ignores --- lib/solargraph/parser/parser_gem/node_processors/send_node.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index cedec1b89..48184b52b 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -36,15 +36,12 @@ def process process_autoload elsif method_name == :private_constant process_private_constant - # @sg-ignore elsif method_name == :alias_method && node.children[2] && node.children[2] && node.children[2].type == :sym && node.children[3] && node.children[3].type == :sym process_alias_method - # @sg-ignore elsif method_name == :private_class_method && node.children[2].is_a?(AST::Node) # Processing a private class can potentially handle children on its own return if process_private_class_method end - # @sg-ignore elsif method_name == :require && node.children[0].to_s == '(const nil :Bundler)' pins.push Pin::Reference::Require.new(Solargraph::Location.new(region.filename, Solargraph::Range.from_to(0, 0, 0, 0)), 'bundler/require', source: :parser) end From 9213b75e8d58c275e55b440f98db34677f1df621 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 28 Aug 2025 15:23:06 -0400 Subject: [PATCH 114/460] More annotations addressing strong-level type violations --- lib/solargraph/api_map/index.rb | 4 ++-- lib/solargraph/complex_type.rb | 4 ++++ lib/solargraph/complex_type/type_methods.rb | 2 +- lib/solargraph/complex_type/unique_type.rb | 3 +++ .../convention/data_definition/data_assignment_node.rb | 1 + .../convention/data_definition/data_definition_node.rb | 4 +++- .../convention/struct_definition/struct_assignment_node.rb | 1 + .../convention/struct_definition/struct_definition_node.rb | 4 +++- lib/solargraph/gem_pins.rb | 3 ++- lib/solargraph/language_server/host.rb | 3 ++- lib/solargraph/logging.rb | 1 - lib/solargraph/parser/flow_sensitive_typing.rb | 2 ++ lib/solargraph/parser/node_processor.rb | 4 ++++ lib/solargraph/pin/closure.rb | 2 ++ lib/solargraph/range.rb | 1 + lib/solargraph/rbs_map.rb | 1 - lib/solargraph/rbs_map/core_map.rb | 3 +++ lib/solargraph/source_map.rb | 2 ++ lib/solargraph/type_checker/param_def.rb | 2 ++ lib/solargraph/yardoc.rb | 1 + 20 files changed, 39 insertions(+), 9 deletions(-) diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 33a80a27e..1adb04afe 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -63,7 +63,7 @@ def superclass_references @superclass_references ||= Hash.new { |h, k| h[k] = [] } end - # @param pins [Array] + # @param pins [Enumerable] # @return [self] def merge pins deep_clone.catalog pins @@ -89,7 +89,7 @@ def deep_clone end end - # @param new_pins [Array] + # @param new_pins [Enumerable] # @return [self] def catalog new_pins # @type [Hash{Class> => Set>}] diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 00dda2d3e..627a18356 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -158,10 +158,12 @@ def to_s map(&:tag).join(', ') end + # @return [String] def tags map(&:tag).join(', ') end + # @return [String] def simple_tags simplify_literals.tags end @@ -175,10 +177,12 @@ def downcast_to_literal_if_possible ComplexType.new(items.map(&:downcast_to_literal_if_possible)) end + # @return [String] def desc rooted_tags end + # @return [String] def rooted_tags map(&:rooted_tag).join(', ') end diff --git a/lib/solargraph/complex_type/type_methods.rb b/lib/solargraph/complex_type/type_methods.rb index 4c98afecd..4a5aac1f4 100644 --- a/lib/solargraph/complex_type/type_methods.rb +++ b/lib/solargraph/complex_type/type_methods.rb @@ -130,7 +130,7 @@ def namespace end.call end - # @return [ComplexType, UniqueType] + # @return [self] def namespace_type return ComplexType.parse('::Object') if duck_type? return ComplexType.parse('::NilClass') if nil_type? diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 86a69fe0f..5894d6a00 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -114,10 +114,12 @@ def literal? non_literal_name != name end + # @return [String] def non_literal_name @non_literal_name ||= determine_non_literal_name end + # @return [String] def determine_non_literal_name # https://github.com/ruby/rbs/blob/master/docs/syntax.md # @@ -170,6 +172,7 @@ def rbs_name end end + # @return [String] def desc rooted_tags end diff --git a/lib/solargraph/convention/data_definition/data_assignment_node.rb b/lib/solargraph/convention/data_definition/data_assignment_node.rb index 7aadcf190..cffe77494 100644 --- a/lib/solargraph/convention/data_definition/data_assignment_node.rb +++ b/lib/solargraph/convention/data_definition/data_assignment_node.rb @@ -22,6 +22,7 @@ class << self # s(:def, :foo, # s(:args), # s(:send, nil, :bar)))) + # @param node [::Parser::AST::Node] def match?(node) return false unless node&.type == :casgn return false if node.children[2].nil? diff --git a/lib/solargraph/convention/data_definition/data_definition_node.rb b/lib/solargraph/convention/data_definition/data_definition_node.rb index dd5929822..fb160c58c 100644 --- a/lib/solargraph/convention/data_definition/data_definition_node.rb +++ b/lib/solargraph/convention/data_definition/data_definition_node.rb @@ -25,6 +25,8 @@ class << self # s(:def, :foo, # s(:args), # s(:send, nil, :bar))) + # + # @param node [Parser::AST::Node] def match?(node) return false unless node&.type == :class @@ -46,7 +48,7 @@ def data_definition_node?(data_node) end end - # @return [Parser::AST::Node] + # @param node [Parser::AST::Node] def initialize(node) @node = node end diff --git a/lib/solargraph/convention/struct_definition/struct_assignment_node.rb b/lib/solargraph/convention/struct_definition/struct_assignment_node.rb index 04f96d40e..141abf599 100644 --- a/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_assignment_node.rb @@ -22,6 +22,7 @@ class << self # s(:def, :foo, # s(:args), # s(:send, nil, :bar)))) + # @param node [Parser::AST::Node] def match?(node) return false unless node&.type == :casgn return false if node.children[2].nil? diff --git a/lib/solargraph/convention/struct_definition/struct_definition_node.rb b/lib/solargraph/convention/struct_definition/struct_definition_node.rb index 540320c37..725e4227f 100644 --- a/lib/solargraph/convention/struct_definition/struct_definition_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_definition_node.rb @@ -25,6 +25,8 @@ class << self # s(:def, :foo, # s(:args), # s(:send, nil, :bar))) + # + # @param node [Parser::AST::Node] def match?(node) return false unless node&.type == :class @@ -46,7 +48,7 @@ def struct_definition_node?(struct_node) end end - # @return [Parser::AST::Node] + # @param node [Parser::AST::Node] def initialize(node) @node = node end diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index 7b0009251..a193a8a39 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -51,7 +51,8 @@ def self.build_yard_pins(yard_plugins, gemspec) end # @param yard_pins [Array] - # @param rbs_map [RbsMap] + # @param rbs_pins [Array] + # # @return [Array] def self.combine(yard_pins, rbs_pins) in_yard = Set.new diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index 1c5831bda..65ef909fa 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -299,6 +299,7 @@ def prepare directory, name = nil end end + # @return [String] def command_path options['commandPath'] || 'solargraph' end @@ -716,7 +717,7 @@ def diagnoser # A hash of client requests by ID. The host uses this to keep track of # pending responses. # - # @return [Hash{Integer => Solargraph::LanguageServer::Host}] + # @return [Hash{Integer => Solargraph::LanguageServer::Request}] def requests @requests ||= {} end diff --git a/lib/solargraph/logging.rb b/lib/solargraph/logging.rb index a8bc3b3ee..8f3edaba2 100644 --- a/lib/solargraph/logging.rb +++ b/lib/solargraph/logging.rb @@ -21,7 +21,6 @@ module Logging end DEFAULT_LOG_LEVEL end - # @sg-ignore Fix cvar issue @@logger = Logger.new(STDERR, level: level) # @sg-ignore Fix cvar issue @@logger.formatter = proc do |severity, datetime, progname, msg| diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 308db214b..b29699515 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -156,6 +156,8 @@ def process_facts(facts_by_pin, presences) # @param conditional_node [Parser::AST::Node] # @param true_ranges [Array] + # + # @return [void] def process_conditional(conditional_node, true_ranges) if conditional_node.type == :send process_isa(conditional_node, true_ranges) diff --git a/lib/solargraph/parser/node_processor.rb b/lib/solargraph/parser/node_processor.rb index 0067af647..dbe0b7cd5 100644 --- a/lib/solargraph/parser/node_processor.rb +++ b/lib/solargraph/parser/node_processor.rb @@ -23,6 +23,10 @@ def register type, cls @@processors[type] << cls end + # @param type [Symbol] + # @param cls [Class] + # + # @return [void] def deregister type, cls @@processors[type].delete(cls) end diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index 551ba5522..ac95be10b 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -8,6 +8,7 @@ class Closure < Base # @param scope [::Symbol] :class or :instance # @param generics [::Array, nil] + # @param generic_defaults [Hash{String => ComplexType}] def initialize scope: :class, generics: nil, generic_defaults: {}, **splat super(**splat) @scope = scope @@ -15,6 +16,7 @@ def initialize scope: :class, generics: nil, generic_defaults: {}, **splat @generic_defaults = generic_defaults end + # @return [Hash{String => ComplexType}] def generic_defaults @generic_defaults ||= {} end diff --git a/lib/solargraph/range.rb b/lib/solargraph/range.rb index 615f180af..7a9bc0e30 100644 --- a/lib/solargraph/range.rb +++ b/lib/solargraph/range.rb @@ -24,6 +24,7 @@ def initialize start, ending [start, ending] end + # @param other [BasicObject] def <=>(other) return nil unless other.is_a?(Range) if start == other.start diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index 2f36ce991..08dce5b53 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -42,7 +42,6 @@ def loader @loader ||= RBS::EnvironmentLoader.new(core_root: nil, repository: repository) end - # @sg-ignore # @return [String] representing the version of the RBS info fetched # for the given library. Must change when the RBS info is # updated upstream for the same library and version. May change diff --git a/lib/solargraph/rbs_map/core_map.rb b/lib/solargraph/rbs_map/core_map.rb index 0d265d773..8c3d7dbdd 100644 --- a/lib/solargraph/rbs_map/core_map.rb +++ b/lib/solargraph/rbs_map/core_map.rb @@ -14,6 +14,7 @@ def resolved? def initialize; end + # @return [Enumerable] def pins return @pins if @pins @@ -39,10 +40,12 @@ def loader private + # @return [RBS::EnvironmentLoader] def loader @loader ||= RBS::EnvironmentLoader.new(repository: RBS::Repository.new(no_stdlib: false)) end + # @return [Conversions] def conversions @conversions ||= Conversions.new(loader: loader) end diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 61b08eea8..a934d0850 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -160,10 +160,12 @@ def map source private + # @return [Hash{Class => Array}] def pin_class_hash @pin_class_hash ||= pins.to_set.classify(&:class).transform_values(&:to_a) end + # @return [Data] def data @data ||= Data.new(source) end diff --git a/lib/solargraph/type_checker/param_def.rb b/lib/solargraph/type_checker/param_def.rb index 2c626270a..dcab5f4a8 100644 --- a/lib/solargraph/type_checker/param_def.rb +++ b/lib/solargraph/type_checker/param_def.rb @@ -12,6 +12,8 @@ class ParamDef # @return [Symbol] attr_reader :type + # @param name [String] + # @param type [Symbol] The type of parameter, such as :req, :opt, :rest, etc. def initialize name, type @name = name @type = type diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index d8e597978..625e41ce4 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -44,6 +44,7 @@ def cached?(gemspec) # True if another process is currently building the yardoc cache. # + # @param gemspec [Gem::Specification] def processing?(gemspec) yardoc = File.join(PinCache.yardoc_path(gemspec), 'processing') File.exist?(yardoc) From 13d88639292e411f15f9e5568ea1c4f6a5efc097 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 28 Aug 2025 15:34:26 -0400 Subject: [PATCH 115/460] Follow-on annotation fix --- lib/solargraph/api_map.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 89ed3a308..113dbf571 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -134,7 +134,7 @@ def uncached_yard_gemspecs @doc_map.uncached_yard_gemspecs end - # @return [Array] + # @return [Enumerable] def core_pins @@core_map.pins end From 54c6314e77aa6523f603c9fd168d36e50f8df6ac Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 28 Aug 2025 15:56:39 -0400 Subject: [PATCH 116/460] Remove @sg-ignores resolved with this branch --- lib/solargraph/diagnostics/rubocop_helpers.rb | 1 - lib/solargraph/yardoc.rb | 3 --- 2 files changed, 4 deletions(-) diff --git a/lib/solargraph/diagnostics/rubocop_helpers.rb b/lib/solargraph/diagnostics/rubocop_helpers.rb index bfae43822..f6f4c82c8 100644 --- a/lib/solargraph/diagnostics/rubocop_helpers.rb +++ b/lib/solargraph/diagnostics/rubocop_helpers.rb @@ -19,7 +19,6 @@ def require_rubocop(version = nil) gem_path = Gem::Specification.find_by_name('rubocop', version).full_gem_path gem_lib_path = File.join(gem_path, 'lib') $LOAD_PATH.unshift(gem_lib_path) unless $LOAD_PATH.include?(gem_lib_path) - # @sg-ignore rescue Gem::MissingSpecVersionError => e raise InvalidRubocopVersionError, "could not find '#{e.name}' (#{e.requirement}) - "\ diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index d8e597978..6da2e9ce4 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -23,9 +23,6 @@ def cache(yard_plugins, gemspec) yard_plugins.each { |plugin| cmd << " --plugin #{plugin}" } Solargraph.logger.debug { "Running: #{cmd}" } # @todo set these up to run in parallel - # - # @sg-ignore RBS gem doesn't reflect that Open3.* also include - # kwopts from Process.spawn() stdout_and_stderr_str, status = Open3.capture2e(cmd, chdir: gemspec.gem_dir) unless status.success? Solargraph.logger.warn { "YARD failed running #{cmd.inspect} in #{gemspec.gem_dir}" } From c2bf3459c1db521c87d40e1e154fa720d6926c5b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 28 Aug 2025 17:25:38 -0400 Subject: [PATCH 117/460] Resolve merge conflict --- .rubocop.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 626e96f55..6a78c105a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -38,9 +38,6 @@ Metrics/ParameterLists: Max: 7 CountKeywordArgs: false -RSpec/SpecFilePathFormat: - Enabled: false - # we tend to use @@ and the risk doesn't seem high Style/ClassVars: Enabled: false From 30337a9275f4e3a6a0e6beb1897292c13391940b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 28 Aug 2025 17:30:18 -0400 Subject: [PATCH 118/460] More annotations addressing strong-level type violations --- lib/solargraph/api_map.rb | 1 + lib/solargraph/complex_type/unique_type.rb | 2 +- lib/solargraph/language_server/message/base.rb | 2 +- lib/solargraph/parser/flow_sensitive_typing.rb | 4 ++++ .../parser/parser_gem/node_processors/masgn_node.rb | 3 +++ lib/solargraph/pin/common.rb | 3 +++ lib/solargraph/rbs_map.rb | 1 + lib/solargraph/shell.rb | 2 ++ lib/solargraph/type_checker/rules.rb | 2 +- 9 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 113dbf571..6e2043368 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -368,6 +368,7 @@ def get_instance_variable_pins(namespace, scope = :instance) result end + # @sg-ignore Missing @return tag for Solargraph::ApiMap#visible_pins # @see Solargraph::Parser::FlowSensitiveTyping#visible_pins def visible_pins(*args, **kwargs, &blk) Solargraph::Parser::FlowSensitiveTyping.visible_pins(*args, **kwargs, &blk) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 5894d6a00..e4c2a6375 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -257,7 +257,7 @@ def downcast_to_literal_if_possible # @param generics_to_resolve [Enumerable] # @param context_type [UniqueType, nil] - # @param resolved_generic_values [Hash{String => ComplexType}] Added to as types are encountered or resolved + # @param resolved_generic_values [Hash{String => ComplexType, ComplexType::UniqueType}] Added to as types are encountered or resolved # @return [UniqueType, ComplexType] def resolve_generics_from_context generics_to_resolve, context_type, resolved_generic_values: {} if name == ComplexType::GENERIC_TAG_NAME diff --git a/lib/solargraph/language_server/message/base.rb b/lib/solargraph/language_server/message/base.rb index 2a871419f..cc72d99b5 100644 --- a/lib/solargraph/language_server/message/base.rb +++ b/lib/solargraph/language_server/message/base.rb @@ -16,7 +16,7 @@ class Base # @return [String] attr_reader :method - # @return [Hash{String => Array, Hash{String => undefined}, String, Integer}] + # @return [Hash{String => undefined}] attr_reader :params # @return [Hash, Array, nil] diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index b29699515..8692e9762 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -15,7 +15,9 @@ def initialize(locals, enclosing_breakable_pin = nil) # # @return [void] def process_and(and_node, true_ranges = []) + # @type [Parser::AST::Node] lhs = and_node.children[0] + # @type [Parser::AST::Node] rhs = and_node.children[1] before_rhs_loc = rhs.location.expression.adjust(begin_pos: -1) @@ -42,7 +44,9 @@ def process_if(if_node) # s(:send, nil, :bar)) # [4] pry(main)> conditional_node = if_node.children[0] + # @type [Parser::AST::Node] then_clause = if_node.children[1] + # @type [Parser::AST::Node] else_clause = if_node.children[2] true_ranges = [] diff --git a/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb b/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb index c2df3a3b6..dbef1e2d7 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb @@ -22,8 +22,11 @@ def process # s(:int, 2), # s(:int, 3))) masgn = node + # @type [Parser::AST::Node] mlhs = masgn.children.fetch(0) + # @type [Array] lhs_arr = mlhs.children + # @type [Parser::AST::Node] mass_rhs = node.children.fetch(1) # Get pins created for the mlhs node diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 12d52076a..69bac1cd2 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -6,6 +6,9 @@ module Common # @!method closure # @abstract # @return [Pin::Closure, nil] + # @!method source + # @abstract + # @return [Source, nil] # @return [Location] attr_reader :location diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index 08dce5b53..c6c10bac6 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -48,6 +48,7 @@ def loader # if the config for where information comes form changes. def cache_key @hextdigest ||= begin + # @type [String, nil] data = nil if rbs_collection_config_path lockfile_path = RBS::Collection::Config.to_lockfile_path(Pathname.new(rbs_collection_config_path)) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index afea86a92..a005f600b 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -36,6 +36,7 @@ def socket Signal.trap("TERM") do Backport.stop end + # @sg-ignore Wrong argument type for Backport.prepare_tcp_server: adapter expected Backport::Adapter, received Module Backport.prepare_tcp_server host: options[:host], port: port, adapter: Solargraph::LanguageServer::Transport::Adapter STDERR.puts "Solargraph is listening PORT=#{port} PID=#{Process.pid}" end @@ -52,6 +53,7 @@ def stdio Signal.trap("TERM") do Backport.stop end + # @sg-ignore Wrong argument type for Backport.prepare_stdio_server: adapter expected Backport::Adapter, received Module Backport.prepare_stdio_server adapter: Solargraph::LanguageServer::Transport::Adapter STDERR.puts "Solargraph is listening on stdio PID=#{Process.pid}" end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 8f15037d5..a27fcbefa 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,7 +58,7 @@ def require_all_return_types_match_inferred? rank >= LEVELS[:alpha] end - # We keep this at strong because if you added an @sg-ignore to + # We keep this at strong because if you added an @ sg-ignore to # address a strong-level issue, then ran at a lower level, you'd # get a false positive - we don't run stronger level checks than # requested for performance reasons From 22b8d73685d95b88ae42c533ef09e7509126b121 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 28 Aug 2025 17:59:09 -0400 Subject: [PATCH 119/460] Drop unneeded @sg-ignores --- lib/solargraph/api_map.rb | 1 - lib/solargraph/page.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 6e2043368..5a6d1a0cb 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -950,7 +950,6 @@ def resolve_fqns fqns assignment = constant.assignment - # @sg-ignore Wrong argument type for Solargraph::ApiMap#resolve_trivial_constant: node expected AST::Node, received Parser::AST::Node, nil target_ns = resolve_trivial_constant(assignment) if assignment return nil unless target_ns qualify_namespace target_ns, constant_namespace diff --git a/lib/solargraph/page.rb b/lib/solargraph/page.rb index 34ae12fb0..12782da90 100644 --- a/lib/solargraph/page.rb +++ b/lib/solargraph/page.rb @@ -27,7 +27,6 @@ def initialize locals, render_method end # @param text [String] - # @sg-ignore # @return [String] def htmlify text # @type [String] From 434732336d1be0b7d8ea9732688ba869ce7d67a2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 28 Aug 2025 19:39:00 -0400 Subject: [PATCH 120/460] Fix type errors found in strong typechecking --- lib/solargraph/api_map.rb | 2 +- lib/solargraph/doc_map.rb | 2 +- lib/solargraph/yard_map/mapper/to_method.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 89ed3a308..36985b133 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -310,7 +310,7 @@ def qualify tag, context_tag = '' fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace) return unless fqns - return fqns if %w[Class Module].include? type + return fqns if %w[Class Module].include? type.tag fqns + type.substring end diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 56f51973f..531cf03d7 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -338,7 +338,7 @@ def gemspec_or_preference gemspec return gemspec unless preference_map.key?(gemspec.name) return gemspec if gemspec.version == preference_map[gemspec.name].version - change_gemspec_version gemspec, preference_map[by_path.name].version + change_gemspec_version gemspec, preference_map[gemspec.name].version end # @param gemspec [Gem::Specification] diff --git a/lib/solargraph/yard_map/mapper/to_method.rb b/lib/solargraph/yard_map/mapper/to_method.rb index 6bb4fa261..d8e3b8b43 100644 --- a/lib/solargraph/yard_map/mapper/to_method.rb +++ b/lib/solargraph/yard_map/mapper/to_method.rb @@ -28,7 +28,7 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = override_key = [closure.path, final_scope, name] final_visibility = VISIBILITY_OVERRIDE[override_key] final_visibility ||= VISIBILITY_OVERRIDE[[closure.path, final_scope]] - final_visibility ||= :private if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(name) + final_visibility ||= :private if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(name.to_sym) final_visibility ||= visibility final_visibility ||= :private if code_object.module_function? && final_scope == :instance final_visibility ||= :public if code_object.module_function? && final_scope == :class From 15efc9aab8be581605f2a74f880529b6d5f9ecd6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 28 Aug 2025 19:47:21 -0400 Subject: [PATCH 121/460] Mark with nocov --- lib/solargraph/doc_map.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 531cf03d7..87cf1b7f2 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -335,10 +335,12 @@ def resolve_path_to_gemspecs path # @param gemspec [Gem::Specification] # @return [Gem::Specification] def gemspec_or_preference gemspec + # :nocov: dormant feature return gemspec unless preference_map.key?(gemspec.name) return gemspec if gemspec.version == preference_map[gemspec.name].version change_gemspec_version gemspec, preference_map[gemspec.name].version + # :nocov: end # @param gemspec [Gem::Specification] From 97dc720dcffc45fdbea62315d8185b4934e8fbcc Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 28 Aug 2025 20:01:40 -0400 Subject: [PATCH 122/460] Fix merge issue --- .rubocop_todo.yml | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ae2961be6..a08ef75e3 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -64,7 +64,6 @@ Layout/BlockAlignment: Layout/ClosingHeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment. @@ -117,13 +116,6 @@ Layout/EmptyLines: - 'spec/pin/symbol_spec.rb' - 'spec/type_checker/levels/strict_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only -Layout/EmptyLinesAroundClassBody: - Exclude: - - 'lib/solargraph/rbs_map/core_map.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines @@ -225,7 +217,6 @@ Layout/HashAlignment: Layout/HeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - 'spec/yard_map/mapper/to_method_spec.rb' # This cop supports safe autocorrection (--autocorrect). @@ -387,6 +378,7 @@ Layout/SpaceBeforeComma: # SupportedStylesForEmptyBraces: space, no_space Layout/SpaceInsideBlockBraces: Exclude: + - 'lib/solargraph/api_map.rb' - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/diagnostics/update_errors.rb' - 'lib/solargraph/language_server/host.rb' @@ -510,7 +502,6 @@ Lint/DuplicateMethods: - 'lib/solargraph/pin/base.rb' - 'lib/solargraph/pin/signature.rb' - 'lib/solargraph/rbs_map.rb' - - 'lib/solargraph/rbs_map/core_map.rb' - 'lib/solargraph/source/chain/link.rb' # Configuration parameters: AllowComments, AllowEmptyLambdas. @@ -558,6 +549,7 @@ Lint/NonAtomicFileOperation: # This cop supports safe autocorrection (--autocorrect). Lint/ParenthesesAsGroupedExpression: Exclude: + - 'lib/solargraph.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'spec/language_server/host_spec.rb' - 'spec/source_map/clip_spec.rb' @@ -659,6 +651,7 @@ Lint/UselessAccessModifier: # This cop supports safe autocorrection (--autocorrect). Lint/UselessAssignment: Exclude: + - 'lib/solargraph/api_map.rb' - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - 'lib/solargraph/language_server/message/extended/document_gems.rb' @@ -742,7 +735,6 @@ Metrics/ClassLength: # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/CyclomaticComplexity: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/api_map/source_to_yard.rb' - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' @@ -1042,6 +1034,7 @@ RSpec/ExampleLength: # DisallowedExamples: works RSpec/ExampleWording: Exclude: + - 'spec/convention/struct_definition_spec.rb' - 'spec/pin/base_spec.rb' - 'spec/pin/method_spec.rb' @@ -1082,6 +1075,7 @@ RSpec/ImplicitExpect: RSpec/InstanceVariable: Exclude: - 'spec/api_map/config_spec.rb' + - 'spec/api_map_spec.rb' - 'spec/diagnostics/require_not_found_spec.rb' - 'spec/language_server/host/dispatch_spec.rb' - 'spec/language_server/host_spec.rb' @@ -1506,6 +1500,7 @@ Style/Documentation: - 'lib/solargraph/parser.rb' - 'lib/solargraph/parser/comment_ripper.rb' - 'lib/solargraph/parser/flow_sensitive_typing.rb' + - 'lib/solargraph/parser/node_methods.rb' - 'lib/solargraph/parser/node_processor/base.rb' - 'lib/solargraph/parser/parser_gem.rb' - 'lib/solargraph/parser/parser_gem/class_methods.rb' @@ -1646,6 +1641,7 @@ Style/FrozenStringLiteralComment: - 'lib/solargraph/parser.rb' - 'lib/solargraph/parser/comment_ripper.rb' - 'lib/solargraph/parser/flow_sensitive_typing.rb' + - 'lib/solargraph/parser/node_methods.rb' - 'lib/solargraph/parser/parser_gem.rb' - 'lib/solargraph/parser/snippet.rb' - 'lib/solargraph/pin/breakable.rb' @@ -1919,6 +1915,7 @@ Style/MethodDefParentheses: - 'lib/solargraph/location.rb' - 'lib/solargraph/parser/comment_ripper.rb' - 'lib/solargraph/parser/flow_sensitive_typing.rb' + - 'lib/solargraph/parser/node_methods.rb' - 'lib/solargraph/parser/node_processor/base.rb' - 'lib/solargraph/parser/parser_gem/flawed_builder.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' @@ -2121,15 +2118,10 @@ Style/RedundantFreeze: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/source_map/mapper.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowComments. -Style/RedundantInitialize: - Exclude: - - 'lib/solargraph/rbs_map/core_map.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: + - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/source_map/mapper.rb' @@ -2522,7 +2514,6 @@ YARD/MismatchName: Exclude: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/gem_pins.rb' - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/language_server/host/dispatch.rb' - 'lib/solargraph/language_server/request.rb' From a1f98ec0aca3015d8dbebddf9c677d1621ff1686 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 29 Aug 2025 08:34:47 -0400 Subject: [PATCH 123/460] Run plugin specs separately for perf insights I notice the plugin-using specs come in last while waiting for tests to finish - I thought it might be useful to run them separately as well as together so that we can see how that affects typechecking performance. --- .github/workflows/plugins.yml | 65 +++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index b0f22ec3e..3f306a166 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -1,4 +1,4 @@ -name: Plugin Backwards Compatibility Tests +name: Plugin on: push: @@ -10,7 +10,7 @@ permissions: contents: read jobs: - test: + regression: runs-on: ubuntu-latest steps: @@ -29,13 +29,72 @@ jobs: echo 'gem "solargraph-rails"' > .Gemfile echo 'gem "solargraph-rspec"' >> .Gemfile bundle install + bundle update rbs - name: Configure to use plugins run: | bundle exec solargraph config yq -yi '.plugins += ["solargraph-rails"]' .solargraph.yml yq -yi '.plugins += ["solargraph-rspec"]' .solargraph.yml - name: Install gem types - run: bundle exec rbs collection install + run: bundle exec rbs collection update + - name: Ensure typechecking still works + run: bundle exec solargraph typecheck --level typed + - name: Ensure specs still run + run: bundle exec rake spec + rails: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.0' + bundler-cache: false + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: yq + version: 1.0 + - name: Install gems + run: | + echo 'gem "solargraph-rails"' > .Gemfile + bundle install + bundle update rbs + - name: Configure to use plugins + run: | + bundle exec solargraph config + yq -yi '.plugins += ["solargraph-rails"]' .solargraph.yml + - name: Install gem types + run: bundle exec rbs collection update + - name: Ensure typechecking still works + run: bundle exec solargraph typecheck --level typed + - name: Ensure specs still run + run: bundle exec rake spec + rspec: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.0' + bundler-cache: false + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: yq + version: 1.0 + - name: Install gems + run: | + echo 'gem "solargraph-rspec"' >> .Gemfile + bundle install + bundle update rbs + - name: Configure to use plugins + run: | + bundle exec solargraph config + yq -yi '.plugins += ["solargraph-rspec"]' .solargraph.yml + - name: Install gem types + run: bundle exec rbs collection update - name: Ensure typechecking still works run: bundle exec solargraph typecheck --level typed - name: Ensure specs still run From 7fac0fcd5aca6d002acbd3003f54b43e341d9a0f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 29 Aug 2025 10:57:57 -0400 Subject: [PATCH 124/460] Improve comment --- lib/solargraph/pin/base.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 874b81568..719664e0a 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -188,7 +188,11 @@ def combine_return_type(other) return_type else all_items = return_type.items + other.return_type.items - if all_items.any? { |item| item.selfy? } && all_items.any? { |item| item.rooted_namespace == context.rooted_namespace } + # If we got a 'self' return type from something, but + # something else listed the type specifically, assume 'self' + # is the better choice + if all_items.any? { |item| item.selfy? } && + all_items.any? { |item| item.rooted_namespace == context.rooted_namespace } # assume this was a declaration that should have said 'self' all_items.delete_if { |item| item.rooted_namespace == context.rooted_namespace } end From a814572fccb17b2069e69ab6d57fc7d24a1686a6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 29 Aug 2025 11:03:41 -0400 Subject: [PATCH 125/460] Use less aggressive change --- lib/solargraph/pin/base.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 719664e0a..1b76106e8 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -188,13 +188,9 @@ def combine_return_type(other) return_type else all_items = return_type.items + other.return_type.items - # If we got a 'self' return type from something, but - # something else listed the type specifically, assume 'self' - # is the better choice - if all_items.any? { |item| item.selfy? } && - all_items.any? { |item| item.rooted_namespace == context.rooted_namespace } + if all_items.any? { |item| item.selfy? } && all_items.any? { |item| item.rooted_tag == context.reduce_class_type.rooted_tag } # assume this was a declaration that should have said 'self' - all_items.delete_if { |item| item.rooted_namespace == context.rooted_namespace } + all_items.delete_if { |item| item.rooted_tag == context.reduce_class_type.rooted_tag } end ComplexType.new(all_items) end From 706a70f120238d40c4c29d6540856970224c838b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 29 Aug 2025 11:10:23 -0400 Subject: [PATCH 126/460] Add more RBS interface core fills --- lib/solargraph/rbs_map/core_fills.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/solargraph/rbs_map/core_fills.rb b/lib/solargraph/rbs_map/core_fills.rb index 5066a529d..3bb32a0da 100644 --- a/lib/solargraph/rbs_map/core_fills.rb +++ b/lib/solargraph/rbs_map/core_fills.rb @@ -48,6 +48,24 @@ module CoreFills Solargraph::Pin::Reference::Include.new(name: '_ToAry', closure: Solargraph::Pin::Namespace.new(name: 'Array', source: :core_fill), generic_values: ['generic'], + source: :core_fill), + Solargraph::Pin::Reference::Include.new(name: '_ToAry', + closure: Solargraph::Pin::Namespace.new(name: 'Set', source: :core_fill), + generic_values: ['generic'], + source: :core_fill), + Solargraph::Pin::Reference::Include.new(name: '_Each', + closure: Solargraph::Pin::Namespace.new(name: 'Array', source: :core_fill), + generic_values: ['generic'], + source: :core_fill), + Solargraph::Pin::Reference::Include.new(name: '_Each', + closure: Solargraph::Pin::Namespace.new(name: 'Set', source: :core_fill), + generic_values: ['generic'], + source: :core_fill), + Solargraph::Pin::Reference::Include.new(name: '_ToS', + closure: Solargraph::Pin::Namespace.new(name: 'Object', source: :core_fill), + source: :core_fill), + Solargraph::Pin::Reference::Include.new(name: '_ToS', + closure: Solargraph::Pin::Namespace.new(name: 'String', source: :core_fill), source: :core_fill) ] From 447c778642edfef4adc62d2af3526f730417342c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 29 Aug 2025 12:05:14 -0400 Subject: [PATCH 127/460] Make 'self' types concrete while checking arguments --- lib/solargraph/type_checker.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index e0b23f56b..dd13d7ec7 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -411,6 +411,7 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum # @todo Some level (strong, I guess) should require the param here else argtype = argchain.infer(api_map, closure_pin, locals) + argtype = argtype.self_to_type(closure_pin.context) if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype) errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") return errors @@ -450,8 +451,10 @@ def kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, # @todo Some level (strong, I guess) should require the param here else ptype = data[:qualified] + ptype = ptype.self_to_type(pin.context) unless ptype.undefined? argtype = argchain.infer(api_map, block_pin, locals) + argtype = argtype.self_to_type(block_pin.context) if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype) result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") end @@ -477,7 +480,9 @@ def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kw kwargs.each_pair do |pname, argchain| next unless params.key?(pname.to_s) ptype = params[pname.to_s][:qualified] + ptype = ptype.self_to_type(pin.context) argtype = argchain.infer(api_map, block_pin, locals) + argtype = argtype.self_to_type(block_pin.context) if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype) result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}") end From 91103c60bc1d2d1247cf2ddd85e05ea0ed369d02 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 29 Aug 2025 12:44:55 -0400 Subject: [PATCH 128/460] ast, rubygems, and parser shims for strong typechecking --- rbs/fills/rubygems/0/spec_fetcher.rbs | 107 +++++++++++++ sig/shims/ast/2.4/ast.rbs | 2 +- sig/shims/parser/3.2.0.1/manifest.yaml | 7 + sig/shims/parser/3.2.0.1/parser.rbs | 201 +++++++++++++++++++++++++ sig/shims/parser/3.2.0.1/polyfill.rbs | 4 + 5 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 rbs/fills/rubygems/0/spec_fetcher.rbs create mode 100644 sig/shims/parser/3.2.0.1/manifest.yaml create mode 100644 sig/shims/parser/3.2.0.1/parser.rbs create mode 100644 sig/shims/parser/3.2.0.1/polyfill.rbs diff --git a/rbs/fills/rubygems/0/spec_fetcher.rbs b/rbs/fills/rubygems/0/spec_fetcher.rbs new file mode 100644 index 000000000..9914dc85d --- /dev/null +++ b/rbs/fills/rubygems/0/spec_fetcher.rbs @@ -0,0 +1,107 @@ +# +# SpecFetcher handles metadata updates from remote gem repositories. +# +class Gem::SpecFetcher + self.@fetcher: untyped + + @sources: untyped + + @update_cache: untyped + + @specs: untyped + + @latest_specs: untyped + + @prerelease_specs: untyped + + @caches: untyped + + @fetcher: untyped + + include Gem::UserInteraction + + include Gem::Text + + attr_reader latest_specs: untyped + + attr_reader sources: untyped + + attr_reader specs: untyped + + attr_reader prerelease_specs: untyped + + # + # Default fetcher instance. Use this instead of ::new to reduce object + # allocation. + # + def self.fetcher: () -> untyped + + def self.fetcher=: (untyped fetcher) -> untyped + + # + # Creates a new SpecFetcher. Ordinarily you want to use the default fetcher + # from Gem::SpecFetcher::fetcher which uses the Gem.sources. + # + # If you need to retrieve specifications from a different `source`, you can send + # it as an argument. + # + def initialize: (?untyped? sources) -> void + + # + # Find and fetch gem name tuples that match `dependency`. + # + # If `matching_platform` is false, gems for all platforms are returned. + # + def search_for_dependency: (untyped dependency, ?bool matching_platform) -> ::Array[untyped] + + # + # Return all gem name tuples who's names match `obj` + # + def detect: (?::Symbol type) { (untyped) -> untyped } -> untyped + + # + # Find and fetch specs that match `dependency`. + # + # If `matching_platform` is false, gems for all platforms are returned. + # + def spec_for_dependency: (untyped dependency, ?bool matching_platform) -> ::Array[untyped] + + # + # Suggests gems based on the supplied `gem_name`. Returns an array of + # alternative gem names. + # + def suggest_gems_from_name: (untyped gem_name, ?::Symbol type, ?::Integer num_results) -> (::Array[untyped] | untyped) + + # + # Returns a list of gems available for each source in Gem::sources. + # + # `type` can be one of 3 values: :released => Return the list of all released + # specs :complete => Return the list of all specs :latest => Return the + # list of only the highest version of each gem :prerelease => Return the list of + # all prerelease only specs + # + def available_specs: (untyped type) -> ::Array[untyped] + + def tuples_for: (untyped source, untyped type, ?bool gracefully_ignore) -> untyped +end diff --git a/sig/shims/ast/2.4/ast.rbs b/sig/shims/ast/2.4/ast.rbs index e7fb8975e..68f26024b 100644 --- a/sig/shims/ast/2.4/ast.rbs +++ b/sig/shims/ast/2.4/ast.rbs @@ -10,7 +10,7 @@ module AST class Node public - attr_reader children: Array[Node] + attr_reader children: Array[self] attr_reader hash: String attr_reader type: Symbol diff --git a/sig/shims/parser/3.2.0.1/manifest.yaml b/sig/shims/parser/3.2.0.1/manifest.yaml new file mode 100644 index 000000000..f00038381 --- /dev/null +++ b/sig/shims/parser/3.2.0.1/manifest.yaml @@ -0,0 +1,7 @@ +# manifest.yaml describes dependencies which do not appear in the gemspec. +# If this gem includes such dependencies, comment-out the following lines and +# declare the dependencies. +# If all dependencies appear in the gemspec, you should remove this file. +# +dependencies: + - name: ast diff --git a/sig/shims/parser/3.2.0.1/parser.rbs b/sig/shims/parser/3.2.0.1/parser.rbs new file mode 100644 index 000000000..5fac38b0f --- /dev/null +++ b/sig/shims/parser/3.2.0.1/parser.rbs @@ -0,0 +1,201 @@ +module Parser + CurrentRuby: Parser::Base + + class SyntaxError < StandardError + end + class UnknownEncodingInMagicComment < StandardError + end + + class Base < Racc::Parser + def version: -> Integer + def self.parse: (String string, ?String file, ?Integer line) -> Parser::AST::Node? + def self.parse_with_comments: (String string, ?String file, ?Integer line) -> [Parser::AST::Node?, Array[Source::Comment]] + def parse: (Parser::Source::Buffer source_buffer) -> Parser::AST::Node? + end + + class Ruby18 < Base + end + class Ruby19 < Base + end + class Ruby20 < Base + end + class Ruby21 < Base + end + class Ruby22 < Base + end + class Ruby23 < Base + end + class Ruby24 < Base + end + class Ruby25 < Base + end + class Ruby26 < Base + end + class Ruby27 < Base + end + class Ruby30 < Base + end + class Ruby31 < Base + end + class Ruby32 < Base + end + class Ruby33 < Base + end + + module AST + class Node < ::AST::Node + attr_reader location: Source::Map + alias loc location + + def children: () -> Array[self] + end + + class Processor + module Mixin + def process: (Node? node) -> Node? + end + + include Mixin + end + end + + module Source + class Range + attr_reader source_buffer: Buffer + attr_reader begin_pos: Integer + attr_reader end_pos: Integer + def begin: () -> Range + def end: () -> Range + def size: () -> Integer + alias length size + def line: () -> Integer + alias first_line line + def column: () -> Integer + def last_line: () -> Integer + def last_column: () -> Integer + def column_range: () -> ::Range[Integer] + def source_line: () -> String + def source: () -> String + def with: (?begin_pos: Integer, ?end_pos: Integer) -> Range + def adjust: (?begin_pos: Integer, ?end_pos: Integer) -> Range + def resize: (Integer new_size) -> Range + def join: (Range other) -> Range + def intersect: (Range other) -> Range? + def disjoint?: (Range other) -> bool + def overlaps?: (Range other) -> bool + def contains?: (Range other) -> bool + def contained?: (Range other) -> bool + def crossing?: (Range other) -> bool + def empty?: () -> bool + end + + ## + # A buffer with source code. {Buffer} contains the source code itself, + # associated location information (name and first line), and takes care + # of encoding. + # + # A source buffer is immutable once populated. + # + # @!attribute [r] name + # Buffer name. If the buffer was created from a file, the name corresponds + # to relative path to the file. + # @return [String] buffer name + # + # @!attribute [r] first_line + # First line of the buffer, 1 by default. + # @return [Integer] first line + # + # @api public + # + class Buffer + attr_reader name: String + attr_reader first_line: Integer + + def self.recognize_encoding: (String) -> Encoding + def self.reencode_string: (String) -> String + + def initialize: (untyped name, ?Integer first_line, ?source: untyped) -> void + def read: () -> self + def source: () -> String + def source=: (String) -> String + def raw_source: (String) -> String + def decompose_position: (Integer) -> [Integer, Integer] + def source_lines: () -> Array[String] + def source_line: (Integer) -> String + def line_range: (Integer) -> ::Range[Integer] + def source_range: () -> ::Range[Integer] + def last_line: () -> Integer + end + + class TreeRewriter + def replace: (Range range, String content) -> self + def remove: (Range range) -> self + def insert_before: (Range range, String content) -> self + def insert_after: (Range range, String content) -> self + end + + class Map + attr_reader node: AST::Node | nil + attr_reader expression: Range + def line: () -> Integer + def first_line: () -> Integer + def last_line: () -> Integer + def column: () -> Integer + def last_column: () -> Integer + end + + class Map::Collection < Map + attr_reader begin: Range? + attr_reader end: Range? + end + + class Map::Condition < Map + attr_reader keyword: Range + attr_reader begin: Range? + attr_reader else: Range? + attr_reader end: Range + end + + class Map::Heredoc < Map + attr_reader heredoc_body: Range + attr_reader heredoc_end: Range + end + + class Map::Keyword < Map + attr_reader keyword: Range + attr_reader begin: Range? + attr_reader end: Range? + end + + class Map::MethodDefinition < Map + attr_reader keyword: Range + attr_reader operator: Range? + attr_reader name: Range? + attr_reader end: Range? + attr_reader assignment: Range? + end + + class Map::Operator < Map + attr_reader operator: Range? + end + + class Map::Send < Map + attr_reader dot: Range? + attr_reader selector: Range + attr_reader operator: Range? + attr_reader begin: Range? + attr_reader end: Range? + end + + class Map::Ternary < Map + attr_reader question: Range? + attr_reader colon: Range + end + + class Comment + attr_reader text: String + attr_reader location: Map + alias loc location + end + end +end diff --git a/sig/shims/parser/3.2.0.1/polyfill.rbs b/sig/shims/parser/3.2.0.1/polyfill.rbs new file mode 100644 index 000000000..2e8c12487 --- /dev/null +++ b/sig/shims/parser/3.2.0.1/polyfill.rbs @@ -0,0 +1,4 @@ +module Racc + class Parser + end +end From 354f169ac68a1764fe53af9b6af1d6ca5b455dd7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 29 Aug 2025 12:49:37 -0400 Subject: [PATCH 129/460] More annotations towards strong type checking solargraph code --- lib/solargraph/complex_type.rb | 1 + lib/solargraph/complex_type/unique_type.rb | 1 + lib/solargraph/doc_map.rb | 2 ++ .../language_server/message/extended/check_gem_version.rb | 2 +- lib/solargraph/page.rb | 1 + lib/solargraph/parser/node_methods.rb | 2 +- lib/solargraph/parser/parser_gem/node_methods.rb | 2 +- lib/solargraph/pin/common.rb | 5 ++--- lib/solargraph/pin/delegated_method.rb | 1 + lib/solargraph/position.rb | 1 + lib/solargraph/source_map/mapper.rb | 2 ++ 11 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index d36ae8b22..274b16a6a 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -204,6 +204,7 @@ def generic? any?(&:generic?) end + # @return [self] def simplify_literals ComplexType.new(map(&:simplify_literals)) end diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 3f935c724..21cf6934b 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -101,6 +101,7 @@ def to_s tag end + # @return [self] def simplify_literals transform do |t| next t unless t.literal? diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 56f51973f..5fe5e03f9 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -177,8 +177,10 @@ def load_serialized_gem_pins @uncached_yard_gemspecs = [] @uncached_rbs_collection_gemspecs = [] with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v } + # @sg-ignore Wrong argument type for Hash.[]: arg_0 expected _ToHash), undefined>, received Array)> # @type [Array] paths = Hash[without_gemspecs].keys + # @sg-ignore Wrong argument type for Hash.[]: arg_0 expected _ToHash), undefined>, received Array)> # @type [Array] gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a diff --git a/lib/solargraph/language_server/message/extended/check_gem_version.rb b/lib/solargraph/language_server/message/extended/check_gem_version.rb index 2e80f40c6..ead1eeaf2 100644 --- a/lib/solargraph/language_server/message/extended/check_gem_version.rb +++ b/lib/solargraph/language_server/message/extended/check_gem_version.rb @@ -83,7 +83,7 @@ def available @fetched = true begin @available ||= begin - # @sg-ignore + # @sg-ignore Variable type could not be inferred for tuple # @type [Gem::Dependency, nil] tuple = CheckGemVersion.fetcher.search_for_dependency(Gem::Dependency.new('solargraph')).flatten.first if tuple.nil? diff --git a/lib/solargraph/page.rb b/lib/solargraph/page.rb index 12782da90..5d879bbe1 100644 --- a/lib/solargraph/page.rb +++ b/lib/solargraph/page.rb @@ -27,6 +27,7 @@ def initialize locals, render_method end # @param text [String] + # @sg-ignore https://github.com/lsegal/yard/pull/1615 # @return [String] def htmlify text # @type [String] diff --git a/lib/solargraph/parser/node_methods.rb b/lib/solargraph/parser/node_methods.rb index 5d3d1079a..f33a924c1 100644 --- a/lib/solargraph/parser/node_methods.rb +++ b/lib/solargraph/parser/node_methods.rb @@ -74,7 +74,7 @@ def process node # @abstract # @param node [Parser::AST::Node] - # @return [Hash{Parser::AST::Node => Source::Chain}] + # @return [Hash{Symbol => Source::Chain}] def convert_hash node raise NotImplementedError end diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 3c45599b5..1746e5eae 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -120,7 +120,7 @@ def drill_signature node, signature end # @param node [Parser::AST::Node] - # @return [Hash{Parser::AST::Node => Chain}] + # @return [Hash{Symbol => Chain}] def convert_hash node return {} unless Parser.is_ast_node?(node) return convert_hash(node.children[0]) if node.type == :kwsplat diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 69bac1cd2..062099ee4 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -3,16 +3,15 @@ module Solargraph module Pin module Common - # @!method closure - # @abstract - # @return [Pin::Closure, nil] # @!method source # @abstract # @return [Source, nil] + # @type @closure [Pin::Closure, nil] # @return [Location] attr_reader :location + # @sg-ignore Solargraph::Pin::Common#closure return type could not be inferred # @return [Pin::Closure, nil] def closure Solargraph.assert_or_log(:closure, "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") unless @closure diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index bcf5b5912..9483fb058 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -51,6 +51,7 @@ def type_location %i[typify realize infer probe].each do |method| # @param api_map [ApiMap] define_method(method) do |api_map| + # @sg-ignore Unresolved call to resolve_method resolve_method(api_map) # @sg-ignore Need to set context correctly in define_method blocks @resolved_method ? @resolved_method.send(method, api_map) : super(api_map) diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 1197038ef..2faa0a99b 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -58,6 +58,7 @@ def inspect # @return [Integer] def self.to_offset text, position return 0 if text.empty? + # @sg-ignore Unresolved call to + on Integer text.lines[0...position.line].sum(&:length) + position.character end diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 5fdcb9fe6..18fdf1f88 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -70,6 +70,7 @@ def closure_at(position) # @param comment [String] # @return [void] def process_comment source_position, comment_position, comment + # @sg-ignore Wrong argument type for String#=~: object expected String::_MatchAgainst, received Regexp return unless comment.encode('UTF-8', invalid: :replace, replace: '?') =~ DIRECTIVE_REGEXP cmnt = remove_inline_comment_hashes(comment) parse = Solargraph::Source.parse_docstring(cmnt) @@ -244,6 +245,7 @@ def remove_inline_comment_hashes comment # @return [void] def process_comment_directives + # @sg-ignore Wrong argument type for String#=~: object expected String::_MatchAgainst, received Regexp return unless @code.encode('UTF-8', invalid: :replace, replace: '?') =~ DIRECTIVE_REGEXP code_lines = @code.lines @source.associated_comments.each do |line, comments| From 36383596fa21593bb6db0f7fffa343b37141aa75 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 29 Aug 2025 12:51:12 -0400 Subject: [PATCH 130/460] strict -> strong --- .github/workflows/typecheck.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 4cde97763..f40977acf 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -18,7 +18,7 @@ permissions: jobs: solargraph_typed: - name: Solargraph / typed + name: Solargraph / strong runs-on: ubuntu-latest @@ -36,4 +36,4 @@ jobs: - name: Install gem types run: bundle exec rbs collection install - name: Typecheck self - run: SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strict \ No newline at end of file + run: SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strong From b4a2ab1443bab5bc083339ffcdbc23d81c463959 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 29 Aug 2025 12:53:22 -0400 Subject: [PATCH 131/460] Also change default in Rakefile --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 0e20d1280..c83d9ab6b 100755 --- a/Rakefile +++ b/Rakefile @@ -9,7 +9,7 @@ task :console do end desc "Run the type checker" -task typecheck: [:typecheck_strict] +task typecheck: [:typecheck_strong] desc "Run the type checker at typed level - return code issues provable without annotations being correct" task :typecheck_typed do From 6acfa0c54a806da203daf30765c7a99a67066f16 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 30 Aug 2025 09:06:14 -0400 Subject: [PATCH 132/460] RuboCop todo file stability To avoid merge conflicts and contributors having to deal with non-intuitive RuboCop todo changes: * Lock down development versions of RuboCop and plugins so that unrelated PRs aren't affected by newly implemented RuboCop rules. * Exclude rule entirely if more than 5 files violate it today, so that PRs are less likely to cause todo file changes unless they are specifically targeted at cleanup. * Clarify guidance on RuboCop todo file in CI error message. * Fix to hopefully ensure guidance always appears in CI error message. --- .github/workflows/linting.yml | 6 +- .rubocop.yml | 1 - .rubocop_todo.yml | 1588 ++------------------------------- solargraph.gemspec | 13 +- 4 files changed, 98 insertions(+), 1510 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 8abbf51ef..aa22ce22c 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -115,11 +115,13 @@ jobs: - name: Run RuboCop against todo file continue-on-error: true run: | - bundle exec rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp + cmd="bundle exec rubocop --auto-gen-config --exclude-limit=5 --no-offense-counts --no-auto-gen-timestampxb*com" + ${cmd:?} + set +e if [ -n "$(git status --porcelain)" ] then git status --porcelain git diff -u . - >&2 echo "Please fix deltas if bad or run 'bundle exec rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp' and push up changes if good" + >&2 echo "Please address any new issues, then run '${cmd:?}' and push up any improvements" exit 1 fi diff --git a/.rubocop.yml b/.rubocop.yml index a73324db2..c7643c3c6 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -34,7 +34,6 @@ Metrics/ParameterLists: Max: 7 CountKeywordArgs: false - # we tend to use @@ and the risk doesn't seem high Style/ClassVars: Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f3f0069f3..bf6b2272a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,5 +1,5 @@ # This configuration was generated by -# `rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` +# `rubocop --auto-gen-config --exclude-limit 5 --no-offense-counts --no-auto-gen-timestamp` # using RuboCop version 1.79.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. @@ -83,23 +83,7 @@ Layout/CommentIndentation: # This cop supports safe autocorrection (--autocorrect). Layout/ElseAlignment: - Exclude: - - 'lib/solargraph.rb' - - 'lib/solargraph/api_map/store.rb' - - 'lib/solargraph/diagnostics/rubocop.rb' - - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/block_node.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/namespace.rb' - - 'lib/solargraph/rbs_map.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker/rules.rb' - - 'lib/solargraph/yard_map/mapper.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines. @@ -111,18 +95,7 @@ Layout/EmptyLineBetweenDefs: # This cop supports safe autocorrection (--autocorrect). Layout/EmptyLines: - Exclude: - - 'lib/solargraph/bench.rb' - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/doc_map.rb' - - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - - 'lib/solargraph/language_server/message/initialize.rb' - - 'lib/solargraph/pin/delegated_method.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'spec/complex_type_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/symbol_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. @@ -142,23 +115,7 @@ Layout/EmptyLinesAroundModuleBody: # Configuration parameters: EnforcedStyleAlignWith, Severity. # SupportedStylesAlignWith: keyword, variable, start_of_line Layout/EndAlignment: - Exclude: - - 'lib/solargraph.rb' - - 'lib/solargraph/api_map/store.rb' - - 'lib/solargraph/diagnostics/rubocop.rb' - - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/block_node.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/namespace.rb' - - 'lib/solargraph/rbs_map.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker/rules.rb' - - 'lib/solargraph/yard_map/mapper.rb' + Enabled: false # Configuration parameters: EnforcedStyle. # SupportedStyles: native, lf, crlf @@ -172,13 +129,7 @@ Layout/EndOfLine: # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. Layout/ExtraSpacing: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb' - - 'lib/solargraph/pin/closure.rb' - - 'lib/solargraph/pin/local_variable.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/type_checker.rb' - - 'spec/spec_helper.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. @@ -203,22 +154,7 @@ Layout/FirstArrayElementIndentation: # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_braces Layout/FirstHashElementIndentation: - Exclude: - - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - - 'lib/solargraph/language_server/message/extended/document_gems.rb' - - 'lib/solargraph/language_server/message/text_document/completion.rb' - - 'lib/solargraph/language_server/message/text_document/rename.rb' - - 'lib/solargraph/language_server/message/text_document/signature_help.rb' - - 'lib/solargraph/pin/base_variable.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/message/text_document/formatting_spec.rb' - - 'spec/language_server/message/text_document/hover_spec.rb' - - 'spec/language_server/message/text_document/rename_spec.rb' - - 'spec/language_server/message/text_document/type_definition_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. @@ -239,27 +175,7 @@ Layout/HeredocIndentation: # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: Width, AllowedPatterns. Layout/IndentationWidth: - Exclude: - - 'lib/solargraph.rb' - - 'lib/solargraph/api_map/store.rb' - - 'lib/solargraph/diagnostics/rubocop.rb' - - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/block_node.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/namespace.rb' - - 'lib/solargraph/rbs_map.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker.rb' - - 'lib/solargraph/type_checker/rules.rb' - - 'lib/solargraph/yard_map/mapper.rb' - - 'spec/api_map/config_spec.rb' - - 'spec/shell_spec.rb' - - 'spec/source_map/mapper_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowDoxygenCommentStyle, AllowGemfileRubyComment, AllowRBSInlineAnnotation, AllowSteepAnnotation. @@ -289,14 +205,7 @@ Layout/MultilineMethodCallBraceLayout: # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: aligned, indented, indented_relative_to_receiver Layout/MultilineMethodCallIndentation: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/diagnostics/type_check.rb' - - 'lib/solargraph/language_server/message/completion_item/resolve.rb' - - 'lib/solargraph/language_server/message/text_document/hover.rb' - - 'lib/solargraph/library.rb' - - 'lib/solargraph/pin/search.rb' - - 'lib/solargraph/type_checker.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. @@ -316,13 +225,7 @@ Layout/SpaceAfterComma: # Configuration parameters: EnforcedStyle. # SupportedStyles: space, no_space Layout/SpaceAroundEqualsInParameterDefault: - Exclude: - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/base_variable.rb' - - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/closure.rb' - - 'lib/solargraph/pin/local_variable.rb' - - 'lib/solargraph/pin/parameter.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). Layout/SpaceAroundKeyword: @@ -334,56 +237,14 @@ Layout/SpaceAroundKeyword: # SupportedStylesForExponentOperator: space, no_space # SupportedStylesForRationalLiterals: space, no_space Layout/SpaceAroundOperators: - Exclude: - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin/local_variable.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source/change.rb' - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/workspace/config.rb' - - 'spec/library_spec.rb' - - 'spec/yard_map/mapper_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. # SupportedStyles: space, no_space # SupportedStylesForEmptyBraces: space, no_space Layout/SpaceBeforeBlockBraces: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/store.rb' - - 'lib/solargraph/diagnostics/rubocop.rb' - - 'lib/solargraph/diagnostics/update_errors.rb' - - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/and_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/if_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/chain/class_variable.rb' - - 'lib/solargraph/source/chain/constant.rb' - - 'lib/solargraph/source/chain/global_variable.rb' - - 'lib/solargraph/source/chain/instance_variable.rb' - - 'lib/solargraph/source/chain/variable.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/workspace/config.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/library_spec.rb' - - 'spec/pin/constant_spec.rb' - - 'spec/pin/instance_variable_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). Layout/SpaceBeforeComma: @@ -395,28 +256,7 @@ Layout/SpaceBeforeComma: # SupportedStyles: space, no_space # SupportedStylesForEmptyBraces: space, no_space Layout/SpaceInsideBlockBraces: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/store.rb' - - 'lib/solargraph/diagnostics/update_errors.rb' - - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/and_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/if_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/chain/class_variable.rb' - - 'lib/solargraph/source/chain/global_variable.rb' - - 'lib/solargraph/source/chain/instance_variable.rb' - - 'lib/solargraph/source/chain/variable.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/library_spec.rb' - - 'spec/pin/constant_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. @@ -455,13 +295,7 @@ Lint/AmbiguousBlockAssociation: # This cop supports safe autocorrection (--autocorrect). Lint/AmbiguousOperator: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/language_server/message/workspace/did_change_watched_files.rb' - - 'lib/solargraph/parser/parser_gem/class_methods.rb' - - 'lib/solargraph/pin/constant.rb' - - 'lib/solargraph/pin/method.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). Lint/AmbiguousOperatorPrecedence: @@ -472,15 +306,7 @@ Lint/AmbiguousOperatorPrecedence: # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: RequireParenthesesForMethodChains. Lint/AmbiguousRange: - Exclude: - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source/change.rb' - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'spec/library_spec.rb' + Enabled: false # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowSafeAssignment. @@ -514,14 +340,7 @@ Lint/DuplicateBranch: - 'lib/solargraph/rbs_map/conversions.rb' Lint/DuplicateMethods: - Exclude: - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/location.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/signature.rb' - - 'lib/solargraph/rbs_map.rb' - - 'lib/solargraph/rbs_map/core_map.rb' - - 'lib/solargraph/source/chain/link.rb' + Enabled: false # Configuration parameters: AllowComments, AllowEmptyLambdas. Lint/EmptyBlock: @@ -530,13 +349,7 @@ Lint/EmptyBlock: # Configuration parameters: AllowComments. Lint/EmptyClass: - Exclude: - - 'spec/fixtures/rubocop-validation-error/app.rb' - - 'spec/fixtures/workspace-with-gemfile/lib/other.rb' - - 'spec/fixtures/workspace/lib/other.rb' - - 'spec/fixtures/workspace/lib/something.rb' - - 'spec/fixtures/workspace_folders/folder1/app.rb' - - 'spec/fixtures/workspace_folders/folder2/app.rb' + Enabled: false # Configuration parameters: AllowComments. Lint/EmptyFile: @@ -635,31 +448,7 @@ Lint/UnusedBlockArgument: # Configuration parameters: AutoCorrect, AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions. # NotImplementedExceptions: NotImplementedError Lint/UnusedMethodArgument: - Exclude: - - 'lib/solargraph.rb' - - 'lib/solargraph/complex_type/type_methods.rb' - - 'lib/solargraph/convention/base.rb' - - 'lib/solargraph/diagnostics/base.rb' - - 'lib/solargraph/diagnostics/update_errors.rb' - - 'lib/solargraph/doc_map.rb' - - 'lib/solargraph/pin/namespace.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source/chain.rb' - - 'lib/solargraph/source/chain/block_symbol.rb' - - 'lib/solargraph/source/chain/block_variable.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/chain/class_variable.rb' - - 'lib/solargraph/source/chain/constant.rb' - - 'lib/solargraph/source/chain/global_variable.rb' - - 'lib/solargraph/source/chain/hash.rb' - - 'lib/solargraph/source/chain/head.rb' - - 'lib/solargraph/source/chain/instance_variable.rb' - - 'lib/solargraph/source/chain/link.rb' - - 'lib/solargraph/source/chain/literal.rb' - - 'lib/solargraph/source/chain/variable.rb' - - 'lib/solargraph/source/chain/z_super.rb' - - 'spec/doc_map_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AutoCorrect, ContextCreatingMethods, MethodCreatingMethods. @@ -670,30 +459,7 @@ Lint/UselessAccessModifier: # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AutoCorrect. Lint/UselessAssignment: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/doc_map.rb' - - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - - 'lib/solargraph/language_server/message/extended/document_gems.rb' - - 'lib/solargraph/language_server/message/text_document/completion.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker.rb' - - 'spec/fixtures/long_squiggly_heredoc.rb' - - 'spec/fixtures/rubocop-unused-variable-error/app.rb' - - 'spec/fixtures/unicode.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/library_spec.rb' - - 'spec/pin/namespace_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source_map/mapper_spec.rb' + Enabled: false Lint/UselessConstantScoping: Exclude: @@ -707,43 +473,16 @@ Lint/UselessMethodDefinition: # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max. Metrics/AbcSize: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/source_to_yard.rb' - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/doc_map.rb' - - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/language_server/message/initialize.rb' - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker.rb' + Enabled: false -# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. # AllowedMethods: refine Metrics/BlockLength: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/source_to_yard.rb' - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/convention/struct_definition.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/type_checker.rb' + Max: 54 -# Configuration parameters: CountBlocks, CountModifierForms, Max. +# Configuration parameters: CountBlocks, CountModifierForms. Metrics/BlockNesting: - Exclude: - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/type_checker.rb' + Max: 5 # Configuration parameters: CountComments, Max, CountAsOne. Metrics/ClassLength: @@ -755,33 +494,15 @@ Metrics/ClassLength: # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/CyclomaticComplexity: - Exclude: - - 'lib/solargraph/api_map/source_to_yard.rb' - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker.rb' + Enabled: false # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/convention/struct_definition.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source_map/mapper.rb' + Enabled: false -# Configuration parameters: CountComments, Max, CountAsOne. +# Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: - Exclude: - - 'lib/solargraph/complex_type/type_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin_cache.rb' + Max: 169 # Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters. Metrics/ParameterLists: @@ -794,13 +515,7 @@ Metrics/ParameterLists: # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/PerceivedComplexity: - Exclude: - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker.rb' + Enabled: false Naming/AccessorMethodName: Exclude: @@ -823,41 +538,18 @@ Naming/HeredocDelimiterNaming: # Configuration parameters: EnforcedStyleForLeadingUnderscores. # SupportedStylesForLeadingUnderscores: disallowed, required, optional Naming/MemoizedInstanceVariableName: - Exclude: - - 'lib/solargraph/complex_type/type_methods.rb' - - 'lib/solargraph/convention/gemfile.rb' - - 'lib/solargraph/convention/gemspec.rb' - - 'lib/solargraph/convention/rakefile.rb' - - 'lib/solargraph/doc_map.rb' - - 'lib/solargraph/rbs_map.rb' - - 'lib/solargraph/workspace.rb' + Enabled: false # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to Naming/MethodParameterName: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/range.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/yard_map/mapper/to_method.rb' - - 'lib/solargraph/yard_map/to_method.rb' + Enabled: false # Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates. # AllowedMethods: call # WaywardPredicates: nonzero? Naming/PredicateMethod: - Exclude: - - 'lib/solargraph/api_map/store.rb' - - 'lib/solargraph/convention/data_definition.rb' - - 'lib/solargraph/convention/struct_definition.rb' - - 'lib/solargraph/language_server/progress.rb' - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/node_processor/base.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/local_variable.rb' - - 'lib/solargraph/workspace.rb' + Enabled: false # Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs. # NamePrefix: is_, has_, have_, does_ @@ -912,16 +604,7 @@ RSpec/BeforeAfterAll: # Configuration parameters: Prefixes, AllowedPatterns. # Prefixes: when, with, without RSpec/ContextWording: - Exclude: - - 'spec/complex_type_spec.rb' - - 'spec/library_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/pin/symbol_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker/levels/strong_spec.rb' - - 'spec/type_checker/levels/typed_spec.rb' + Enabled: false # Configuration parameters: IgnoredMetadata. RSpec/DescribeClass: @@ -938,81 +621,11 @@ RSpec/DescribeClass: # Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. # SupportedStyles: described_class, explicit RSpec/DescribedClass: - Exclude: - - 'spec/api_map/cache_spec.rb' - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/api_map/store_spec.rb' - - 'spec/api_map_spec.rb' - - 'spec/diagnostics/base_spec.rb' - - 'spec/diagnostics/require_not_found_spec.rb' - - 'spec/diagnostics/rubocop_helpers_spec.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/diagnostics/type_check_spec.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/diagnostics_spec.rb' - - 'spec/doc_map_spec.rb' - - 'spec/gem_pins_spec.rb' - - 'spec/language_server/host/diagnoser_spec.rb' - - 'spec/language_server/host/dispatch_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/message/text_document/formatting_spec.rb' - - 'spec/language_server/message/text_document/hover_spec.rb' - - 'spec/language_server/message/text_document/rename_spec.rb' - - 'spec/language_server/message/text_document/type_definition_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/message_spec.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - - 'spec/language_server/uri_helpers_spec.rb' - - 'spec/library_spec.rb' - - 'spec/logging_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/parser/node_processor_spec.rb' - - 'spec/parser_spec.rb' - - 'spec/pin/base_spec.rb' - - 'spec/pin/delegated_method_spec.rb' - - 'spec/pin/instance_variable_spec.rb' - - 'spec/pin/keyword_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/pin/namespace_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/pin/search_spec.rb' - - 'spec/pin/symbol_spec.rb' - - 'spec/position_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/rbs_map_spec.rb' - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/chain/global_variable_spec.rb' - - 'spec/source/chain/head_spec.rb' - - 'spec/source/chain/instance_variable_spec.rb' - - 'spec/source/chain/z_super_spec.rb' - - 'spec/source/change_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source/updater_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' - - 'spec/type_checker/checks_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker/rules_spec.rb' - - 'spec/type_checker_spec.rb' - - 'spec/workspace/config_spec.rb' - - 'spec/workspace_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' - - 'spec/yard_map/mapper_spec.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AutoCorrect. -RSpec/EmptyExampleGroup: + Enabled: false + +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AutoCorrect. +RSpec/EmptyExampleGroup: Exclude: - 'spec/convention_spec.rb' @@ -1023,33 +636,7 @@ RSpec/EmptyLineAfterFinalLet: # Configuration parameters: Max, CountAsOne. RSpec/ExampleLength: - Exclude: - - 'spec/api_map_spec.rb' - - 'spec/complex_type_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/message/text_document/hover_spec.rb' - - 'spec/language_server/message/text_document/rename_spec.rb' - - 'spec/language_server/message/text_document/type_definition_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/library_spec.rb' - - 'spec/parser/flow_sensitive_typing_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/parser/node_processor_spec.rb' - - 'spec/pin/base_variable_spec.rb' - - 'spec/pin/delegated_method_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: CustomTransform, IgnoredWords, DisallowedExamples. @@ -1077,15 +664,7 @@ RSpec/ExpectActual: # Configuration parameters: EnforcedStyle. # SupportedStyles: implicit, each, example RSpec/HookArgument: - Exclude: - - 'spec/api_map/config_spec.rb' - - 'spec/diagnostics/require_not_found_spec.rb' - - 'spec/language_server/host/dispatch_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/workspace/config_spec.rb' - - 'spec/workspace_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: . @@ -1095,13 +674,7 @@ RSpec/ImplicitExpect: # Configuration parameters: AssignmentOnly. RSpec/InstanceVariable: - Exclude: - - 'spec/api_map/config_spec.rb' - - 'spec/api_map_spec.rb' - - 'spec/diagnostics/require_not_found_spec.rb' - - 'spec/language_server/host/dispatch_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/protocol_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). RSpec/LeadingSubject: @@ -1127,89 +700,17 @@ RSpec/MissingExampleGroupArgument: Exclude: - 'spec/diagnostics/rubocop_helpers_spec.rb' -# Configuration parameters: Max. RSpec/MultipleExpectations: - Exclude: - - 'spec/api_map/cache_spec.rb' - - 'spec/api_map/config_spec.rb' - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/api_map/store_spec.rb' - - 'spec/api_map_spec.rb' - - 'spec/complex_type_spec.rb' - - 'spec/convention/struct_definition_spec.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/diagnostics/type_check_spec.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/diagnostics_spec.rb' - - 'spec/doc_map_spec.rb' - - 'spec/gem_pins_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/rename_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/language_server/transport/adapter_spec.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - - 'spec/library_spec.rb' - - 'spec/parser/flow_sensitive_typing_spec.rb' - - 'spec/parser/node_chainer_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/parser/node_processor_spec.rb' - - 'spec/pin/base_spec.rb' - - 'spec/pin/base_variable_spec.rb' - - 'spec/pin/constant_spec.rb' - - 'spec/pin/instance_variable_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/pin/namespace_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/position_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/rbs_map_spec.rb' - - 'spec/shell_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/chain/global_variable_spec.rb' - - 'spec/source/chain/head_spec.rb' - - 'spec/source/chain/instance_variable_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map/node_processor_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' - - 'spec/type_checker/checks_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker/levels/strong_spec.rb' - - 'spec/type_checker/levels/typed_spec.rb' - - 'spec/type_checker/rules_spec.rb' - - 'spec/workspace/config_spec.rb' - - 'spec/workspace_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' - - 'spec/yard_map/mapper_spec.rb' + Max: 14 -# Configuration parameters: Max, AllowedGroups. +# Configuration parameters: AllowedGroups. RSpec/NestedGroups: - Exclude: - - 'spec/complex_type_spec.rb' + Max: 4 # Configuration parameters: AllowedPatterns. # AllowedPatterns: ^expect_, ^assert_ RSpec/NoExpectationExample: - Exclude: - - 'spec/language_server/protocol_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/pin/block_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/type_checker/checks_spec.rb' - - 'spec/type_checker/levels/typed_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. @@ -1220,18 +721,7 @@ RSpec/NotToNot: - 'spec/rbs_map/core_map_spec.rb' RSpec/PendingWithoutReason: - Exclude: - - 'spec/api_map_spec.rb' - - 'spec/complex_type_spec.rb' - - 'spec/parser/node_chainer_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' + Enabled: false # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Strict, EnforcedStyle, AllowedExplicitMatchers. @@ -1251,16 +741,7 @@ RSpec/RemoveConst: - 'spec/diagnostics/rubocop_helpers_spec.rb' RSpec/RepeatedDescription: - Exclude: - - 'spec/api_map_spec.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' + Enabled: false RSpec/RepeatedExample: Exclude: @@ -1279,99 +760,7 @@ RSpec/ScatteredLet: # Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata. # Include: **/*_spec.rb RSpec/SpecFilePathFormat: - Exclude: - - '**/spec/routing/**/*' - - 'spec/api_map/cache_spec.rb' - - 'spec/api_map/config_spec.rb' - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/api_map/store_spec.rb' - - 'spec/api_map_spec.rb' - - 'spec/convention/activesupport_concern_spec.rb' - - 'spec/convention/struct_definition_spec.rb' - - 'spec/convention_spec.rb' - - 'spec/diagnostics/base_spec.rb' - - 'spec/diagnostics/require_not_found_spec.rb' - - 'spec/diagnostics/rubocop_helpers_spec.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/diagnostics/type_check_spec.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/diagnostics_spec.rb' - - 'spec/doc_map_spec.rb' - - 'spec/gem_pins_spec.rb' - - 'spec/language_server/host/diagnoser_spec.rb' - - 'spec/language_server/host/dispatch_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/message/text_document/formatting_spec.rb' - - 'spec/language_server/message/text_document/hover_spec.rb' - - 'spec/language_server/message/text_document/rename_spec.rb' - - 'spec/language_server/message/text_document/type_definition_spec.rb' - - 'spec/language_server/message/workspace/did_change_configuration_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/message_spec.rb' - - 'spec/language_server/transport/adapter_spec.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - - 'spec/language_server/uri_helpers_spec.rb' - - 'spec/library_spec.rb' - - 'spec/logging_spec.rb' - - 'spec/parser/flow_sensitive_typing_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/parser/node_processor_spec.rb' - - 'spec/parser_spec.rb' - - 'spec/pin/base_spec.rb' - - 'spec/pin/base_variable_spec.rb' - - 'spec/pin/block_spec.rb' - - 'spec/pin/constant_spec.rb' - - 'spec/pin/delegated_method_spec.rb' - - 'spec/pin/documenting_spec.rb' - - 'spec/pin/instance_variable_spec.rb' - - 'spec/pin/keyword_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/pin/namespace_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/pin/search_spec.rb' - - 'spec/pin/symbol_spec.rb' - - 'spec/position_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/rbs_map_spec.rb' - - 'spec/shell_spec.rb' - - 'spec/source/chain/array_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/chain/constant_spec.rb' - - 'spec/source/chain/global_variable_spec.rb' - - 'spec/source/chain/head_spec.rb' - - 'spec/source/chain/instance_variable_spec.rb' - - 'spec/source/chain/link_spec.rb' - - 'spec/source/chain/literal_spec.rb' - - 'spec/source/chain/z_super_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source/change_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source/updater_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' - - 'spec/type_checker/checks_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker/levels/strong_spec.rb' - - 'spec/type_checker/levels/typed_spec.rb' - - 'spec/type_checker/rules_spec.rb' - - 'spec/type_checker_spec.rb' - - 'spec/workspace/config_spec.rb' - - 'spec/workspace_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' - - 'spec/yard_map/mapper_spec.rb' + Enabled: false RSpec/StubbedMock: Exclude: @@ -1379,20 +768,7 @@ RSpec/StubbedMock: # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. RSpec/VerifiedDoubles: - Exclude: - - 'spec/complex_type_spec.rb' - - 'spec/language_server/host/diagnoser_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/text_document/formatting_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/workspace_spec.rb' + Enabled: false Security/MarshalLoad: Exclude: @@ -1402,20 +778,7 @@ Security/MarshalLoad: # Configuration parameters: EnforcedStyle, AllowModifiersOnSymbols, AllowModifiersOnAttrs, AllowModifiersOnAliasMethod. # SupportedStyles: inline, group Style/AccessModifierDeclarations: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/location.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/position.rb' - - 'lib/solargraph/range.rb' - - 'lib/solargraph/source/chain.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/chain/hash.rb' - - 'lib/solargraph/source/chain/if.rb' - - 'lib/solargraph/source/chain/link.rb' - - 'lib/solargraph/source/chain/literal.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. @@ -1431,21 +794,7 @@ Style/AccessorGrouping: # Configuration parameters: EnforcedStyle. # SupportedStyles: always, conditionals Style/AndOr: - Exclude: - - 'lib/solargraph/api_map/source_to_yard.rb' - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/language_server/message/base.rb' - - 'lib/solargraph/page.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/position.rb' - - 'lib/solargraph/source/change.rb' - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source/updater.rb' - - 'lib/solargraph/workspace.rb' - - 'lib/solargraph/yard_map/mapper.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowOnlyRestArgument, UseAnonymousForwarding, RedundantRestArgumentNames, RedundantKeywordRestArgumentNames, RedundantBlockArgumentNames. @@ -1464,44 +813,12 @@ Style/ArgumentsForwarding: # FunctionalMethods: let, let!, subject, watch # AllowedMethods: lambda, proc, it Style/BlockDelimiters: - Exclude: - - 'lib/solargraph/api_map/source_to_yard.rb' - - 'lib/solargraph/api_map/store.rb' - - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/language_server/transport/adapter_spec.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - - 'spec/library_spec.rb' - - 'spec/parser/node_processor_spec.rb' - - 'spec/pin/documenting_spec.rb' - - 'spec/position_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_spec.rb' - - 'spec/type_checker_spec.rb' - - 'spec/workspace_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' + Enabled: false # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: MinBranchesCount. Style/CaseLikeIf: - Exclude: - - 'lib/solargraph/language_server/message/workspace/did_change_watched_files.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/yard_map/mapper.rb' + Enabled: false # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle, EnforcedStyleForClasses, EnforcedStyleForModules. @@ -1509,18 +826,7 @@ Style/CaseLikeIf: # SupportedStylesForClasses: ~, nested, compact # SupportedStylesForModules: ~, nested, compact Style/ClassAndModuleChildren: - Exclude: - - 'lib/solargraph/language_server/message/text_document/definition.rb' - - 'lib/solargraph/language_server/message/text_document/document_highlight.rb' - - 'lib/solargraph/language_server/message/text_document/document_symbol.rb' - - 'lib/solargraph/language_server/message/text_document/prepare_rename.rb' - - 'lib/solargraph/language_server/message/text_document/references.rb' - - 'lib/solargraph/language_server/message/text_document/rename.rb' - - 'lib/solargraph/language_server/message/text_document/type_definition.rb' - - 'lib/solargraph/language_server/message/workspace/did_change_configuration.rb' - - 'lib/solargraph/language_server/message/workspace/did_change_watched_files.rb' - - 'lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb' - - 'lib/solargraph/language_server/message/workspace/workspace_symbol.rb' + Enabled: false # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowedMethods, AllowedPatterns. @@ -1556,150 +862,13 @@ Style/ConcatArrayLiterals: # SupportedStyles: assign_to_condition, assign_inside_condition Style/ConditionalAssignment: Exclude: + - 'lib/solargraph/api_map/source_to_yard.rb' - 'lib/solargraph/parser/parser_gem/node_processors/defs_node.rb' - 'lib/solargraph/source/chain/call.rb' # Configuration parameters: AllowedConstants. Style/Documentation: - Exclude: - - 'spec/**/*' - - 'test/**/*' - - 'lib/solargraph/api_map/cache.rb' - - 'lib/solargraph/api_map/index.rb' - - 'lib/solargraph/api_map/source_to_yard.rb' - - 'lib/solargraph/convention/data_definition.rb' - - 'lib/solargraph/convention/gemfile.rb' - - 'lib/solargraph/convention/gemspec.rb' - - 'lib/solargraph/convention/rakefile.rb' - - 'lib/solargraph/convention/struct_definition.rb' - - 'lib/solargraph/converters/dd.rb' - - 'lib/solargraph/converters/dl.rb' - - 'lib/solargraph/converters/dt.rb' - - 'lib/solargraph/diagnostics/update_errors.rb' - - 'lib/solargraph/language_server/message/base.rb' - - 'lib/solargraph/language_server/message/cancel_request.rb' - - 'lib/solargraph/language_server/message/client.rb' - - 'lib/solargraph/language_server/message/client/register_capability.rb' - - 'lib/solargraph/language_server/message/completion_item.rb' - - 'lib/solargraph/language_server/message/exit_notification.rb' - - 'lib/solargraph/language_server/message/extended/document.rb' - - 'lib/solargraph/language_server/message/extended/search.rb' - - 'lib/solargraph/language_server/message/initialize.rb' - - 'lib/solargraph/language_server/message/initialized.rb' - - 'lib/solargraph/language_server/message/method_not_found.rb' - - 'lib/solargraph/language_server/message/method_not_implemented.rb' - - 'lib/solargraph/language_server/message/shutdown.rb' - - 'lib/solargraph/language_server/message/text_document.rb' - - 'lib/solargraph/language_server/message/text_document/base.rb' - - 'lib/solargraph/language_server/message/text_document/code_action.rb' - - 'lib/solargraph/language_server/message/text_document/completion.rb' - - 'lib/solargraph/language_server/message/text_document/definition.rb' - - 'lib/solargraph/language_server/message/text_document/did_change.rb' - - 'lib/solargraph/language_server/message/text_document/did_close.rb' - - 'lib/solargraph/language_server/message/text_document/did_open.rb' - - 'lib/solargraph/language_server/message/text_document/did_save.rb' - - 'lib/solargraph/language_server/message/text_document/document_highlight.rb' - - 'lib/solargraph/language_server/message/text_document/document_symbol.rb' - - 'lib/solargraph/language_server/message/text_document/folding_range.rb' - - 'lib/solargraph/language_server/message/text_document/formatting.rb' - - 'lib/solargraph/language_server/message/text_document/hover.rb' - - 'lib/solargraph/language_server/message/text_document/on_type_formatting.rb' - - 'lib/solargraph/language_server/message/text_document/prepare_rename.rb' - - 'lib/solargraph/language_server/message/text_document/references.rb' - - 'lib/solargraph/language_server/message/text_document/rename.rb' - - 'lib/solargraph/language_server/message/text_document/signature_help.rb' - - 'lib/solargraph/language_server/message/text_document/type_definition.rb' - - 'lib/solargraph/language_server/message/workspace.rb' - - 'lib/solargraph/language_server/message/workspace/did_change_configuration.rb' - - 'lib/solargraph/language_server/message/workspace/did_change_watched_files.rb' - - 'lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb' - - 'lib/solargraph/language_server/message/workspace/workspace_symbol.rb' - - 'lib/solargraph/language_server/request.rb' - - 'lib/solargraph/language_server/transport/data_reader.rb' - - 'lib/solargraph/logging.rb' - - 'lib/solargraph/page.rb' - - 'lib/solargraph/parser.rb' - - 'lib/solargraph/parser/comment_ripper.rb' - - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/parser/node_methods.rb' - - 'lib/solargraph/parser/node_processor/base.rb' - - 'lib/solargraph/parser/parser_gem.rb' - - 'lib/solargraph/parser/parser_gem/class_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_processors.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/alias_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/and_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/args_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/begin_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/block_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/cvasgn_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/def_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/defs_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/gvasgn_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/if_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/sym_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/until_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/while_node.rb' - - 'lib/solargraph/parser/snippet.rb' - - 'lib/solargraph/pin/base_variable.rb' - - 'lib/solargraph/pin/block.rb' - - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/closure.rb' - - 'lib/solargraph/pin/common.rb' - - 'lib/solargraph/pin/constant.rb' - - 'lib/solargraph/pin/instance_variable.rb' - - 'lib/solargraph/pin/keyword.rb' - - 'lib/solargraph/pin/local_variable.rb' - - 'lib/solargraph/pin/namespace.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/pin/proxy_type.rb' - - 'lib/solargraph/pin/reference.rb' - - 'lib/solargraph/pin/reference/override.rb' - - 'lib/solargraph/pin/reference/require.rb' - - 'lib/solargraph/pin/search.rb' - - 'lib/solargraph/pin/signature.rb' - - 'lib/solargraph/pin/singleton.rb' - - 'lib/solargraph/pin/symbol.rb' - - 'lib/solargraph/pin/until.rb' - - 'lib/solargraph/pin/while.rb' - - 'lib/solargraph/pin_cache.rb' - - 'lib/solargraph/rbs_map.rb' - - 'lib/solargraph/server_methods.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source/chain/array.rb' - - 'lib/solargraph/source/chain/block_symbol.rb' - - 'lib/solargraph/source/chain/block_variable.rb' - - 'lib/solargraph/source/chain/class_variable.rb' - - 'lib/solargraph/source/chain/constant.rb' - - 'lib/solargraph/source/chain/global_variable.rb' - - 'lib/solargraph/source/chain/hash.rb' - - 'lib/solargraph/source/chain/if.rb' - - 'lib/solargraph/source/chain/instance_variable.rb' - - 'lib/solargraph/source/chain/link.rb' - - 'lib/solargraph/source/chain/literal.rb' - - 'lib/solargraph/source/chain/or.rb' - - 'lib/solargraph/source/chain/q_call.rb' - - 'lib/solargraph/source/chain/variable.rb' - - 'lib/solargraph/source/chain/z_super.rb' - - 'lib/solargraph/source/encoding_fixes.rb' - - 'lib/solargraph/source_map/data.rb' - - 'lib/solargraph/yard_map/cache.rb' - - 'lib/solargraph/yard_map/helpers.rb' - - 'lib/solargraph/yard_map/mapper.rb' - - 'lib/solargraph/yard_map/mapper/to_constant.rb' - - 'lib/solargraph/yard_map/mapper/to_method.rb' - - 'lib/solargraph/yard_map/mapper/to_namespace.rb' - - 'lib/solargraph/yard_map/to_method.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). Style/EmptyLambdaParameter: @@ -1750,136 +919,7 @@ Style/FloatDivision: # Configuration parameters: EnforcedStyle. # SupportedStyles: always, always_true, never Style/FrozenStringLiteralComment: - Exclude: - - '**/*.arb' - - 'Gemfile' - - 'Rakefile' - - 'bin/solargraph' - - 'lib/solargraph/converters/dd.rb' - - 'lib/solargraph/converters/dl.rb' - - 'lib/solargraph/converters/dt.rb' - - 'lib/solargraph/converters/misc.rb' - - 'lib/solargraph/parser.rb' - - 'lib/solargraph/parser/comment_ripper.rb' - - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/parser/node_methods.rb' - - 'lib/solargraph/parser/parser_gem.rb' - - 'lib/solargraph/parser/snippet.rb' - - 'lib/solargraph/pin/breakable.rb' - - 'lib/solargraph/pin/signature.rb' - - 'lib/solargraph/pin_cache.rb' - - 'lib/solargraph/source/chain/array.rb' - - 'lib/solargraph/source/chain/q_call.rb' - - 'lib/solargraph/yard_map/helpers.rb' - - 'solargraph.gemspec' - - 'spec/api_map/cache_spec.rb' - - 'spec/api_map/config_spec.rb' - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/api_map_spec.rb' - - 'spec/complex_type_spec.rb' - - 'spec/convention/struct_definition_spec.rb' - - 'spec/convention_spec.rb' - - 'spec/diagnostics/base_spec.rb' - - 'spec/diagnostics/require_not_found_spec.rb' - - 'spec/diagnostics/rubocop_helpers_spec.rb' - - 'spec/diagnostics/type_check_spec.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/diagnostics_spec.rb' - - 'spec/fixtures/formattable.rb' - - 'spec/fixtures/long_squiggly_heredoc.rb' - - 'spec/fixtures/rdoc-lib/Gemfile' - - 'spec/fixtures/rdoc-lib/lib/example.rb' - - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' - - 'spec/fixtures/rubocop-custom-version/specifications/rubocop-0.0.0.gemspec' - - 'spec/fixtures/rubocop-validation-error/app.rb' - - 'spec/fixtures/unicode.rb' - - 'spec/fixtures/workspace-with-gemfile/Gemfile' - - 'spec/fixtures/workspace-with-gemfile/app.rb' - - 'spec/fixtures/workspace-with-gemfile/lib/other.rb' - - 'spec/fixtures/workspace-with-gemfile/lib/thing.rb' - - 'spec/fixtures/workspace/app.rb' - - 'spec/fixtures/workspace/lib/other.rb' - - 'spec/fixtures/workspace/lib/something.rb' - - 'spec/fixtures/workspace/lib/thing.rb' - - 'spec/fixtures/workspace_folders/folder1/app.rb' - - 'spec/fixtures/workspace_folders/folder2/app.rb' - - 'spec/fixtures/yard_map/attr.rb' - - 'spec/language_server/host/diagnoser_spec.rb' - - 'spec/language_server/host/dispatch_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/message/text_document/formatting_spec.rb' - - 'spec/language_server/message/text_document/hover_spec.rb' - - 'spec/language_server/message/text_document/type_definition_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/message_spec.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/language_server/transport/adapter_spec.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - - 'spec/language_server/uri_helpers_spec.rb' - - 'spec/library_spec.rb' - - 'spec/logging_spec.rb' - - 'spec/parser/node_chainer_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/parser/node_processor_spec.rb' - - 'spec/parser_spec.rb' - - 'spec/pin/base_spec.rb' - - 'spec/pin/base_variable_spec.rb' - - 'spec/pin/block_spec.rb' - - 'spec/pin/class_variable_spec.rb' - - 'spec/pin/constant_spec.rb' - - 'spec/pin/delegated_method_spec.rb' - - 'spec/pin/documenting_spec.rb' - - 'spec/pin/instance_variable_spec.rb' - - 'spec/pin/keyword_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/pin/namespace_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/pin/search_spec.rb' - - 'spec/pin/symbol_spec.rb' - - 'spec/position_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/rbs_map_spec.rb' - - 'spec/shell_spec.rb' - - 'spec/source/chain/array_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/chain/constant_spec.rb' - - 'spec/source/chain/global_variable_spec.rb' - - 'spec/source/chain/head_spec.rb' - - 'spec/source/chain/instance_variable_spec.rb' - - 'spec/source/chain/link_spec.rb' - - 'spec/source/chain/literal_spec.rb' - - 'spec/source/chain/z_super_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source/change_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source/updater_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map/node_processor_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' - - 'spec/spec_helper.rb' - - 'spec/type_checker/checks_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker/levels/strong_spec.rb' - - 'spec/type_checker/levels/typed_spec.rb' - - 'spec/type_checker/rules_spec.rb' - - 'spec/type_checker_spec.rb' - - 'spec/workspace/config_spec.rb' - - 'spec/workspace_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' - - 'spec/yard_map/mapper_spec.rb' + Enabled: false # This cop supports unsafe autocorrection (--autocorrect-all). Style/GlobalStdStream: @@ -1892,15 +932,7 @@ Style/GlobalStdStream: # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin_cache.rb' - - 'lib/solargraph/range.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/workspace.rb' + Enabled: false # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowSplatArgument. @@ -1935,52 +967,11 @@ Style/IdenticalConditionalBranches: # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowIfModifier. Style/IfInsideElse: - Exclude: - - 'lib/solargraph/complex_type/type_methods.rb' - - 'lib/solargraph/language_server/transport/data_reader.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/type_checker.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). Style/IfUnlessModifier: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/index.rb' - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/doc_map.rb' - - 'lib/solargraph/language_server/message/completion_item/resolve.rb' - - 'lib/solargraph/language_server/message/initialize.rb' - - 'lib/solargraph/language_server/message/text_document/completion.rb' - - 'lib/solargraph/language_server/message/text_document/hover.rb' - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/parser_gem/class_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/common.rb' - - 'lib/solargraph/pin/constant.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/range.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/source/chain.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/change.rb' - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source_map.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker.rb' - - 'lib/solargraph/workspace.rb' - - 'lib/solargraph/workspace/config.rb' - - 'lib/solargraph/yard_map/helpers.rb' - - 'lib/solargraph/yard_map/mapper/to_method.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. @@ -2010,69 +1001,8 @@ Style/MapToSet: # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline -Style/MethodDefParentheses: - Exclude: - - 'lib/solargraph.rb' - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/index.rb' - - 'lib/solargraph/api_map/store.rb' - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/complex_type/type_methods.rb' - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/convention.rb' - - 'lib/solargraph/convention/data_definition.rb' - - 'lib/solargraph/convention/data_definition/data_assignment_node.rb' - - 'lib/solargraph/convention/data_definition/data_definition_node.rb' - - 'lib/solargraph/convention/struct_definition.rb' - - 'lib/solargraph/convention/struct_definition/struct_assignment_node.rb' - - 'lib/solargraph/convention/struct_definition/struct_definition_node.rb' - - 'lib/solargraph/diagnostics/rubocop_helpers.rb' - - 'lib/solargraph/doc_map.rb' - - 'lib/solargraph/equality.rb' - - 'lib/solargraph/gem_pins.rb' - - 'lib/solargraph/language_server/host/message_worker.rb' - - 'lib/solargraph/language_server/host/sources.rb' - - 'lib/solargraph/language_server/message/text_document/formatting.rb' - - 'lib/solargraph/location.rb' - - 'lib/solargraph/parser/comment_ripper.rb' - - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/parser/node_methods.rb' - - 'lib/solargraph/parser/node_processor/base.rb' - - 'lib/solargraph/parser/parser_gem/flawed_builder.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/args_node.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/base_variable.rb' - - 'lib/solargraph/pin/block.rb' - - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/closure.rb' - - 'lib/solargraph/pin/delegated_method.rb' - - 'lib/solargraph/pin/local_variable.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/pin_cache.rb' - - 'lib/solargraph/position.rb' - - 'lib/solargraph/range.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/chain/constant.rb' - - 'lib/solargraph/source_map.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker.rb' - - 'lib/solargraph/type_checker/checks.rb' - - 'lib/solargraph/yard_map/helpers.rb' - - 'lib/solargraph/yardoc.rb' - - 'spec/doc_map_spec.rb' - - 'spec/fixtures/rdoc-lib/lib/example.rb' - - 'spec/source_map_spec.rb' - - 'spec/spec_helper.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker/levels/strong_spec.rb' - - 'spec/type_checker/levels/typed_spec.rb' +Style/MethodDefParentheses: + Enabled: false Style/MultilineBlockChain: Exclude: @@ -2091,29 +1021,13 @@ Style/MultilineTernaryOperator: # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowMethodComparison, ComparisonsThreshold. Style/MultipleComparison: - Exclude: - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/complex_type/type_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/type_checker.rb' + Enabled: false # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: literals, strict Style/MutableConstant: - Exclude: - - 'lib/solargraph/diagnostics/rubocop.rb' - - 'lib/solargraph/logging.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/rbs_map/core_fills.rb' - - 'lib/solargraph/yard_map/mapper/to_method.rb' - - 'spec/complex_type_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. @@ -2148,34 +1062,15 @@ Style/Next: - 'lib/solargraph/type_checker/checks.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: MinDigits, Strict, AllowedNumbers, AllowedPatterns. +# Configuration parameters: Strict, AllowedNumbers, AllowedPatterns. Style/NumericLiterals: - Exclude: - - 'lib/solargraph/language_server/error_codes.rb' - - 'spec/language_server/protocol_spec.rb' + MinDigits: 6 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. # SupportedStyles: predicate, comparison Style/NumericPredicate: - Exclude: - - 'spec/**/*' - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/index.rb' - - 'lib/solargraph/api_map/store.rb' - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/complex_type/type_methods.rb' - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/comment_ripper.rb' - - 'lib/solargraph/pin/delegated_method.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source/chain/array.rb' - - 'lib/solargraph/source/change.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/workspace.rb' + Enabled: false Style/OpenStructUse: Exclude: @@ -2184,14 +1079,7 @@ Style/OpenStructUse: # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/language_server/message/text_document/completion.rb' - - 'lib/solargraph/source/chain.rb' - - 'lib/solargraph/source/chain/hash.rb' - - 'lib/solargraph/source/chain/z_super.rb' - - 'lib/solargraph/source/change.rb' - - 'lib/solargraph/source/updater.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowSafeAssignment, AllowInMultilineConditions. @@ -2254,14 +1142,7 @@ Style/RedundantInterpolation: # This cop supports safe autocorrection (--autocorrect). Style/RedundantParentheses: - Exclude: - - 'lib/solargraph/diagnostics/type_check.rb' - - 'lib/solargraph/language_server/message/initialize.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/type_checker.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). Style/RedundantRegexpArgument: @@ -2274,13 +1155,7 @@ Style/RedundantRegexpArgument: # This cop supports safe autocorrection (--autocorrect). Style/RedundantRegexpEscape: - Exclude: - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/diagnostics/rubocop.rb' - - 'lib/solargraph/language_server/uri_helpers.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/source_map/mapper.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowMultipleReturnValues. @@ -2294,15 +1169,7 @@ Style/RedundantReturn: # This cop supports safe autocorrection (--autocorrect). Style/RedundantSelf: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/equality.rb' - - 'lib/solargraph/location.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/signature.rb' - - 'lib/solargraph/source/chain.rb' - - 'lib/solargraph/source/chain/link.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, AllowInnerSlashes. @@ -2323,19 +1190,7 @@ Style/RescueStandardError: # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # AllowedMethods: present?, blank?, presence, try, try! Style/SafeNavigation: - Exclude: - - 'lib/solargraph/api_map/index.rb' - - 'lib/solargraph/doc_map.rb' - - 'lib/solargraph/language_server/message/completion_item/resolve.rb' - - 'lib/solargraph/language_server/request.rb' - - 'lib/solargraph/language_server/transport/data_reader.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/conversions.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin_cache.rb' - - 'lib/solargraph/range.rb' - - 'lib/solargraph/type_checker.rb' + Enabled: false # Configuration parameters: Max. Style/SafeNavigationChainLength: @@ -2344,36 +1199,12 @@ Style/SafeNavigationChainLength: # This cop supports unsafe autocorrection (--autocorrect-all). Style/SlicingWithRange: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/convention/data_definition/data_definition_node.rb' - - 'lib/solargraph/convention/struct_definition/struct_definition_node.rb' - - 'lib/solargraph/diagnostics/rubocop_helpers.rb' - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/namespace.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source/chain/constant.rb' - - 'lib/solargraph/source/change.rb' - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker/checks.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowModifier. Style/SoleNestedConditional: - Exclude: - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/type_checker.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). Style/StderrPuts: @@ -2384,114 +1215,13 @@ Style/StderrPuts: # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Mode. Style/StringConcatenation: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/index.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/closure.rb' - - 'lib/solargraph/pin/local_variable.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/namespace.rb' - - 'solargraph.gemspec' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes Style/StringLiterals: - Exclude: - - 'Gemfile' - - 'Rakefile' - - 'lib/solargraph/api_map/index.rb' - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/convention/struct_definition.rb' - - 'lib/solargraph/doc_map.rb' - - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/language_server/message/extended/document_gems.rb' - - 'lib/solargraph/language_server/message/extended/download_core.rb' - - 'lib/solargraph/language_server/message/initialize.rb' - - 'lib/solargraph/language_server/message/text_document/completion.rb' - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/parser_gem/class_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/conversions.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/server_methods.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/workspace.rb' - - 'lib/solargraph/yard_map/mapper/to_method.rb' - - 'lib/solargraph/yard_tags.rb' - - 'solargraph.gemspec' - - 'spec/api_map/cache_spec.rb' - - 'spec/api_map/config_spec.rb' - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/complex_type_spec.rb' - - 'spec/convention/struct_definition_spec.rb' - - 'spec/diagnostics/base_spec.rb' - - 'spec/diagnostics/require_not_found_spec.rb' - - 'spec/diagnostics/rubocop_helpers_spec.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/diagnostics/type_check_spec.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/diagnostics_spec.rb' - - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' - - 'spec/language_server/host/diagnoser_spec.rb' - - 'spec/language_server/host/dispatch_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/rename_spec.rb' - - 'spec/language_server/message_spec.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/language_server/transport/adapter_spec.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - - 'spec/library_spec.rb' - - 'spec/logging_spec.rb' - - 'spec/parser/node_chainer_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/parser_spec.rb' - - 'spec/pin/base_spec.rb' - - 'spec/pin/base_variable_spec.rb' - - 'spec/pin/constant_spec.rb' - - 'spec/pin/instance_variable_spec.rb' - - 'spec/pin/keyword_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/namespace_spec.rb' - - 'spec/pin/symbol_spec.rb' - - 'spec/position_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/shell_spec.rb' - - 'spec/source/chain/array_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/chain/constant_spec.rb' - - 'spec/source/chain/global_variable_spec.rb' - - 'spec/source/chain/head_spec.rb' - - 'spec/source/chain/instance_variable_spec.rb' - - 'spec/source/chain/link_spec.rb' - - 'spec/source/chain/literal_spec.rb' - - 'spec/source/chain/z_super_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source/change_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source/updater_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/workspace/config_spec.rb' - - 'spec/workspace_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). Style/SuperArguments: @@ -2505,35 +1235,13 @@ Style/SuperArguments: # Configuration parameters: EnforcedStyle, MinSize. # SupportedStyles: percent, brackets Style/SymbolArray: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/block_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source/chain/literal.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/source_map/mapper_spec.rb' + Enabled: false # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments. # AllowedMethods: define_method Style/SymbolProc: - Exclude: - - 'lib/solargraph/gem_pins.rb' - - 'lib/solargraph/language_server/message/text_document/hover.rb' - - 'lib/solargraph/language_server/message/text_document/signature_help.rb' - - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/closure.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, AllowSafeAssignment. @@ -2546,19 +1254,7 @@ Style/TernaryParentheses: # Configuration parameters: EnforcedStyleForMultiline. # SupportedStylesForMultiline: comma, consistent_comma, no_comma Style/TrailingCommaInArguments: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/parser/node_processor.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/block_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/def_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/defs_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/until_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/while_node.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/yard_map/mapper/to_method.rb' - - 'lib/solargraph/yard_map/mapper/to_namespace.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyleForMultiline. @@ -2572,13 +1268,7 @@ Style/TrailingCommaInArrayLiteral: # Configuration parameters: EnforcedStyleForMultiline. # SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma Style/TrailingCommaInHashLiteral: - Exclude: - - 'lib/solargraph/pin/base_variable.rb' - - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/closure.rb' - - 'lib/solargraph/pin/local_variable.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/rbs_map/conversions.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, AllowedMethods. @@ -2597,20 +1287,7 @@ Style/WhileUntilModifier: # Configuration parameters: EnforcedStyle, MinSize, WordRegex. # SupportedStyles: percent, brackets Style/WordArray: - Exclude: - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/diagnostics/type_check.rb' - - 'lib/solargraph/language_server/message/text_document/formatting.rb' - - 'spec/doc_map_spec.rb' - - 'spec/parser/node_chainer_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). Style/YAMLFileRead: @@ -2619,13 +1296,7 @@ Style/YAMLFileRead: # This cop supports unsafe autocorrection (--autocorrect-all). Style/ZeroLengthPredicate: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/index.rb' - - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/source/chain/array.rb' - - 'spec/language_server/protocol_spec.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. @@ -2638,33 +1309,7 @@ YARD/CollectionType: # Configuration parameters: EnforcedStylePrototypeName. # SupportedStylesPrototypeName: before, after YARD/MismatchName: - Exclude: - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/gem_pins.rb' - - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/language_server/host/dispatch.rb' - - 'lib/solargraph/language_server/request.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/region.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/base_variable.rb' - - 'lib/solargraph/pin/block.rb' - - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/closure.rb' - - 'lib/solargraph/pin/delegated_method.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/namespace.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/pin/proxy_type.rb' - - 'lib/solargraph/pin/reference.rb' - - 'lib/solargraph/pin/symbol.rb' - - 'lib/solargraph/pin/until.rb' - - 'lib/solargraph/pin/while.rb' - - 'lib/solargraph/pin_cache.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/chain/z_super.rb' - - 'lib/solargraph/type_checker.rb' + Enabled: false YARD/TagTypeSyntax: Exclude: @@ -2673,72 +1318,7 @@ YARD/TagTypeSyntax: - 'lib/solargraph/type_checker.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. +# Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. # URISchemes: http, https Layout/LineLength: - Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/source_to_yard.rb' - - 'lib/solargraph/api_map/store.rb' - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/convention/data_definition.rb' - - 'lib/solargraph/doc_map.rb' - - 'lib/solargraph/gem_pins.rb' - - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - - 'lib/solargraph/language_server/message/extended/download_core.rb' - - 'lib/solargraph/language_server/message/initialize.rb' - - 'lib/solargraph/language_server/message/text_document/completion.rb' - - 'lib/solargraph/language_server/message/text_document/definition.rb' - - 'lib/solargraph/language_server/message/text_document/document_highlight.rb' - - 'lib/solargraph/language_server/message/text_document/prepare_rename.rb' - - 'lib/solargraph/language_server/message/text_document/references.rb' - - 'lib/solargraph/language_server/message/text_document/rename.rb' - - 'lib/solargraph/language_server/message/workspace/did_change_watched_files.rb' - - 'lib/solargraph/library.rb' - - 'lib/solargraph/parser/comment_ripper.rb' - - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/and_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/if_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/common.rb' - - 'lib/solargraph/pin/documenting.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/rbs_map/core_fills.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source/chain.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/chain/if.rb' - - 'lib/solargraph/source/chain/instance_variable.rb' - - 'lib/solargraph/source/chain/variable.rb' - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/encoding_fixes.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/source_map.rb' - - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/source_map/mapper.rb' - - 'lib/solargraph/type_checker.rb' - - 'lib/solargraph/workspace.rb' - - 'lib/solargraph/workspace/config.rb' - - 'lib/solargraph/yard_map/mapper/to_method.rb' - - 'spec/api_map_spec.rb' - - 'spec/complex_type_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/protocol_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/workspace_spec.rb' + Max: 244 diff --git a/solargraph.gemspec b/solargraph.gemspec index e6bb9394a..7610bb8ea 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -48,9 +48,16 @@ Gem::Specification.new do |s| s.add_development_dependency 'public_suffix', '~> 3.1' s.add_development_dependency 'rake', '~> 13.2' s.add_development_dependency 'rspec', '~> 3.5' - s.add_development_dependency 'rubocop-rake', '~> 0.7' - s.add_development_dependency 'rubocop-rspec', '~> 3.6' - s.add_development_dependency 'rubocop-yard', '~> 1.0' + # + # very specific development-time RuboCop version patterns for CI + # stability - feel free to update in an isolated PR + # + # even more specific on RuboCop itself, which is written into _todo + # file. + s.add_development_dependency 'rubocop', '~> 1.79.2.0' + s.add_development_dependency 'rubocop-rake', '~> 0.7.1' + s.add_development_dependency 'rubocop-rspec', '~> 3.6.0' + s.add_development_dependency 'rubocop-yard', '~> 1.0.0' s.add_development_dependency 'simplecov', '~> 0.21' s.add_development_dependency 'simplecov-lcov', '~> 0.8' s.add_development_dependency 'undercover', '~> 0.7' From 61260f346883de89c8dd9c61b205e88f59ae3a8b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 30 Aug 2025 10:18:50 -0400 Subject: [PATCH 133/460] Fix merge issue --- .github/workflows/linting.yml | 2 +- .rubocop_todo.yml | 25 +++++++++---------------- lib/solargraph/api_map.rb | 2 -- spec/api_map_spec.rb | 3 --- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index aa22ce22c..b4ef26bfe 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -115,7 +115,7 @@ jobs: - name: Run RuboCop against todo file continue-on-error: true run: | - cmd="bundle exec rubocop --auto-gen-config --exclude-limit=5 --no-offense-counts --no-auto-gen-timestampxb*com" + cmd="bundle exec rubocop --auto-gen-config --exclude-limit=5 --no-offense-counts --no-auto-gen-timestamp" ${cmd:?} set +e if [ -n "$(git status --porcelain)" ] diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7641198af..0ed335f34 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,5 +1,5 @@ # This configuration was generated by -# `rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` +# `rubocop --auto-gen-config --exclude-limit 5 --no-offense-counts --no-auto-gen-timestamp` # using RuboCop version 1.80.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. @@ -121,7 +121,13 @@ Layout/EndOfLine: # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. - Enabled: false +Layout/ExtraSpacing: + Exclude: + - 'lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb' + - 'lib/solargraph/pin/closure.rb' + - 'lib/solargraph/rbs_map/conversions.rb' + - 'lib/solargraph/type_checker.rb' + - 'spec/spec_helper.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. @@ -604,9 +610,9 @@ RSpec/DescribeClass: - '**/spec/routing/**/*' - '**/spec/system/**/*' - '**/spec/views/**/*' + - 'spec/api_map_method_spec.rb' - 'spec/complex_type_spec.rb' - 'spec/source_map/node_processor_spec.rb' - - 'spec/api_map_method_spec.rb' # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. @@ -633,7 +639,6 @@ RSpec/ExampleLength: # DisallowedExamples: works RSpec/ExampleWording: Exclude: - - 'spec/convention/struct_definition_spec.rb' - 'spec/pin/base_spec.rb' - 'spec/pin/method_spec.rb' @@ -1114,12 +1119,6 @@ Style/RedundantFreeze: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/source_map/mapper.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowComments. -Style/RedundantInitialize: - Exclude: - - 'lib/solargraph/rbs_map/core_map.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: @@ -1140,12 +1139,6 @@ Style/RedundantRegexpArgument: - 'spec/diagnostics/rubocop_spec.rb' - 'spec/language_server/host_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantRegexpCharacterClass: - Exclude: - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/source_chainer.rb' - # This cop supports safe autocorrection (--autocorrect). Style/RedundantRegexpEscape: Enabled: false diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index f58633a0c..ee35dc497 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -753,7 +753,6 @@ def store # @param skip [Set] # @param no_core [Boolean] Skip core classes if true # @return [Array] - # rubocop:disable Metrics/CyclomaticComplexity def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false rooted_type = ComplexType.parse(rooted_tag).force_rooted fqns = rooted_type.namespace @@ -827,7 +826,6 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false end result end - # rubocop:enable Metrics/CyclomaticComplexity # @param fqns [String] # @param visibility [Array] diff --git a/spec/api_map_spec.rb b/spec/api_map_spec.rb index a612b428e..85e62d507 100755 --- a/spec/api_map_spec.rb +++ b/spec/api_map_spec.rb @@ -1,7 +1,6 @@ require 'tmpdir' describe Solargraph::ApiMap do - # rubocop:disable RSpec/InstanceVariable before :all do @api_map = Solargraph::ApiMap.new end @@ -873,6 +872,4 @@ def c clip = api_map.clip_at('test.rb', [18, 4]) expect(clip.infer.to_s).to eq('Integer') end - - # rubocop:enable RSpec/InstanceVariable end From 1558f227896d67b7de7592ea78ed46445b165be6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 30 Aug 2025 13:32:57 -0400 Subject: [PATCH 134/460] Fix merge issue --- spec/rbs_map/conversions_spec.rb | 77 ++++++++++++++++---------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 92d21d646..c430c1e26 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -42,6 +42,45 @@ def bar: () -> untyped end end + # https://github.com/castwide/solargraph/issues/1042 + context 'with Hash superclass with untyped value and alias' do + let(:rbs) do + <<~RBS + class Sub < Hash[Symbol, untyped] + alias meth_alias [] + end + RBS + end + + let(:api_map) { Solargraph::ApiMap.new } + + let(:sup_method_stack) { api_map.get_method_stack('Hash{Symbol => undefined}', '[]', scope: :instance) } + + let(:sub_alias_stack) { api_map.get_method_stack('Sub', 'meth_alias', scope: :instance) } + + before do + api_map.index conversions.pins + end + + it 'does not crash looking at superclass method' do + expect { sup_method_stack }.not_to raise_error + end + + it 'does not crash looking at alias' do + expect { sub_alias_stack }.not_to raise_error + end + + it 'finds superclass method pin return type' do + expect(sup_method_stack.map(&:return_type).map(&:rooted_tags).uniq).to eq(['undefined']) + end + + it 'finds superclass method pin parameter type' do + expect(sup_method_stack.flat_map(&:signatures).flat_map(&:parameters).map(&:return_type).map(&:rooted_tags) + .uniq).to eq(['Symbol']) + end + end + + # https://github.com/castwide/solargraph/issues/1042 context 'with Hash superclass with untyped value and alias' do let(:rbs) do @@ -97,42 +136,4 @@ class Sub < Hash[Symbol, untyped] end end end - - # https://github.com/castwide/solargraph/issues/1042 - context 'with Hash superclass with untyped value and alias' do - let(:rbs) do - <<~RBS - class Sub < Hash[Symbol, untyped] - alias meth_alias [] - end - RBS - end - - let(:api_map) { Solargraph::ApiMap.new } - - let(:sup_method_stack) { api_map.get_method_stack('Hash{Symbol => undefined}', '[]', scope: :instance) } - - let(:sub_alias_stack) { api_map.get_method_stack('Sub', 'meth_alias', scope: :instance) } - - before do - api_map.index conversions.pins - end - - it 'does not crash looking at superclass method' do - expect { sup_method_stack }.not_to raise_error - end - - it 'does not crash looking at alias' do - expect { sub_alias_stack }.not_to raise_error - end - - it 'finds superclass method pin return type' do - expect(sup_method_stack.map(&:return_type).map(&:rooted_tags).uniq).to eq(['undefined']) - end - - it 'finds superclass method pin parameter type' do - expect(sup_method_stack.flat_map(&:signatures).flat_map(&:parameters).map(&:return_type).map(&:rooted_tags) - .uniq).to eq(['Symbol']) - end - end end From 16bd8ac3fd26e29e42e7b3424a9045ac9ef6989b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 30 Aug 2025 13:34:48 -0400 Subject: [PATCH 135/460] Fix merge issue --- spec/rbs_map/conversions_spec.rb | 39 -------------------------------- 1 file changed, 39 deletions(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index c430c1e26..30354c31a 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -42,45 +42,6 @@ def bar: () -> untyped end end - # https://github.com/castwide/solargraph/issues/1042 - context 'with Hash superclass with untyped value and alias' do - let(:rbs) do - <<~RBS - class Sub < Hash[Symbol, untyped] - alias meth_alias [] - end - RBS - end - - let(:api_map) { Solargraph::ApiMap.new } - - let(:sup_method_stack) { api_map.get_method_stack('Hash{Symbol => undefined}', '[]', scope: :instance) } - - let(:sub_alias_stack) { api_map.get_method_stack('Sub', 'meth_alias', scope: :instance) } - - before do - api_map.index conversions.pins - end - - it 'does not crash looking at superclass method' do - expect { sup_method_stack }.not_to raise_error - end - - it 'does not crash looking at alias' do - expect { sub_alias_stack }.not_to raise_error - end - - it 'finds superclass method pin return type' do - expect(sup_method_stack.map(&:return_type).map(&:rooted_tags).uniq).to eq(['undefined']) - end - - it 'finds superclass method pin parameter type' do - expect(sup_method_stack.flat_map(&:signatures).flat_map(&:parameters).map(&:return_type).map(&:rooted_tags) - .uniq).to eq(['Symbol']) - end - end - - # https://github.com/castwide/solargraph/issues/1042 context 'with Hash superclass with untyped value and alias' do let(:rbs) do From 91ced058c5c81f3b1e63c51b27c08f3ca14c2339 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 30 Aug 2025 21:25:08 -0400 Subject: [PATCH 136/460] Add spec --- spec/type_checker/levels/strong_spec.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index a03e6eb5d..94f0f207f 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -97,6 +97,29 @@ def bar expect(checker.problems.map(&:message)).to include('Call to #foo is missing keyword argument b') end + it 'understands complex use of other' do + checker = type_checker(%( + class A + # @param other [self] + # + # @return [void] + def foo other; end + + # @param other [self] + # + # @return [void] + def bar(other); end + end + + class B < A + def bar(other) + foo(other) + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'calls out type issues even when keyword issues are there' do pending('fixes to arg vs param checking algorithm') From 2702f443f84884bc5a72d84a3da66f5d7df51a51 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 30 Aug 2025 21:39:22 -0400 Subject: [PATCH 137/460] Work around strong typechecking issue --- lib/solargraph/type_checker.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index dd13d7ec7..2f9b7da1c 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -453,8 +453,7 @@ def kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, ptype = data[:qualified] ptype = ptype.self_to_type(pin.context) unless ptype.undefined? - argtype = argchain.infer(api_map, block_pin, locals) - argtype = argtype.self_to_type(block_pin.context) + argtype = argchain.infer(api_map, block_pin, locals).self_to_type(block_pin.context) if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype) result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") end From 07e8b06b17592364e53543518bef8d78ab951738 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 07:53:06 -0400 Subject: [PATCH 138/460] Fix merge issue --- spec/rbs_map/conversions_spec.rb | 79 ++++++++++++++++---------------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 8164b3647..cf429f58f 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -22,7 +22,6 @@ before do rbs_file = File.join(temp_dir, 'foo.rbs') File.write(rbs_file, rbs) - loader.add(path: Pathname(temp_dir)) end attr_reader :temp_dir @@ -46,9 +45,47 @@ def bar: () -> untyped expect(method_pin.return_type.tag).to eq('undefined') end end - end + # https://github.com/castwide/solargraph/issues/1042 + context 'with Hash superclass with untyped value and alias' do + let(:rbs) do + <<~RBS + class Sub < Hash[Symbol, untyped] + alias meth_alias [] + end + RBS + end + + let(:api_map) { Solargraph::ApiMap.new } + + let(:sup_method_stack) { api_map.get_method_stack('Hash{Symbol => undefined}', '[]', scope: :instance) } + + let(:sub_alias_stack) { api_map.get_method_stack('Sub', 'meth_alias', scope: :instance) } + + before do + api_map.index conversions.pins + end + + it 'does not crash looking at superclass method' do + expect { sup_method_stack }.not_to raise_error + end + + it 'does not crash looking at alias' do + expect { sub_alias_stack }.not_to raise_error + end + + it 'finds superclass method pin return type' do + expect(sup_method_stack.map(&:return_type).map(&:rooted_tags).uniq).to eq(['undefined']) + end + + it 'finds superclass method pin parameter type' do + expect(sup_method_stack.flat_map(&:signatures).flat_map(&:parameters).map(&:return_type).map(&:rooted_tags) + .uniq).to eq(['Symbol']) + end + end + end + if Gem::Version.new(RBS::VERSION) >= Gem::Version.new('3.9.1') context 'with method pin for Open3.capture2e' do it 'accepts chdir kwarg' do @@ -65,42 +102,4 @@ def bar: () -> untyped end end end - - # https://github.com/castwide/solargraph/issues/1042 - context 'with Hash superclass with untyped value and alias' do - let(:rbs) do - <<~RBS - class Sub < Hash[Symbol, untyped] - alias meth_alias [] - end - RBS - end - - let(:api_map) { Solargraph::ApiMap.new } - - let(:sup_method_stack) { api_map.get_method_stack('Hash{Symbol => undefined}', '[]', scope: :instance) } - - let(:sub_alias_stack) { api_map.get_method_stack('Sub', 'meth_alias', scope: :instance) } - - before do - api_map.index conversions.pins - end - - it 'does not crash looking at superclass method' do - expect { sup_method_stack }.not_to raise_error - end - - it 'does not crash looking at alias' do - expect { sub_alias_stack }.not_to raise_error - end - - it 'finds superclass method pin return type' do - expect(sup_method_stack.map(&:return_type).map(&:rooted_tags).uniq).to eq(['undefined']) - end - - it 'finds superclass method pin parameter type' do - expect(sup_method_stack.flat_map(&:signatures).flat_map(&:parameters).map(&:return_type).map(&:rooted_tags) - .uniq).to eq(['Symbol']) - end - end end From 3e3d802750203dbe6d953daf90c90178b1aebacb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 09:03:15 -0400 Subject: [PATCH 139/460] Fix case where include tags need to be qualified [regression] It looks like https://github.com/castwide/solargraph/pull/1048 regressed the ability for ApiMap#get_methods to find the method in this situation: ```ruby module A module B # @return [String] def foo 'foo' end end end class A::C include B end ``` It looks like the 'B' in this situation doesn't get qualified like it used to. --- lib/solargraph/api_map.rb | 7 +-- spec/api_map/include_spec.rb | 92 ++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 spec/api_map/include_spec.rb diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index f58633a0c..ae52de260 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -795,9 +795,10 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false type = const.infer(self) result.concat inner_get_methods(type.namespace, scope, visibility, deep, skip, true) if type.defined? else - referenced_tag = ref.parametrized_tag - next unless referenced_tag.defined? - result.concat inner_get_methods_from_reference(referenced_tag.to_s, namespace_pin, rooted_type, scope, visibility, deep, skip, true) + referenced_type = ref.parametrized_tag + next unless referenced_type.defined? + rooted_include_tag = qualify(referenced_type.rooted_tag, rooted_tag) + result.concat inner_get_methods_from_reference(rooted_include_tag, namespace_pin, rooted_type, scope, visibility, deep, skip, true) end end rooted_sc_tag = qualify_superclass(rooted_tag) diff --git a/spec/api_map/include_spec.rb b/spec/api_map/include_spec.rb new file mode 100644 index 000000000..471a748f9 --- /dev/null +++ b/spec/api_map/include_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +describe Solargraph::ApiMap do + let(:api_map) { Solargraph::ApiMap.new.map(source) } + + context 'with a non-rooted include in local source' do + let :source do + Solargraph::Source.load_string(%( + module A + module B + # @return [String] + def foo + 'foo' + end + end + end + + class A::C + include B + end + ), 'test.rb') + end + + it 'understands method' do + pin = api_map.get_method_stack('A::B', 'foo', scope: :instance) + expect(pin.map(&:return_type).map(&:tags)).to eq(['String']) + end + + it 'handles includes via relative name' do + api_map = Solargraph::ApiMap.new.map(source) + + pin = api_map.get_method_stack('A::C', 'foo', scope: :instance) + expect(pin.map(&:return_type).map(&:rooted_tags)).to eq(['String']) + end + end + + context 'with a non-rooted include in RBS' do + # create a temporary directory with the scope of the spec + around do |example| + require 'tmpdir' + Dir.mktmpdir("rspec-solargraph-") do |dir| + @temp_dir = dir + example.run + end + end + + attr_reader :temp_dir + + let(:rbs) do + <<~RBS + module A + module B + def foo: () -> String + end + + class E + def foo: () -> String + end + + class D < C + end + end + class A::C + include B + end + RBS + end + + let(:conversions) do + loader = RBS::EnvironmentLoader.new(core_root: nil, repository: RBS::Repository.new(no_stdlib: false)) + loader.add(path: Pathname(temp_dir)) + Solargraph::RbsMap::Conversions.new(loader: loader) + end + + let(:api_map) { Solargraph::ApiMap.new pins: conversions.pins } + + before do + rbs_file = File.join(temp_dir, 'foo.rbs') + File.write(rbs_file, rbs) + end + + it 'understands method' do + pin = api_map.get_method_stack('A::B', 'foo', scope: :instance) + expect(pin.map(&:return_type).map(&:tags)).to eq(['String']) + end + + it 'handles includes via relative name' do + pin = api_map.get_method_stack('A::C', 'foo', scope: :instance) + expect(pin.map(&:return_type).map(&:tags)).to eq(['String']) + end + end +end From d02ad8ef87cbce6a97e11524660219a74b1de270 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 09:10:52 -0400 Subject: [PATCH 140/460] RuboCop fixes --- spec/api_map/include_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/api_map/include_spec.rb b/spec/api_map/include_spec.rb index 471a748f9..7f3b6efa7 100644 --- a/spec/api_map/include_spec.rb +++ b/spec/api_map/include_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe Solargraph::ApiMap do - let(:api_map) { Solargraph::ApiMap.new.map(source) } + let(:api_map) { described_class.new.map(source) } context 'with a non-rooted include in local source' do let :source do @@ -27,7 +27,7 @@ class A::C end it 'handles includes via relative name' do - api_map = Solargraph::ApiMap.new.map(source) + api_map = described_class.new.map(source) pin = api_map.get_method_stack('A::C', 'foo', scope: :instance) expect(pin.map(&:return_type).map(&:rooted_tags)).to eq(['String']) @@ -38,7 +38,7 @@ class A::C # create a temporary directory with the scope of the spec around do |example| require 'tmpdir' - Dir.mktmpdir("rspec-solargraph-") do |dir| + Dir.mktmpdir('rspec-solargraph-') do |dir| @temp_dir = dir example.run end @@ -63,7 +63,7 @@ class D < C class A::C include B end - RBS + RBS end let(:conversions) do @@ -72,7 +72,7 @@ class A::C Solargraph::RbsMap::Conversions.new(loader: loader) end - let(:api_map) { Solargraph::ApiMap.new pins: conversions.pins } + let(:api_map) { described_class.new pins: conversions.pins } before do rbs_file = File.join(temp_dir, 'foo.rbs') From e262a3d4fc02c90b2a06ae53d094be65c3c12982 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 09:17:20 -0400 Subject: [PATCH 141/460] Disable RSpec/SpecFilePathFormat rule --- .rubocop.yml | 4 ++ .rubocop_todo.yml | 120 +--------------------------------------------- 2 files changed, 6 insertions(+), 118 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index a73324db2..31ee361d9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,6 +21,10 @@ AllCops: - "vendor/**/.*" TargetRubyVersion: 3.0 +# We don't use the spec/solargraph directory +RSpec/SpecFilePathFormat: + Enabled: false + Style/MethodDefParentheses: EnforcedStyle: require_no_parentheses diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b8d123824..3716d5983 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.80.0. +# using RuboCop version 1.80.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -920,9 +920,9 @@ RSpec/DescribeClass: - '**/spec/routing/**/*' - '**/spec/system/**/*' - '**/spec/views/**/*' + - 'spec/api_map_method_spec.rb' - 'spec/complex_type_spec.rb' - 'spec/source_map/node_processor_spec.rb' - - 'spec/api_map_method_spec.rb' # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. @@ -1045,7 +1045,6 @@ RSpec/ExampleLength: # DisallowedExamples: works RSpec/ExampleWording: Exclude: - - 'spec/convention/struct_definition_spec.rb' - 'spec/pin/base_spec.rb' - 'spec/pin/method_spec.rb' @@ -1086,7 +1085,6 @@ RSpec/ImplicitExpect: RSpec/InstanceVariable: Exclude: - 'spec/api_map/config_spec.rb' - - 'spec/api_map_spec.rb' - 'spec/diagnostics/require_not_found_spec.rb' - 'spec/language_server/host/dispatch_spec.rb' - 'spec/language_server/host_spec.rb' @@ -1263,102 +1261,6 @@ RSpec/ScatteredLet: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata. -RSpec/SpecFilePathFormat: - Exclude: - - '**/spec/routing/**/*' - - 'spec/api_map/cache_spec.rb' - - 'spec/api_map/config_spec.rb' - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/api_map/store_spec.rb' - - 'spec/api_map_spec.rb' - - 'spec/convention/activesupport_concern_spec.rb' - - 'spec/convention/struct_definition_spec.rb' - - 'spec/convention_spec.rb' - - 'spec/diagnostics/base_spec.rb' - - 'spec/diagnostics/require_not_found_spec.rb' - - 'spec/diagnostics/rubocop_helpers_spec.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/diagnostics/type_check_spec.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/diagnostics_spec.rb' - - 'spec/doc_map_spec.rb' - - 'spec/gem_pins_spec.rb' - - 'spec/language_server/host/diagnoser_spec.rb' - - 'spec/language_server/host/dispatch_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/message/text_document/formatting_spec.rb' - - 'spec/language_server/message/text_document/hover_spec.rb' - - 'spec/language_server/message/text_document/rename_spec.rb' - - 'spec/language_server/message/text_document/type_definition_spec.rb' - - 'spec/language_server/message/workspace/did_change_configuration_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/message_spec.rb' - - 'spec/language_server/transport/adapter_spec.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - - 'spec/language_server/uri_helpers_spec.rb' - - 'spec/library_spec.rb' - - 'spec/logging_spec.rb' - - 'spec/parser/flow_sensitive_typing_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/parser/node_processor_spec.rb' - - 'spec/parser_spec.rb' - - 'spec/pin/base_spec.rb' - - 'spec/pin/base_variable_spec.rb' - - 'spec/pin/block_spec.rb' - - 'spec/pin/constant_spec.rb' - - 'spec/pin/delegated_method_spec.rb' - - 'spec/pin/documenting_spec.rb' - - 'spec/pin/instance_variable_spec.rb' - - 'spec/pin/keyword_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/pin/namespace_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/pin/search_spec.rb' - - 'spec/pin/symbol_spec.rb' - - 'spec/position_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/rbs_map_spec.rb' - - 'spec/shell_spec.rb' - - 'spec/source/chain/array_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/chain/constant_spec.rb' - - 'spec/source/chain/global_variable_spec.rb' - - 'spec/source/chain/head_spec.rb' - - 'spec/source/chain/instance_variable_spec.rb' - - 'spec/source/chain/link_spec.rb' - - 'spec/source/chain/literal_spec.rb' - - 'spec/source/chain/z_super_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source/change_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source/updater_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' - - 'spec/type_checker/checks_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker/levels/strong_spec.rb' - - 'spec/type_checker/levels/typed_spec.rb' - - 'spec/type_checker/rules_spec.rb' - - 'spec/type_checker_spec.rb' - - 'spec/workspace/config_spec.rb' - - 'spec/workspace_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' - - 'spec/yard_map/mapper_spec.rb' - RSpec/StubbedMock: Exclude: - 'spec/language_server/host/message_worker_spec.rb' @@ -2225,12 +2127,6 @@ Style/RedundantFreeze: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/source_map/mapper.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowComments. -Style/RedundantInitialize: - Exclude: - - 'lib/solargraph/rbs_map/core_map.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: @@ -2258,24 +2154,13 @@ Style/RedundantRegexpArgument: - 'spec/diagnostics/rubocop_spec.rb' - 'spec/language_server/host_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantRegexpCharacterClass: - Exclude: - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/source_chainer.rb' - # This cop supports safe autocorrection (--autocorrect). Style/RedundantRegexpEscape: Exclude: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/diagnostics/rubocop.rb' - 'lib/solargraph/language_server/uri_helpers.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source/change.rb' - - 'lib/solargraph/source/cursor.rb' - 'lib/solargraph/source_map/clip.rb' - 'lib/solargraph/source_map/mapper.rb' @@ -2638,7 +2523,6 @@ YARD/MismatchName: Exclude: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/gem_pins.rb' - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/language_server/host/dispatch.rb' - 'lib/solargraph/language_server/request.rb' From 7697cd35891e71839202fc3962615e2117fbe9eb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 09:36:46 -0400 Subject: [PATCH 142/460] Fix merge --- .rubocop.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index de021c63c..51b022f51 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -38,9 +38,6 @@ Metrics/ParameterLists: Max: 7 CountKeywordArgs: false -RSpec/SpecFilePathFormat: - Enabled: false - # we tend to use @@ and the risk doesn't seem high Style/ClassVars: Enabled: false From acdfbb9b58e47e6054423a6344bfa970c7d3c53a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 09:37:25 -0400 Subject: [PATCH 143/460] Fix merge --- spec/rbs_map/conversions_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index cf429f58f..da82fbed3 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -46,7 +46,6 @@ def bar: () -> untyped end end - # https://github.com/castwide/solargraph/issues/1042 context 'with Hash superclass with untyped value and alias' do let(:rbs) do From d8072245f718a123f8a1dc1ac428fc9ef129e40d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 09:39:41 -0400 Subject: [PATCH 144/460] Fix RuboCop issue --- spec/rbs_map/conversions_spec.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index da82fbed3..30354c31a 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -15,10 +15,6 @@ Solargraph::RbsMap::Conversions.new(loader: loader) end - let(:pins) do - conversions.pins - end - before do rbs_file = File.join(temp_dir, 'foo.rbs') File.write(rbs_file, rbs) @@ -35,7 +31,7 @@ def bar: () -> untyped RBS end - subject(:method_pin) { pins.find { |pin| pin.path == 'Foo#bar' } } + subject(:method_pin) { conversions.pins.find { |pin| pin.path == 'Foo#bar' } } it { should_not be_nil } From 5ed39e23541908fc857b79a77086dab3c5b53a0f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 09:54:12 -0400 Subject: [PATCH 145/460] Drop unneeded @sg-ignores --- lib/solargraph/parser/parser_gem/node_processors/send_node.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index cedec1b89..48184b52b 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -36,15 +36,12 @@ def process process_autoload elsif method_name == :private_constant process_private_constant - # @sg-ignore elsif method_name == :alias_method && node.children[2] && node.children[2] && node.children[2].type == :sym && node.children[3] && node.children[3].type == :sym process_alias_method - # @sg-ignore elsif method_name == :private_class_method && node.children[2].is_a?(AST::Node) # Processing a private class can potentially handle children on its own return if process_private_class_method end - # @sg-ignore elsif method_name == :require && node.children[0].to_s == '(const nil :Bundler)' pins.push Pin::Reference::Require.new(Solargraph::Location.new(region.filename, Solargraph::Range.from_to(0, 0, 0, 0)), 'bundler/require', source: :parser) end From 7fc5fcd51511954f7e734c7e3ab8ba63b1d2a956 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 10:14:16 -0400 Subject: [PATCH 146/460] Annotation fixes for strong typechecking --- lib/solargraph/pin/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index e6a630562..7f1509d9b 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -456,7 +456,7 @@ def nearly? other # Pin equality is determined using the #nearly? method and also # requiring both pins to have the same location. # - # @param other [self] + # @param other [Object] def == other return false unless nearly? other comments == other.comments && location == other.location From e43a7cb586e0b4d53564db224b5de8623f88e0f8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 10:18:41 -0400 Subject: [PATCH 147/460] Fix merge issue --- lib/solargraph/pin/local_variable.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 78e0b287d..36b75773c 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -26,9 +26,6 @@ def combine_with(other, attrs={}) assignment: assert_same(other, :assignment), presence_certain: assert_same(other, :presence_certain?), }.merge(attrs) - # @sg-ignore Wrong argument type for - # Solargraph::Pin::Base#assert_same: other expected - # Solargraph::Pin::Base, received self new_attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence) super(other, new_attrs) From 96e23889250ec1a4850c4d27c1d9f05969af7944 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 10:58:31 -0400 Subject: [PATCH 148/460] Fix merge --- lib/solargraph/shell.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index bb4df5587..25604fdd4 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -241,22 +241,16 @@ 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 :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) - # @sg-ignore Unresolved call to options # @type [Array] pins = if options[:stack] scope, ns, meth = if path.include? '#' @@ -277,12 +271,9 @@ def pin path exit 1 end pins.each do |pin| - # @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) next @@ -322,7 +313,6 @@ def do_cache gemspec, api_map # @param type [ComplexType] # @return [void] def print_type(type) - # @sg-ignore Unresolved call to options if options[:rbs] puts type.to_rbs else @@ -333,7 +323,6 @@ def print_type(type) # @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 From 5724178b86d7853f6e203c525751bc31449b38a6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 11:09:58 -0400 Subject: [PATCH 149/460] Debug --- .github/workflows/linting.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 8abbf51ef..c0f10e8d7 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -51,6 +51,9 @@ jobs: path: | /home/runner/.cache/solargraph + - name: Debug + run: bundle info rbs + - name: Install gem types run: bundle exec rbs collection install From 6ef2447c5f8bc4b8c4e0d693543c12197cbac6fa Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 11:18:07 -0400 Subject: [PATCH 150/460] Undo last change --- lib/solargraph/shell.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 25604fdd4..bb4df5587 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -241,16 +241,22 @@ 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 :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) + # @sg-ignore Unresolved call to options # @type [Array] pins = if options[:stack] scope, ns, meth = if path.include? '#' @@ -271,9 +277,12 @@ def pin path exit 1 end pins.each do |pin| + # @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) next @@ -313,6 +322,7 @@ def do_cache gemspec, api_map # @param type [ComplexType] # @return [void] def print_type(type) + # @sg-ignore Unresolved call to options if options[:rbs] puts type.to_rbs else @@ -323,6 +333,7 @@ def print_type(type) # @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 From ffa40f9806850c6c85070f5b59b203ef815bfc8c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 17:58:18 -0400 Subject: [PATCH 151/460] Drop @sg-ignores --- lib/solargraph/shell.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index bb4df5587..25604fdd4 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -241,22 +241,16 @@ 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 :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) - # @sg-ignore Unresolved call to options # @type [Array] pins = if options[:stack] scope, ns, meth = if path.include? '#' @@ -277,12 +271,9 @@ def pin path exit 1 end pins.each do |pin| - # @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) next @@ -322,7 +313,6 @@ def do_cache gemspec, api_map # @param type [ComplexType] # @return [void] def print_type(type) - # @sg-ignore Unresolved call to options if options[:rbs] puts type.to_rbs else @@ -333,7 +323,6 @@ def print_type(type) # @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 From c96d16519c33015e5a38e407333e1b086382dd10 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 18:26:57 -0400 Subject: [PATCH 152/460] Fix lint issues --- .rubocop.yml | 7 +- .rubocop_todo.yml | 120 +------------------------------ spec/rbs_map/conversions_spec.rb | 7 +- 3 files changed, 7 insertions(+), 127 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 29a840b9f..51b022f51 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,6 +21,10 @@ AllCops: - "vendor/**/.*" TargetRubyVersion: 3.0 +# We don't use the spec/solargraph directory +RSpec/SpecFilePathFormat: + Enabled: false + Style/MethodDefParentheses: EnforcedStyle: require_no_parentheses @@ -34,9 +38,6 @@ Metrics/ParameterLists: Max: 7 CountKeywordArgs: false -RSpec/SpecFilePathFormat: - Enabled: false - # we tend to use @@ and the risk doesn't seem high Style/ClassVars: Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b8d123824..3716d5983 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.80.0. +# using RuboCop version 1.80.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -920,9 +920,9 @@ RSpec/DescribeClass: - '**/spec/routing/**/*' - '**/spec/system/**/*' - '**/spec/views/**/*' + - 'spec/api_map_method_spec.rb' - 'spec/complex_type_spec.rb' - 'spec/source_map/node_processor_spec.rb' - - 'spec/api_map_method_spec.rb' # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. @@ -1045,7 +1045,6 @@ RSpec/ExampleLength: # DisallowedExamples: works RSpec/ExampleWording: Exclude: - - 'spec/convention/struct_definition_spec.rb' - 'spec/pin/base_spec.rb' - 'spec/pin/method_spec.rb' @@ -1086,7 +1085,6 @@ RSpec/ImplicitExpect: RSpec/InstanceVariable: Exclude: - 'spec/api_map/config_spec.rb' - - 'spec/api_map_spec.rb' - 'spec/diagnostics/require_not_found_spec.rb' - 'spec/language_server/host/dispatch_spec.rb' - 'spec/language_server/host_spec.rb' @@ -1263,102 +1261,6 @@ RSpec/ScatteredLet: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata. -RSpec/SpecFilePathFormat: - Exclude: - - '**/spec/routing/**/*' - - 'spec/api_map/cache_spec.rb' - - 'spec/api_map/config_spec.rb' - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/api_map/store_spec.rb' - - 'spec/api_map_spec.rb' - - 'spec/convention/activesupport_concern_spec.rb' - - 'spec/convention/struct_definition_spec.rb' - - 'spec/convention_spec.rb' - - 'spec/diagnostics/base_spec.rb' - - 'spec/diagnostics/require_not_found_spec.rb' - - 'spec/diagnostics/rubocop_helpers_spec.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/diagnostics/type_check_spec.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/diagnostics_spec.rb' - - 'spec/doc_map_spec.rb' - - 'spec/gem_pins_spec.rb' - - 'spec/language_server/host/diagnoser_spec.rb' - - 'spec/language_server/host/dispatch_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/message/text_document/formatting_spec.rb' - - 'spec/language_server/message/text_document/hover_spec.rb' - - 'spec/language_server/message/text_document/rename_spec.rb' - - 'spec/language_server/message/text_document/type_definition_spec.rb' - - 'spec/language_server/message/workspace/did_change_configuration_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/message_spec.rb' - - 'spec/language_server/transport/adapter_spec.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - - 'spec/language_server/uri_helpers_spec.rb' - - 'spec/library_spec.rb' - - 'spec/logging_spec.rb' - - 'spec/parser/flow_sensitive_typing_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/parser/node_processor_spec.rb' - - 'spec/parser_spec.rb' - - 'spec/pin/base_spec.rb' - - 'spec/pin/base_variable_spec.rb' - - 'spec/pin/block_spec.rb' - - 'spec/pin/constant_spec.rb' - - 'spec/pin/delegated_method_spec.rb' - - 'spec/pin/documenting_spec.rb' - - 'spec/pin/instance_variable_spec.rb' - - 'spec/pin/keyword_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/pin/namespace_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/pin/search_spec.rb' - - 'spec/pin/symbol_spec.rb' - - 'spec/position_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/rbs_map_spec.rb' - - 'spec/shell_spec.rb' - - 'spec/source/chain/array_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/chain/constant_spec.rb' - - 'spec/source/chain/global_variable_spec.rb' - - 'spec/source/chain/head_spec.rb' - - 'spec/source/chain/instance_variable_spec.rb' - - 'spec/source/chain/link_spec.rb' - - 'spec/source/chain/literal_spec.rb' - - 'spec/source/chain/z_super_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source/change_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source/updater_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' - - 'spec/type_checker/checks_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker/levels/strong_spec.rb' - - 'spec/type_checker/levels/typed_spec.rb' - - 'spec/type_checker/rules_spec.rb' - - 'spec/type_checker_spec.rb' - - 'spec/workspace/config_spec.rb' - - 'spec/workspace_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' - - 'spec/yard_map/mapper_spec.rb' - RSpec/StubbedMock: Exclude: - 'spec/language_server/host/message_worker_spec.rb' @@ -2225,12 +2127,6 @@ Style/RedundantFreeze: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/source_map/mapper.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowComments. -Style/RedundantInitialize: - Exclude: - - 'lib/solargraph/rbs_map/core_map.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: @@ -2258,24 +2154,13 @@ Style/RedundantRegexpArgument: - 'spec/diagnostics/rubocop_spec.rb' - 'spec/language_server/host_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantRegexpCharacterClass: - Exclude: - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/source_chainer.rb' - # This cop supports safe autocorrection (--autocorrect). Style/RedundantRegexpEscape: Exclude: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/diagnostics/rubocop.rb' - 'lib/solargraph/language_server/uri_helpers.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source/change.rb' - - 'lib/solargraph/source/cursor.rb' - 'lib/solargraph/source_map/clip.rb' - 'lib/solargraph/source_map/mapper.rb' @@ -2638,7 +2523,6 @@ YARD/MismatchName: Exclude: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/gem_pins.rb' - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/language_server/host/dispatch.rb' - 'lib/solargraph/language_server/request.rb' diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index cf429f58f..30354c31a 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -15,10 +15,6 @@ Solargraph::RbsMap::Conversions.new(loader: loader) end - let(:pins) do - conversions.pins - end - before do rbs_file = File.join(temp_dir, 'foo.rbs') File.write(rbs_file, rbs) @@ -35,7 +31,7 @@ def bar: () -> untyped RBS end - subject(:method_pin) { pins.find { |pin| pin.path == 'Foo#bar' } } + subject(:method_pin) { conversions.pins.find { |pin| pin.path == 'Foo#bar' } } it { should_not be_nil } @@ -46,7 +42,6 @@ def bar: () -> untyped end end - # https://github.com/castwide/solargraph/issues/1042 context 'with Hash superclass with untyped value and alias' do let(:rbs) do From 5681ca274c8c516e20efe31689e7325b4ea69c09 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 18:27:05 -0400 Subject: [PATCH 153/460] Fix resolution issue in Bundler RBS --- rbs/fills/bundler/0/bundler.rbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rbs/fills/bundler/0/bundler.rbs b/rbs/fills/bundler/0/bundler.rbs index 4af422af1..b87780c80 100644 --- a/rbs/fills/bundler/0/bundler.rbs +++ b/rbs/fills/bundler/0/bundler.rbs @@ -677,7 +677,7 @@ class Bundler::Dsl @valid_keys: untyped - include RubyDsl + include ::Bundler::RubyDsl def self.evaluate: (untyped gemfile, untyped lockfile, untyped unlock) -> untyped From 6166a2e55f1a00e0dd038867a35f8e8015ea631e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 18:43:45 -0400 Subject: [PATCH 154/460] Flush cache --- .github/workflows/linting.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 8abbf51ef..897a816fe 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -46,8 +46,8 @@ jobs: key: | 2025-06-26-09-${{ runner.os }}-dot-cache-${{ hashFiles('Gemfile.lock') }} restore-keys: | - 2025-06-26-09-${{ runner.os }}-dot-cache - 2025-06-26-09-${{ runner.os }}-dot-cache- + 2025-08-${{ runner.os }}-dot-cache + 2025-08-${{ runner.os }}-dot-cache- path: | /home/runner/.cache/solargraph From 834cca5387c0b89b57b6f21ec2d52399fe9e388d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 18:44:22 -0400 Subject: [PATCH 155/460] Flush cache --- .github/workflows/linting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 897a816fe..d1598a3fc 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -33,7 +33,7 @@ jobs: ruby-version: 3.4 bundler: latest bundler-cache: true - cache-version: 2025-06-06 + cache-version: 2025-08 - name: Update to best available RBS run: | From 0ae9bd7fc52a69296ce2419bc0d77a92022f2878 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 18:51:34 -0400 Subject: [PATCH 156/460] Flush cache --- .github/workflows/linting.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index c0f10e8d7..ab137b720 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -44,10 +44,10 @@ jobs: uses: actions/cache/restore@v4 with: key: | - 2025-06-26-09-${{ runner.os }}-dot-cache-${{ hashFiles('Gemfile.lock') }} + 2025-08-${{ runner.os }}-dot-cache-${{ hashFiles('Gemfile.lock') }} restore-keys: | - 2025-06-26-09-${{ runner.os }}-dot-cache - 2025-06-26-09-${{ runner.os }}-dot-cache- + 2025-08-${{ runner.os }}-dot-cache + 2025-08-${{ runner.os }}-dot-cache- path: | /home/runner/.cache/solargraph From acac7a73c7eed71cf36c6275204e03deaec04947 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 18:57:03 -0400 Subject: [PATCH 157/460] Align sg-ignore messages on branches --- lib/solargraph/doc_map.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 8b6234c85..f52828f2c 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -177,10 +177,10 @@ def load_serialized_gem_pins @uncached_yard_gemspecs = [] @uncached_rbs_collection_gemspecs = [] with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v } - # @sg-ignore Wrong argument type for Hash.[]: arg_0 expected _ToHash), undefined>, received Array)> + # @sg-ignore Need support for RBS duck interfaces like _ToHash # @type [Array] paths = Hash[without_gemspecs].keys - # @sg-ignore Wrong argument type for Hash.[]: arg_0 expected _ToHash), undefined>, received Array)> + # @sg-ignore Need support for RBS duck interfaces like _ToHash # @type [Array] gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a From 0d1d6503306a1a00d7116eb9f2f15ba3f5fcafc8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Sep 2025 07:26:33 -0400 Subject: [PATCH 158/460] Debug --- .github/workflows/linting.yml | 28 +++++++++++++++------------- .github/workflows/typecheck.yml | 6 +++++- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index ab137b720..eb658772a 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -39,20 +39,22 @@ jobs: run: | bundle update rbs # use latest available for this Ruby version - - name: Restore cache of gem annotations - id: dot-cache-restore - uses: actions/cache/restore@v4 - with: - key: | - 2025-08-${{ runner.os }}-dot-cache-${{ hashFiles('Gemfile.lock') }} - restore-keys: | - 2025-08-${{ runner.os }}-dot-cache - 2025-08-${{ runner.os }}-dot-cache- - path: | - /home/runner/.cache/solargraph + # - name: Restore cache of gem annotations + # id: dot-cache-restore + # uses: actions/cache/restore@v4 + # with: + # key: | + # 2025-08-${{ runner.os }}-dot-cache-${{ hashFiles('Gemfile.lock') }} + # restore-keys: | + # 2025-08-${{ runner.os }}-dot-cache + # 2025-08-${{ runner.os }}-dot-cache- + # path: | + # /home/runner/.cache/solargraph - name: Debug - run: bundle info rbs + run: | + bundle info rbs + bundle info thor - name: Install gem types run: bundle exec rbs collection install @@ -60,7 +62,7 @@ jobs: - name: Overcommit run: | bundle exec overcommit --sign - SOLARGRAPH_ASSERTS=on bundle exec overcommit --run --diff origin/master + OVERCOMMIT_DEBUG=1 SOLARGRAPH_ASSERTS=on bundle exec overcommit --run --diff origin/master rubocop: name: rubocop runs-on: ubuntu-latest diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index f40977acf..5298ce187 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -17,7 +17,7 @@ permissions: contents: read jobs: - solargraph_typed: + solargraph_strong: name: Solargraph / strong runs-on: ubuntu-latest @@ -35,5 +35,9 @@ jobs: bundle update rbs # use latest available for this Ruby version - name: Install gem types run: bundle exec rbs collection install + - name: Debug + run: | + bundle info rbs + bundle info thor - name: Typecheck self run: SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strong From 5ea4723fc332f8dbba491bc634990d916658cff3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Sep 2025 07:30:58 -0400 Subject: [PATCH 159/460] Debug --- lib/solargraph/yardoc.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 2d709f778..a424841a3 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -26,7 +26,7 @@ def cache(yard_plugins, gemspec) stdout_and_stderr_str, status = Open3.capture2e(cmd, chdir: gemspec.gem_dir) unless status.success? Solargraph.logger.warn { "YARD failed running #{cmd.inspect} in #{gemspec.gem_dir}" } - Solargraph.logger.info stdout_and_stderr_str + Solargraph.logger.warn stdout_and_stderr_str end path end From aa8c4330bc0156a1e9def712f9616545c61846d5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Sep 2025 07:52:39 -0400 Subject: [PATCH 160/460] Pull in overcommit fix --- lib/solargraph/yardoc.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index a424841a3..9d36933ec 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -23,7 +23,7 @@ def cache(yard_plugins, gemspec) yard_plugins.each { |plugin| cmd << " --plugin #{plugin}" } Solargraph.logger.debug { "Running: #{cmd}" } # @todo set these up to run in parallel - stdout_and_stderr_str, status = Open3.capture2e(cmd, chdir: gemspec.gem_dir) + stdout_and_stderr_str, status = Open3.capture2e(current_bundle_env_tweaks, cmd, chdir: gemspec.gem_dir) unless status.success? Solargraph.logger.warn { "YARD failed running #{cmd.inspect} in #{gemspec.gem_dir}" } Solargraph.logger.warn stdout_and_stderr_str @@ -57,5 +57,22 @@ def load!(gemspec) YARD::Registry.load! PinCache.yardoc_path gemspec YARD::Registry.all end + + # If the BUNDLE_GEMFILE environment variable is set, we need to + # make sure it's an absolute path, as we'll be changing + # directories. + # + # 'bundle exec' sets an absolute path here, but at least the + # overcommit gem does not, breaking on-the-fly documention with a + # spawned yardoc command from our current bundle + # + # @return [Hash{String => String}] a hash of environment variables to override + def current_bundle_env_tweaks + tweaks = {} + if ENV['BUNDLE_GEMFILE'] && !ENV['BUNDLE_GEMFILE'].empty? + tweaks['BUNDLE_GEMFILE'] = File.expand_path(ENV['BUNDLE_GEMFILE']) + end + tweaks + end end end From f623a73ad88f44d3ff6e6873a1682f33a07d431b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Sep 2025 07:52:39 -0400 Subject: [PATCH 161/460] Pull in overcommit fix --- lib/solargraph/yardoc.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 625e41ce4..ed638a7ce 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -26,7 +26,7 @@ def cache(yard_plugins, gemspec) # # @sg-ignore RBS gem doesn't reflect that Open3.* also include # kwopts from Process.spawn() - stdout_and_stderr_str, status = Open3.capture2e(cmd, chdir: gemspec.gem_dir) + stdout_and_stderr_str, status = Open3.capture2e(current_bundle_env_tweaks, cmd, chdir: gemspec.gem_dir) unless status.success? Solargraph.logger.warn { "YARD failed running #{cmd.inspect} in #{gemspec.gem_dir}" } Solargraph.logger.info stdout_and_stderr_str @@ -60,5 +60,22 @@ def load!(gemspec) YARD::Registry.load! PinCache.yardoc_path gemspec YARD::Registry.all end + + # If the BUNDLE_GEMFILE environment variable is set, we need to + # make sure it's an absolute path, as we'll be changing + # directories. + # + # 'bundle exec' sets an absolute path here, but at least the + # overcommit gem does not, breaking on-the-fly documention with a + # spawned yardoc command from our current bundle + # + # @return [Hash{String => String}] a hash of environment variables to override + def current_bundle_env_tweaks + tweaks = {} + if ENV['BUNDLE_GEMFILE'] && !ENV['BUNDLE_GEMFILE'].empty? + tweaks['BUNDLE_GEMFILE'] = File.expand_path(ENV['BUNDLE_GEMFILE']) + end + tweaks + end end end From a8b678b1a1abec6a571ad321b4245575541bf969 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Sep 2025 08:56:57 -0400 Subject: [PATCH 162/460] Add spec --- .rubocop.yml | 3 +++ spec/yardoc_spec.rb | 52 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 spec/yardoc_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index a73324db2..c17a56410 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,6 +21,9 @@ AllCops: - "vendor/**/.*" TargetRubyVersion: 3.0 +RSpec/SpecFilePathFormat: + Enabled: false + Style/MethodDefParentheses: EnforcedStyle: require_no_parentheses diff --git a/spec/yardoc_spec.rb b/spec/yardoc_spec.rb new file mode 100644 index 000000000..34dcad45c --- /dev/null +++ b/spec/yardoc_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'tmpdir' +require 'open3' + +describe Solargraph::Yardoc do + let(:gem_yardoc_path) do + Solargraph::PinCache.yardoc_path gemspec + end + + before do + FileUtils.mkdir_p(gem_yardoc_path) + end + + describe '#cache' do + let(:api_map) { Solargraph::ApiMap.new } + let(:doc_map) { api_map.doc_map } + let(:gemspec) { Gem::Specification.find_by_path('rubocop') } + let(:output) { '' } + + before do + allow(Solargraph.logger).to receive(:warn) + allow(Solargraph.logger).to receive(:info) + FileUtils.rm_rf(gem_yardoc_path) + end + + context 'when given a relative BUNDLE_GEMFILE path' do + around do |example| + # turn absolute BUNDLE_GEMFILE path into relative + existing_gemfile = ENV.fetch('BUNDLE_GEMFILE', nil) + current_dir = Dir.pwd + # remove prefix current_dir from path + ENV['BUNDLE_GEMFILE'] = existing_gemfile.sub("#{current_dir}/", '') + raise 'could not figure out relative path' if Pathname.new(ENV.fetch('BUNDLE_GEMFILE', nil)).absolute? + example.run + ENV['BUNDLE_GEMFILE'] = existing_gemfile + end + + it 'sends Open3 an absolute path' do + called_with = nil + allow(Open3).to receive(:capture2e) do |*args| + called_with = args + ['output', instance_double(Process::Status, success?: true)] + end + + described_class.cache([], gemspec) + + expect(called_with[0]['BUNDLE_GEMFILE']).to start_with('/') + end + end + end +end From e4e40915fddd53e7be8f90d66a098279bae3b44b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Sep 2025 11:33:11 -0400 Subject: [PATCH 163/460] Another set of @sg-ignores --- .../parser_gem/node_processors/sclass_node.rb | 15 ++++++++++++--- .../parser_gem/node_processors/send_node.rb | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb index e5b049b06..1b573ed93 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb @@ -7,9 +7,18 @@ module NodeProcessors class SclassNode < Parser::NodeProcessor::Base def process sclass = node.children[0] - if sclass.is_a?(AST::Node) && sclass.type == :self + # @todo Changing Parser::AST::Node to AST::Node below will + # cause type errors at strong level because the combined + # pin for AST::Node#children has return type + # "Array, Array". YARD annotations in AST + # provided the Array, RBS for Array. We + # should probably have a rule to combine "A, A"" + # types to "A" if the "A" comes from YARD, with the + # rationale that folks tend to be less formal with types in + # YARD. + if sclass.is_a?(::Parser::AST::Node) && sclass.type == :self closure = region.closure - elsif sclass.is_a?(AST::Node) && sclass.type == :casgn + elsif sclass.is_a?(::Parser::AST::Node) && sclass.type == :casgn names = [region.closure.namespace, region.closure.name] if sclass.children[0].nil? && names.last != sclass.children[1].to_s names << sclass.children[1].to_s @@ -18,7 +27,7 @@ def process end name = names.reject(&:empty?).join('::') closure = Solargraph::Pin::Namespace.new(name: name, location: region.closure.location, source: :parser) - elsif sclass.is_a?(AST::Node) && sclass.type == :const + elsif sclass.is_a?(::Parser::AST::Node) && sclass.type == :const names = [region.closure.namespace, region.closure.name] also = NodeMethods.unpack_name(sclass) if also != region.closure.name diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index cedec1b89..fff3addf6 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -66,7 +66,7 @@ def process_visibility return process_children end # :nocov: - if child.is_a?(AST::Node) && (child.type == :sym || child.type == :str) + if child.is_a?(::Parser::AST::Node) && (child.type == :sym || child.type == :str) name = child.children[0].to_s matches = pins.select{ |pin| pin.is_a?(Pin::Method) && pin.name == name && pin.namespace == region.closure.full_context.namespace && pin.context.scope == (region.scope || :instance)} matches.each do |pin| From 9f7fe59e6f172486fc3cce20fad1d8d5ce29bfa6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 05:45:12 -0400 Subject: [PATCH 164/460] Add --references flag (superclass for now) --- lib/solargraph/api_map.rb | 31 ++++++++++++------------- lib/solargraph/shell.rb | 49 +++++++++++++++++++++++++-------------- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 4e8332080..54019d1b8 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -730,6 +730,21 @@ def inner_get_methods_from_reference fq_reference_tag, namespace_pin, type, scop methods end + # @param fq_sub_tag [String] + # @return [String, nil] + def qualify_superclass fq_sub_tag + fq_sub_type = ComplexType.try_parse(fq_sub_tag) + fq_sub_ns = fq_sub_type.name + sup_tag = store.get_superclass(fq_sub_tag) + sup_type = ComplexType.try_parse(sup_tag) + sup_ns = sup_type.name + return nil if sup_tag.nil? + parts = fq_sub_ns.split('::') + last = parts.pop + parts.pop if last == sup_ns + qualify(sup_tag, parts.join('::')) + end + private # A hash of source maps with filename keys. @@ -865,21 +880,6 @@ def qualify_lower namespace, context qualify namespace, context.split('::')[0..-2].join('::') end - # @param fq_sub_tag [String] - # @return [String, nil] - def qualify_superclass fq_sub_tag - fq_sub_type = ComplexType.try_parse(fq_sub_tag) - fq_sub_ns = fq_sub_type.name - sup_tag = store.get_superclass(fq_sub_tag) - sup_type = ComplexType.try_parse(sup_tag) - sup_ns = sup_type.name - return nil if sup_tag.nil? - parts = fq_sub_ns.split('::') - last = parts.pop - parts.pop if last == sup_ns - qualify(sup_tag, parts.join('::')) - end - # @param name [String] Namespace to fully qualify # @param root [String] The context to search # @param skip [Set] Contexts already searched @@ -949,7 +949,6 @@ def resolve_fqns fqns assignment = constant.assignment - # @sg-ignore Wrong argument type for Solargraph::ApiMap#resolve_trivial_constant: node expected AST::Node, received Parser::AST::Node, nil target_ns = resolve_trivial_constant(assignment) if assignment return nil unless target_ns qualify_namespace target_ns, constant_namespace diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 21a53172f..11039b187 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -246,6 +246,8 @@ def list # @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 @@ -253,27 +255,34 @@ def list # @return [void] def pin path api_map = Solargraph::ApiMap.load_with_cache('.', $stderr) - - # @sg-ignore Unresolved call to options - # @type [Array] - pins = if 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 - api_map.get_method_stack(ns, meth, scope: scope) - else - api_map.get_path_pins path - end - if pins.empty? + pins = api_map.get_path_pins path + references = {} + pin = pins.first + case pin + when nil $stderr.puts "Pin not found for path '#{path}'" exit 1 + when Pin::Method + if 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 + pins = api_map.get_method_stack(ns, meth, scope: scope) + end + when Pin::Namespace + 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 end + pins.each do |pin| # @sg-ignore Unresolved call to options if options[:typify] || options[:probe] @@ -288,6 +297,10 @@ def pin path print_pin(pin) end + references.each do |key, refpin| + puts "\n# #{key.to_s.capitalize}:\n\n" + print_pin(refpin) + end end private From 79e8cd900b13c60591e3bc76e3f5bffeb93bbdcb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 05:49:18 -0400 Subject: [PATCH 165/460] Add another @sg-ignore --- lib/solargraph/shell.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index c56be0107..aa7214196 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -278,6 +278,7 @@ def pin path pins = api_map.get_method_stack(ns, meth, scope: scope) end 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 From da205a3b208c7aad3aedced43b889e70ab247348 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 05:52:39 -0400 Subject: [PATCH 166/460] Catch up with .rubocop_todo.yml --- .rubocop_todo.yml | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c711cd674..c3d28df49 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -920,9 +920,9 @@ RSpec/DescribeClass: - '**/spec/routing/**/*' - '**/spec/system/**/*' - '**/spec/views/**/*' + - 'spec/api_map_method_spec.rb' - 'spec/complex_type_spec.rb' - 'spec/source_map/node_processor_spec.rb' - - 'spec/api_map_method_spec.rb' # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. @@ -1045,7 +1045,6 @@ RSpec/ExampleLength: # DisallowedExamples: works RSpec/ExampleWording: Exclude: - - 'spec/convention/struct_definition_spec.rb' - 'spec/pin/base_spec.rb' - 'spec/pin/method_spec.rb' @@ -1086,7 +1085,6 @@ RSpec/ImplicitExpect: RSpec/InstanceVariable: Exclude: - 'spec/api_map/config_spec.rb' - - 'spec/api_map_spec.rb' - 'spec/diagnostics/require_not_found_spec.rb' - 'spec/language_server/host/dispatch_spec.rb' - 'spec/language_server/host_spec.rb' @@ -1354,10 +1352,6 @@ RSpec/SpecFilePathFormat: - 'spec/yard_map/mapper/to_method_spec.rb' - 'spec/yard_map/mapper_spec.rb' -RSpec/StubbedMock: - Exclude: - - 'spec/language_server/host/message_worker_spec.rb' - # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. RSpec/VerifiedDoubles: Exclude: @@ -2219,12 +2213,6 @@ Style/RedundantFreeze: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/source_map/mapper.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowComments. -Style/RedundantInitialize: - Exclude: - - 'lib/solargraph/rbs_map/core_map.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: @@ -2252,24 +2240,13 @@ Style/RedundantRegexpArgument: - 'spec/diagnostics/rubocop_spec.rb' - 'spec/language_server/host_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantRegexpCharacterClass: - Exclude: - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/source_chainer.rb' - # This cop supports safe autocorrection (--autocorrect). Style/RedundantRegexpEscape: Exclude: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/diagnostics/rubocop.rb' - 'lib/solargraph/language_server/uri_helpers.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source/change.rb' - - 'lib/solargraph/source/cursor.rb' - 'lib/solargraph/source_map/clip.rb' - 'lib/solargraph/source_map/mapper.rb' @@ -2632,7 +2609,6 @@ YARD/MismatchName: Exclude: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/gem_pins.rb' - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/language_server/host/dispatch.rb' - 'lib/solargraph/language_server/request.rb' From 4c486edec853f702aaed8384379c17c9226d4f0f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 05:58:48 -0400 Subject: [PATCH 167/460] Add another @sg-ignore --- lib/solargraph/shell.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index aa7214196..cb919476c 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -265,6 +265,7 @@ def pin path $stderr.puts "Pin not found for path '#{path}'" exit 1 when Pin::Method + # @sg-ignore Unresolved call to options if options[:stack] scope, ns, meth = if path.include? '#' [:instance, *path.split('#', 2)] From c5ee7123532b07c0e3a254e98a28da8e741a9b9e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 06:02:35 -0400 Subject: [PATCH 168/460] Drop @sg-ignore --- lib/solargraph/shell.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index a1d644551..a9f4dc189 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -259,7 +259,6 @@ def pin path $stderr.puts "Pin not found for path '#{path}'" exit 1 when Pin::Method - # @sg-ignore Unresolved call to options if options[:stack] scope, ns, meth = if path.include? '#' [:instance, *path.split('#', 2)] @@ -273,7 +272,6 @@ def pin path pins = api_map.get_method_stack(ns, meth, scope: scope) end 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 From 200e7e42b847bfb554de097d9ba5da6f182a7196 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 07:57:19 -0400 Subject: [PATCH 169/460] Tolerate case statement in specs --- spec/shell_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index e3c85c6e0..b9dc6b327 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -62,6 +62,7 @@ def bundle_exec(*cmd) 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 @@ -105,6 +106,8 @@ def bundle_exec(*cmd) 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') @@ -140,6 +143,7 @@ def bundle_exec(*cmd) 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 = {} From 5658f301390206872dd1a7651a79651c00b7636c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 08:32:45 -0400 Subject: [PATCH 170/460] Understand "Parser::AST::Node < AST::Node" in RBS Right now the parser gem RBS doesn't get handled correctly; it doesn't mark the AST::Node being inherited from as being rooted, meaning we end up resolving the superclass of Parser::AST::Node to be...Parser::AST::Node. --- lib/solargraph/rbs_map/conversions.rb | 6 +- spec/rbs_map/conversions_spec.rb | 134 +++++++++++++++++--------- 2 files changed, 90 insertions(+), 50 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 566f1513f..f941aeaee 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -163,9 +163,10 @@ def class_decl_to_pin decl generic_defaults[param.name.to_s] = ComplexType.parse(tag).force_rooted end end + class_name = decl.name.relative!.to_s class_pin = Solargraph::Pin::Namespace.new( type: :class, - name: decl.name.relative!.to_s, + name: class_name, closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, type_location: location_decl_to_pin_location(decl.location), @@ -180,11 +181,12 @@ def class_decl_to_pin decl if decl.super_class type = build_type(decl.super_class.name, decl.super_class.args) generic_values = type.all_params.map(&:to_s) + superclass_name = decl.super_class.name.to_s pins.push Solargraph::Pin::Reference::Superclass.new( type_location: location_decl_to_pin_location(decl.super_class.location), closure: class_pin, generic_values: generic_values, - name: decl.super_class.name.relative!.to_s, + name: superclass_name, source: :rbs ) end diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index ce800c29f..e6322443d 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -1,81 +1,119 @@ describe Solargraph::RbsMap::Conversions do - # create a temporary directory with the scope of the spec - around do |example| - require 'tmpdir' - Dir.mktmpdir("rspec-solargraph-") do |dir| - @temp_dir = dir - example.run + context 'with RBS to digest' do + # create a temporary directory with the scope of the spec + around do |example| + require 'tmpdir' + Dir.mktmpdir("rspec-solargraph-") do |dir| + @temp_dir = dir + example.run + end end - end - let(:conversions) do - loader = RBS::EnvironmentLoader.new(core_root: nil, repository: RBS::Repository.new(no_stdlib: false)) - loader.add(path: Pathname(temp_dir)) - Solargraph::RbsMap::Conversions.new(loader: loader) - end + let(:conversions) do + loader = RBS::EnvironmentLoader.new(core_root: nil, repository: RBS::Repository.new(no_stdlib: false)) + loader.add(path: Pathname(temp_dir)) + Solargraph::RbsMap::Conversions.new(loader: loader) + end - before do - rbs_file = File.join(temp_dir, 'foo.rbs') - File.write(rbs_file, rbs) - end + let(:api_map) { Solargraph::ApiMap.new } - attr_reader :temp_dir + before do + rbs_file = File.join(temp_dir, 'foo.rbs') + File.write(rbs_file, rbs) + api_map.index conversions.pins + end + + attr_reader :temp_dir - context 'with untyped response' do - let(:rbs) do - <<~RBS + context 'with untyped response' do + let(:rbs) do + <<~RBS class Foo def bar: () -> untyped end RBS - end + end - subject(:method_pin) { conversions.pins.find { |pin| pin.path == 'Foo#bar' } } + subject(:method_pin) { conversions.pins.find { |pin| pin.path == 'Foo#bar' } } - it { should_not be_nil } + it { should_not be_nil } - it { should be_a(Solargraph::Pin::Method) } + it { should be_a(Solargraph::Pin::Method) } - it 'maps untyped in RBS to undefined in Solargraph 'do - expect(method_pin.return_type.tag).to eq('undefined') + it 'maps untyped in RBS to undefined in Solargraph 'do + expect(method_pin.return_type.tag).to eq('undefined') + end end - end - # https://github.com/castwide/solargraph/issues/1042 - context 'with Hash superclass with untyped value and alias' do - let(:rbs) do - <<~RBS + # https://github.com/castwide/solargraph/issues/1042 + context 'with Hash superclass with untyped value and alias' do + let(:rbs) do + <<~RBS class Sub < Hash[Symbol, untyped] alias meth_alias [] end RBS - end + end - let(:api_map) { Solargraph::ApiMap.new } + let(:sup_method_stack) { api_map.get_method_stack('Hash{Symbol => undefined}', '[]', scope: :instance) } - let(:sup_method_stack) { api_map.get_method_stack('Hash{Symbol => undefined}', '[]', scope: :instance) } + let(:sub_alias_stack) { api_map.get_method_stack('Sub', 'meth_alias', scope: :instance) } - let(:sub_alias_stack) { api_map.get_method_stack('Sub', 'meth_alias', scope: :instance) } + it 'does not crash looking at superclass method' do + expect { sup_method_stack }.not_to raise_error + end - before do - api_map.index conversions.pins - end + it 'does not crash looking at alias' do + expect { sub_alias_stack }.not_to raise_error + end - it 'does not crash looking at superclass method' do - expect { sup_method_stack }.not_to raise_error - end + it 'finds superclass method pin return type' do + expect(sup_method_stack.map(&:return_type).map(&:rooted_tags).uniq).to eq(['undefined']) + end - it 'does not crash looking at alias' do - expect { sub_alias_stack }.not_to raise_error + it 'finds superclass method pin parameter type' do + expect(sup_method_stack.flat_map(&:signatures).flat_map(&:parameters).map(&:return_type).map(&:rooted_tags) + .uniq).to eq(['Symbol']) + end end - it 'finds superclass method pin return type' do - expect(sup_method_stack.map(&:return_type).map(&:rooted_tags).uniq).to eq(['undefined']) + context 'with overlapping module hierarchies and inheritance' do + let(:rbs) do + <<~RBS + module B + class C + def foo: () -> String + end + end + module A + module B + class C < ::B::C + end + end + end + RBS + end + + subject(:method_pin) { api_map.get_method_stack('A::B::C', 'foo').first } + + it { should be_a(Solargraph::Pin::Method) } end + end + + if Gem::Version.new(RBS::VERSION) >= Gem::Version.new('3.9.1') + context 'with method pin for Open3.capture2e' do + it 'accepts chdir kwarg' do + api_map = Solargraph::ApiMap.load_with_cache('.', $stdout) + + method_pin = api_map.pins.find do |pin| + pin.is_a?(Solargraph::Pin::Method) && pin.path == 'Open3.capture2e' + end - it 'finds superclass method pin parameter type' do - expect(sup_method_stack.flat_map(&:signatures).flat_map(&:parameters).map(&:return_type).map(&:rooted_tags) - .uniq).to eq(['Symbol']) + chdir_param = method_pin&.signatures&.flat_map(&:parameters)&.find do |param| # rubocop:disable Style/SafeNavigationChainLength + param.name == 'chdir' + end + expect(chdir_param).not_to be_nil, -> { "Found pin #{method_pin.to_rbs} from #{method_pin.type_location}" } + end end end end From e8672b02880d4d0aa83379208d229a7ada5d5438 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 08:45:49 -0400 Subject: [PATCH 171/460] Fix spec expectation --- spec/rbs_map/core_map_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/rbs_map/core_map_spec.rb b/spec/rbs_map/core_map_spec.rb index 352d29937..88590925b 100644 --- a/spec/rbs_map/core_map_spec.rb +++ b/spec/rbs_map/core_map_spec.rb @@ -6,7 +6,7 @@ pin = store.get_path_pins("Errno::#{const}").first expect(pin).to be_a(Solargraph::Pin::Namespace) superclass = store.get_superclass(pin.path) - expect(superclass).to eq('SystemCallError') + expect(superclass).to eq('::SystemCallError') end end From 83dfd6750b63227aa683e8e6cbd03346c26b3934 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 09:37:46 -0400 Subject: [PATCH 172/460] Improve more annotations --- lib/solargraph/api_map.rb | 4 +++- lib/solargraph/environ.rb | 2 +- lib/solargraph/library.rb | 2 +- lib/solargraph/pin/callable.rb | 4 +++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index f58633a0c..537eb3985 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -212,6 +212,7 @@ class << self # # @param directory [String] # @param out [IO] The output stream for messages + # # @return [ApiMap] def self.load_with_cache directory, out api_map = load(directory) @@ -533,7 +534,8 @@ def get_complex_type_methods complex_type, context = '', internal = false # @param name [String] Method name to look up # @param scope [Symbol] :instance or :class # @param visibility [Array] :public, :protected, and/or :private - # @param preserve_generics [Boolean] + # @param preserve_generics [Boolean] True to preserve any + # unresolved generic parameters, false to erase them # @return [Array] def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private, :protected, :public], preserve_generics: false rooted_type = ComplexType.parse(rooted_tag) diff --git a/lib/solargraph/environ.rb b/lib/solargraph/environ.rb index 3d24127c6..639442a04 100644 --- a/lib/solargraph/environ.rb +++ b/lib/solargraph/environ.rb @@ -22,7 +22,7 @@ class Environ # @param requires [Array] # @param domains [Array] # @param pins [Array] - # @param yard_plugins[Array] + # @param yard_plugins [Array] def initialize requires: [], domains: [], pins: [], yard_plugins: [] @requires = requires @domains = domains diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 9d5162431..b64afa4a0 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -580,7 +580,7 @@ def cache_errors def cache_next_gemspec return if @cache_progress - spec = cacheable_specs.first +g spec = cacheable_specs.first return end_cache_progress unless spec pending = api_map.uncached_gemspecs.length - cache_errors.length - 1 diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 504dd4862..8ab1bf733 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -27,7 +27,8 @@ def method_namespace end # @param other [self] - # @return [Pin::Block, nil] + # + # @return [Pin::Signature, nil] def combine_blocks(other) if block.nil? other.block @@ -61,6 +62,7 @@ def generics end # @param other [self] + # # @return [Array] def choose_parameters(other) raise "Trying to combine two pins with different arities - \nself =#{inspect}, \nother=#{other.inspect}, \n\n self.arity=#{self.arity}, \nother.arity=#{other.arity}" if other.arity != arity From 866bd388f48a270b2dec093e595382f592245dd5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 10:45:09 -0400 Subject: [PATCH 173/460] More annotation cleanups / fixes --- lib/solargraph/api_map.rb | 1 + lib/solargraph/api_map/index.rb | 1 + lib/solargraph/api_map/store.rb | 8 +++++--- lib/solargraph/pin/base.rb | 2 +- lib/solargraph/rbs_map.rb | 4 ++-- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 537eb3985..f8702ea2b 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -177,6 +177,7 @@ def clip_at filename, position # Create an ApiMap with a workspace in the specified directory. # # @param directory [String] + # # @return [ApiMap] def self.load directory api_map = new diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index a5870ff50..f37480c8d 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -95,6 +95,7 @@ def deep_clone end # @param new_pins [Enumerable] + # # @return [self] def catalog new_pins # @type [Hash{Class> => Set>}] diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index f3e2ed278..61cd820d9 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -50,7 +50,7 @@ def inspect # @param fqns [String] # @param visibility [Array] - # @return [Enumerable] + # @return [Enumerable] def get_constants fqns, visibility = [:public] namespace_children(fqns).select { |pin| !pin.name.empty? && (pin.is_a?(Pin::Namespace) || pin.is_a?(Pin::Constant)) && visibility.include?(pin.visibility) @@ -126,7 +126,8 @@ def get_instance_variables(fqns, scope = :instance) end # @param fqns [String] - # @return [Enumerable] + # + # @return [Enumerable] def get_class_variables(fqns) namespace_children(fqns).select { |pin| pin.is_a?(Pin::ClassVariable)} end @@ -254,7 +255,8 @@ def index end # @param pinsets [Array>] - # @return [Boolean] + # + # @return [void] def catalog pinsets @pinsets = pinsets # @type [Array] diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 7f1509d9b..c8c5e6d7f 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -338,7 +338,7 @@ def choose_pin_attr_with_same_name(other, attr) # @param other [self] # @param attr [::Symbol] # - # @sg-ignore + # @sg-ignore Missing @return tag for Solargraph::Pin::Base#choose_pin_attr # @return [undefined] def choose_pin_attr(other, attr) # @type [Pin::Base, nil] diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index c6c10bac6..803e3677a 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -23,7 +23,7 @@ class RbsMap attr_reader :rbs_collection_config_path # @param library [String] - # @param version [String, nil + # @param version [String, nil] # @param rbs_collection_config_path [String, Pathname, nil] # @param rbs_collection_paths [Array] def initialize library, version = nil, rbs_collection_config_path: nil, rbs_collection_paths: [] @@ -72,7 +72,7 @@ def cache_key end end - # @param gemspec [Gem::Specification] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @param rbs_collection_path [String, Pathname, nil] # @param rbs_collection_config_path [String, Pathname, nil] # @return [RbsMap] From ae56366ad03f0cf2a2d8b3ae000bee79110d5009 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 10:46:52 -0400 Subject: [PATCH 174/460] Fix typo --- lib/solargraph/library.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index b64afa4a0..9d5162431 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -580,7 +580,7 @@ def cache_errors def cache_next_gemspec return if @cache_progress -g spec = cacheable_specs.first + spec = cacheable_specs.first return end_cache_progress unless spec pending = api_map.uncached_gemspecs.length - cache_errors.length - 1 From 9399bfd4699a3d0f6a3b18a8b54cb650b172b9a0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 11:07:00 -0400 Subject: [PATCH 175/460] Fix merge --- .../parser_gem/node_processors/sclass_node.rb | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb index 1b573ed93..e5b049b06 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb @@ -7,18 +7,9 @@ module NodeProcessors class SclassNode < Parser::NodeProcessor::Base def process sclass = node.children[0] - # @todo Changing Parser::AST::Node to AST::Node below will - # cause type errors at strong level because the combined - # pin for AST::Node#children has return type - # "Array, Array". YARD annotations in AST - # provided the Array, RBS for Array. We - # should probably have a rule to combine "A, A"" - # types to "A" if the "A" comes from YARD, with the - # rationale that folks tend to be less formal with types in - # YARD. - if sclass.is_a?(::Parser::AST::Node) && sclass.type == :self + if sclass.is_a?(AST::Node) && sclass.type == :self closure = region.closure - elsif sclass.is_a?(::Parser::AST::Node) && sclass.type == :casgn + elsif sclass.is_a?(AST::Node) && sclass.type == :casgn names = [region.closure.namespace, region.closure.name] if sclass.children[0].nil? && names.last != sclass.children[1].to_s names << sclass.children[1].to_s @@ -27,7 +18,7 @@ def process end name = names.reject(&:empty?).join('::') closure = Solargraph::Pin::Namespace.new(name: name, location: region.closure.location, source: :parser) - elsif sclass.is_a?(::Parser::AST::Node) && sclass.type == :const + elsif sclass.is_a?(AST::Node) && sclass.type == :const names = [region.closure.namespace, region.closure.name] also = NodeMethods.unpack_name(sclass) if also != region.closure.name From 93e90ba0863d903027b56d90adc34f53cf29f312 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 12:04:50 -0400 Subject: [PATCH 176/460] Fix merge --- .../parser_gem/node_processors/sclass_node.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb index e5b049b06..1b573ed93 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb @@ -7,9 +7,18 @@ module NodeProcessors class SclassNode < Parser::NodeProcessor::Base def process sclass = node.children[0] - if sclass.is_a?(AST::Node) && sclass.type == :self + # @todo Changing Parser::AST::Node to AST::Node below will + # cause type errors at strong level because the combined + # pin for AST::Node#children has return type + # "Array, Array". YARD annotations in AST + # provided the Array, RBS for Array. We + # should probably have a rule to combine "A, A"" + # types to "A" if the "A" comes from YARD, with the + # rationale that folks tend to be less formal with types in + # YARD. + if sclass.is_a?(::Parser::AST::Node) && sclass.type == :self closure = region.closure - elsif sclass.is_a?(AST::Node) && sclass.type == :casgn + elsif sclass.is_a?(::Parser::AST::Node) && sclass.type == :casgn names = [region.closure.namespace, region.closure.name] if sclass.children[0].nil? && names.last != sclass.children[1].to_s names << sclass.children[1].to_s @@ -18,7 +27,7 @@ def process end name = names.reject(&:empty?).join('::') closure = Solargraph::Pin::Namespace.new(name: name, location: region.closure.location, source: :parser) - elsif sclass.is_a?(AST::Node) && sclass.type == :const + elsif sclass.is_a?(::Parser::AST::Node) && sclass.type == :const names = [region.closure.namespace, region.closure.name] also = NodeMethods.unpack_name(sclass) if also != region.closure.name From 5c90bbc91ca2894c0408c2918ed9cfd8183bdce5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 12:12:04 -0400 Subject: [PATCH 177/460] Fix long line --- lib/solargraph/doc_map.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 5fe5e03f9..64ab926bc 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -177,10 +177,10 @@ def load_serialized_gem_pins @uncached_yard_gemspecs = [] @uncached_rbs_collection_gemspecs = [] with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v } - # @sg-ignore Wrong argument type for Hash.[]: arg_0 expected _ToHash), undefined>, received Array)> + # @sg-ignore Need support for RBS duck interfaces like _ToHash # @type [Array] paths = Hash[without_gemspecs].keys - # @sg-ignore Wrong argument type for Hash.[]: arg_0 expected _ToHash), undefined>, received Array)> + # @sg-ignore Need support for RBS duck interfaces like _ToHash # @type [Array] gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a From bafa7f86db626746605c47d42be443a7e80312ae Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 12:24:54 -0400 Subject: [PATCH 178/460] Adjust some Bundler types --- rbs/fills/bundler/0/bundler.rbs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rbs/fills/bundler/0/bundler.rbs b/rbs/fills/bundler/0/bundler.rbs index b87780c80..c4deb5cc3 100644 --- a/rbs/fills/bundler/0/bundler.rbs +++ b/rbs/fills/bundler/0/bundler.rbs @@ -607,7 +607,7 @@ class Bundler::DepProxy def initialize: (untyped dep, untyped platform) -> void - def name: () -> untyped + def name: () -> String def requirement: () -> untyped @@ -878,7 +878,7 @@ class Bundler::EndpointSpecification < Gem::Specification def source=: (untyped source) -> untyped - def version: () -> untyped + def version: () -> String end Bundler::EndpointSpecification::Elem: untyped From 1fa4a20d9987df42a027f5796024e96027e7b971 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 12:34:59 -0400 Subject: [PATCH 179/460] Drop some workarounds --- lib/solargraph/parser/parser_gem/class_methods.rb | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index ddc742bd8..2daf22fc7 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -2,15 +2,6 @@ require 'prism' -# Awaiting ability to use a version containing https://github.com/whitequark/parser/pull/1076 -# -# @!parse -# class ::Parser::Base < ::Parser::Builder -# # @return [Integer] -# def version; end -# end -# class ::Parser::CurrentRuby < ::Parser::Base; end - module Solargraph module Parser module ParserGem @@ -84,6 +75,7 @@ def references source, name # @param top [AST::Node] # @return [Array] def inner_node_references name, top + # @type [Array] result = [] if top.is_a?(AST::Node) && top.to_s.include?(":#{name}") result.push top if top.children.any? { |c| c.to_s == name } @@ -137,9 +129,7 @@ def node_range node def string_ranges node return [] unless is_ast_node?(node) result = [] - if node.type == :str - result.push Range.from_node(node) - end + result.push Range.from_node(node) if node.type == :str node.children.each do |child| result.concat string_ranges(child) end From 5e96bac4bce9e74cb5dcd788b354fa28f3643542 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 13:30:03 -0400 Subject: [PATCH 180/460] Add a couple of types --- rbs/fills/bundler/0/bundler.rbs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rbs/fills/bundler/0/bundler.rbs b/rbs/fills/bundler/0/bundler.rbs index c4deb5cc3..efbc82b83 100644 --- a/rbs/fills/bundler/0/bundler.rbs +++ b/rbs/fills/bundler/0/bundler.rbs @@ -3076,7 +3076,7 @@ class Bundler::RemoteSpecification def initialize: (untyped name, untyped version, untyped platform, untyped spec_fetcher) -> void - def name: () -> untyped + def name: () -> String def platform: () -> untyped @@ -3106,7 +3106,7 @@ class Bundler::RemoteSpecification def to_s: () -> untyped - def version: () -> untyped + def version: () -> String end class Bundler::Resolver From e06b4ca3bf1f5bc8fe04987ca00d30b4d232dfdb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 09:52:33 -0400 Subject: [PATCH 181/460] Cache stdlib/core from command line This adds the ability to cache both the core and standard libraries from the command line: ``` $ bundle exec solargraph help gem Usage: solargraph gems [GEM[=VERSION]...] [STDLIB...] [core] Description: This command will cache the generated type documentation for the specified libraries. While Solargraph will generate this on the fly when needed, it takes time. This command will generate it in advance, which can be useful for CI scenarios. With no arguments, it will cache all libraries in the current workspace. If a gem or standard library name is specified, it will cache that library's type documentation. An equals sign after a gem will allow a specific gem version to be cached. The 'core' argument can be used to cache the type documentation for the core Ruby libraries. Cached documentation is stored in /Users/broz/.cache/solargraph, which can be stored between CI runs. $ ``` --- .rubocop_todo.yml | 5 - lib/solargraph/doc_map.rb | 33 ++- lib/solargraph/pin_cache.rb | 44 ++- lib/solargraph/rbs_map.rb | 57 ++-- lib/solargraph/rbs_map/core_map.rb | 14 +- lib/solargraph/rbs_map/stdlib_map.rb | 25 +- lib/solargraph/shell.rb | 156 +++++++++-- lib/solargraph/workspace.rb | 10 + spec/doc_map_spec.rb | 8 +- spec/language_server/host/diagnoser_spec.rb | 3 +- .../host/message_worker_spec.rb | 3 +- spec/shell_spec.rb | 254 +++++++++++++++++- spec/spec_helper.rb | 26 ++ 13 files changed, 561 insertions(+), 77 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b8d123824..5739f5fcd 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1106,11 +1106,6 @@ RSpec/LetBeforeExamples: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: . -# SupportedStyles: have_received, receive -RSpec/MessageSpies: - EnforcedStyle: receive - RSpec/MissingExampleGroupArgument: Exclude: - 'spec/diagnostics/rubocop_helpers_spec.rb' diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 5fe5e03f9..d7d08edf5 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -61,19 +61,27 @@ def initialize(requires, preferences, workspace = nil) end # @param out [IO] + # @param rebuild [Boolean] whether to rebuild the pins even if they are cached + # # @return [void] - def cache_all!(out) - # if we log at debug level: - if logger.info? - gem_desc = uncached_gemspecs.map { |gemspec| "#{gemspec.name}:#{gemspec.version}" }.join(', ') - logger.info "Caching pins for gems: #{gem_desc}" unless uncached_gemspecs.empty? - end - logger.debug { "Caching for YARD: #{uncached_yard_gemspecs.map(&:name)}" } - logger.debug { "Caching for RBS collection: #{uncached_rbs_collection_gemspecs.map(&:name)}" } + def cache_all!(out, rebuild: false) load_serialized_gem_pins - uncached_gemspecs.each do |gemspec| + PinCache.cache_core(out: out) unless PinCache.core? + gem_specs = uncached_gemspecs + # try any possible standard libraries, but be quiet about it + stdlib_specs = PinCache.possible_stdlibs.map { |stdlib| workspace.find_gem(stdlib, out: nil) }.compact + specs = (gem_specs + stdlib_specs) + specs.each do |gemspec| cache(gemspec, out: out) end + out&.puts "Documentation cached for all #{specs.length} gems." + + # do this after so that we prefer stdlib requires from gems, + # which are likely to be newer and have more pins + PinCache.cache_all_stdlibs(out: out) + + out&.puts "Documentation cached for core, standard library and gems." + load_serialized_gem_pins @uncached_rbs_collection_gemspecs = [] @uncached_yard_gemspecs = [] @@ -357,7 +365,7 @@ def change_gemspec_version gemspec, version # @return [Array] def fetch_dependencies gemspec # @param spec [Gem::Dependency] - only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps| + gem_dep_gemspecs = only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps| Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}" dep = Gem.loaded_specs[spec.name] # @todo is next line necessary? @@ -366,6 +374,11 @@ def fetch_dependencies gemspec rescue Gem::MissingSpecError Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found in RubyGems." end.to_a + # RBS tracks implicit dependencies, like how the YAML standard + # library implies pulling in the psych library. + stdlib_deps = RbsMap::StdlibMap.stdlib_dependencies(gemspec.name, gemspec.version) || [] + stdlib_dep_gemspecs = stdlib_deps.map { |dep| workspace.find_gem(dep['name'], dep['version']) }.compact + (gem_dep_gemspecs.compact + stdlib_dep_gemspecs).uniq(&:name) end # @param gemspec [Gem::Specification] diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 2a0ec4639..a997356b6 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -164,14 +164,16 @@ def has_rbs_collection?(gemspec, hash) exist?(rbs_collection_path(gemspec, hash)) end + # @param out [IO, nil] # @return [void] - def uncache_core - uncache(core_path) + def uncache_core out: nil + uncache(core_path, out: out) end + # @param out [IO, nil] # @return [void] - def uncache_stdlib - uncache(stdlib_path) + def uncache_stdlib out: nil + uncache(stdlib_path, out: out) end # @param gemspec [Gem::Specification] @@ -189,6 +191,40 @@ def clear FileUtils.rm_rf base_dir, secure: true end + def core? + File.file?(core_path) + end + + # @param out [IO, nil] + # @return [Enumerable] + def cache_core out: nil + RbsMap::CoreMap.new.cache_core(out: out) + end + + # @param out [IO, nil] output stream for logging + # + # @return [void] + def cache_all_stdlibs out: $stderr + possible_stdlibs.each do |stdlib| + RbsMap::StdlibMap.new(stdlib, out: out) + end + end + + # @return [Array] a list of possible standard library names + def possible_stdlibs + # all dirs and .rb files in Gem::RUBYGEMS_DIR + Dir.glob(File.join(Gem::RUBYGEMS_DIR, '*')).map do |file_or_dir| + basename = File.basename(file_or_dir) + # remove .rb + basename = basename[0..-4] if basename.end_with?('.rb') + basename + end.sort.uniq + rescue StandardError => e + logger.info { "Failed to get possible stdlibs: #{e.message}" } + logger.debug { e.backtrace.join("\n") } + [] + end + private # @param file [String] diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index c6c10bac6..68514f32a 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -23,10 +23,11 @@ class RbsMap attr_reader :rbs_collection_config_path # @param library [String] - # @param version [String, nil + # @param version [String, nil] # @param rbs_collection_config_path [String, Pathname, nil] # @param rbs_collection_paths [Array] - def initialize library, version = nil, rbs_collection_config_path: nil, rbs_collection_paths: [] + # @param out [IO, nil] where to log messages + def initialize library, version = nil, rbs_collection_config_path: nil, rbs_collection_paths: [], out: $stderr if rbs_collection_config_path.nil? && !rbs_collection_paths.empty? raise 'Please provide rbs_collection_config_path if you provide rbs_collection_paths' end @@ -37,6 +38,11 @@ def initialize library, version = nil, rbs_collection_config_path: nil, rbs_coll add_library loader, library, version end + CACHE_KEY_GEM_EXPORT = 'gem-export' + CACHE_KEY_UNRESOLVED = 'unresolved' + CACHE_KEY_STDLIB = 'stdlib' + CACHE_KEY_LOCAL = 'local' + # @return [RBS::EnvironmentLoader] def loader @loader ||= RBS::EnvironmentLoader.new(core_root: nil, repository: repository) @@ -47,9 +53,13 @@ def loader # updated upstream for the same library and version. May change # if the config for where information comes form changes. def cache_key + return CACHE_KEY_UNRESOLVED unless resolved? + @hextdigest ||= begin # @type [String, nil] data = nil + # @type gem_config [nil, Hash{String => Hash{String => String}}] + gem_config = nil if rbs_collection_config_path lockfile_path = RBS::Collection::Config.to_lockfile_path(Pathname.new(rbs_collection_config_path)) if lockfile_path.exist? @@ -58,21 +68,26 @@ def cache_key data = gem_config&.to_s end end - if data.nil? || data.empty? - if resolved? - # definitely came from the gem itself and not elsewhere - - # only one version per gem - 'gem-export' + if gem_config.nil? + CACHE_KEY_STDLIB + else + # @type [String] + source = gem_config.dig('source', 'type') + case source + when 'rubygems' + CACHE_KEY_GEM_EXPORT + when 'local' + CACHE_KEY_LOCAL + when 'stdlib' + CACHE_KEY_STDLIB else - 'unresolved' + Digest::SHA1.hexdigest(data) end - else - Digest::SHA1.hexdigest(data) end end end - # @param gemspec [Gem::Specification] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @param rbs_collection_path [String, Pathname, nil] # @param rbs_collection_config_path [String, Pathname, nil] # @return [RbsMap] @@ -88,9 +103,15 @@ def self.from_gemspec gemspec, rbs_collection_path, rbs_collection_config_path rbs_collection_config_path: rbs_collection_config_path) end + # @param out [IO, nil] where to log messages # @return [Array] - def pins - @pins ||= resolved? ? conversions.pins : [] + def pins out: $stderr + @pins ||= if resolved? + loader.libs.each { |lib| log_caching(lib, out: out) } + conversions.pins + else + [] + end end # @generic T @@ -140,11 +161,17 @@ def conversions @conversions ||= Conversions.new(loader: loader) end + # @param lib [RBS::EnvironmentLoader::Library] + # @param out [IO, nil] where to log messages + # @return [void] + def log_caching lib, out:; end + # @param loader [RBS::EnvironmentLoader] # @param library [String] - # @param version [String, nil] + # @param version [String, nil] the version of the library to load, or nil for any + # @param out [IO, nil] where to log messages # @return [Boolean] true if adding the library succeeded - def add_library loader, library, version + def add_library loader, library, version, out: $stderr @resolved = if loader.has_library?(library: library, version: version) loader.add library: library, version: version logger.debug { "#{short_name} successfully loaded library #{library}:#{version}" } diff --git a/lib/solargraph/rbs_map/core_map.rb b/lib/solargraph/rbs_map/core_map.rb index 8c3d7dbdd..a85cc34d0 100644 --- a/lib/solargraph/rbs_map/core_map.rb +++ b/lib/solargraph/rbs_map/core_map.rb @@ -5,25 +5,27 @@ class RbsMap # Ruby core pins # class CoreMap + include Logging def resolved? true end - FILLS_DIRECTORY = File.join(File.dirname(__FILE__), '..', '..', '..', 'rbs', 'fills') + FILLS_DIRECTORY = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'rbs', 'fills')) def initialize; end + # @param out [IO, nil] output stream for logging # @return [Enumerable] - def pins + def pins out: $stderr return @pins if @pins - @pins = [] cache = PinCache.deserialize_core if cache @pins.replace cache else loader.add(path: Pathname(FILLS_DIRECTORY)) + out&.puts 'Caching RBS pins for Ruby core' @pins = conversions.pins @pins.concat RbsMap::CoreFills::ALL processed = ApiMap::Store.new(pins).pins.reject { |p| p.is_a?(Solargraph::Pin::Reference::Override) } @@ -34,6 +36,12 @@ def pins @pins end + # @param out [IO, nil] output stream for logging + # @return [Enumerable] + def cache_core out: $stderr + pins out: out + end + def loader @loader ||= RBS::EnvironmentLoader.new(repository: RBS::Repository.new(no_stdlib: false)) end diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index b6804157f..e7891bfe3 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -12,8 +12,13 @@ class StdlibMap < RbsMap # @type [Hash{String => RbsMap}] @stdlib_maps_hash = {} + def log_caching lib, out: $stderr + out&.puts("Caching RBS pins for standard library #{lib.name}") + end + # @param library [String] - def initialize library + # @param out [IO, nil] where to log messages + def initialize library, out: $stderr cached_pins = PinCache.deserialize_stdlib_require library if cached_pins @pins = cached_pins @@ -24,7 +29,7 @@ def initialize library super unless resolved? @pins = [] - logger.info { "Could not resolve #{library.inspect}" } + logger.debug { "StdlibMap could not resolve #{library.inspect}" } return end generated_pins = pins @@ -33,6 +38,22 @@ def initialize library end end + # @return [RBS::Collection::Sources::Stdlib] + def self.source + @source ||= RBS::Collection::Sources::Stdlib.instance + end + + # @param name [String] + # @param version [String, nil] + # @return [Array String}>, nil] + def self.stdlib_dependencies name, version = nil + if source.has?(name, version) + source.dependencies_of(name, version) + else + [] + end + end + # @param library [String] # @return [StdlibMap] def self.load library diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index a005f600b..4f114c5fe 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -15,7 +15,7 @@ def self.exit_on_failure? map %w[--version -v] => :version - desc "--version, -v", "Print the version" + desc '--version, -v', 'Print the version' # @return [void] def version puts Solargraph::VERSION @@ -103,12 +103,60 @@ def clear # @param gem [String] # @param version [String, nil] def cache gem, version = nil + gems(gem + (version ? "=#{version}" : '')) + # ' + end + + desc 'gems [GEM[=VERSION]...] [STDLIB...] [core]', 'Cache documentation for + installed libraries' + long_desc %( This command will cache the + generated type documentation for the specified libraries. While + Solargraph will generate this on the fly when needed, it takes + time. This command will generate it in advance, which can be + useful for CI scenarios. + + With no arguments, it will cache all libraries in the current + workspace. If a gem or standard library name is specified, it + will cache that library's type documentation. + + An equals sign after a gem will allow a specific gem version + to be cached. + + The 'core' argument can be used to cache the type + documentation for the core Ruby libraries. + + Cached documentation is stored in #{PinCache.base_dir}, which + can be stored between CI runs. + ) + # @param names [Array] + # @return [void] + def gems *names + $stderr.puts("Caching these gems: #{names}") + # print time with ms + workspace = Solargraph::Workspace.new('.') + api_map = Solargraph::ApiMap.load(Dir.pwd) - spec = Gem::Specification.find_by_name(gem, version) - api_map.cache_gem(spec, rebuild: options[:rebuild], out: $stdout) + if names.empty? + api_map.cache_all!($stdout) + else + names.each do |name| + if name == 'core' + PinCache.cache_core(out: $stdout) + next + end + + gemspec = workspace.find_gem(*name.split('=')) + if gemspec.nil? + warn "Gem '#{name}' not found" + else + api_map.cache_gem(gemspec, rebuild: options[:rebuild], out: $stdout) + end + end + $stderr.puts "Documentation cached for #{names.count} gems." + end end - desc 'uncache GEM [...GEM]', "Delete specific cached gem documentation" + desc 'uncache GEM [...GEM]', 'Delete specific cached gem documentation' long_desc %( Specify one or more gem names to clear. 'core' or 'stdlib' may also be specified to clear cached system documentation. @@ -120,12 +168,12 @@ def uncache *gems raise ArgumentError, 'No gems specified.' if gems.empty? gems.each do |gem| if gem == 'core' - PinCache.uncache_core + PinCache.uncache_core(out: $stdout) next end if gem == 'stdlib' - PinCache.uncache_stdlib + PinCache.uncache_stdlib(out: $stdout) next end @@ -134,26 +182,6 @@ def uncache *gems end end - desc 'gems [GEM[=VERSION]]', 'Cache documentation for installed gems' - option :rebuild, type: :boolean, desc: 'Rebuild existing documentation', default: false - # @param names [Array] - # @return [void] - def gems *names - api_map = ApiMap.load('.') - if names.empty? - Gem::Specification.to_a.each { |spec| do_cache spec, api_map } - STDERR.puts "Documentation cached for all #{Gem::Specification.count} gems." - else - names.each do |name| - spec = Gem::Specification.find_by_name(*name.split('=')) - do_cache spec, api_map - rescue Gem::MissingSpecError - warn "Gem '#{name}' not found" - end - STDERR.puts "Documentation cached for #{names.count} gems." - end - end - desc 'reporters', 'Get a list of diagnostics reporters' # @return [void] def reporters @@ -191,7 +219,6 @@ def typecheck *files filecount += 1 probcount += problems.length end - # " } puts "Typecheck finished in #{time.real} seconds." puts "#{probcount} problem#{probcount != 1 ? 's' : ''} found#{files.length != 1 ? " in #{filecount} of #{files.length} files" : ''}." @@ -241,6 +268,61 @@ def list puts "#{workspace.filenames.length} files total." end + desc 'pin [PATH]', 'Describe a pin', hide: true + option :rbs, type: :boolean, desc: 'Output the pin as RBS', default: false + option :typify, type: :boolean, desc: 'Output the calculated return type of the pin from annotations', default: false + option :references, type: :boolean, desc: 'Show references', default: false + option :probe, type: :boolean, desc: 'Output the calculated return type of the pin from annotations and inference', default: false + 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) + pins = api_map.get_path_pins path + references = {} + pin = pins.first + case pin + when nil + $stderr.puts "Pin not found for path '#{path}'" + exit 1 + when Pin::Method + if 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 + pins = api_map.get_method_stack(ns, meth, scope: scope) + end + when Pin::Namespace + 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 + end + + pins.each do |pin| + if options[:typify] || options[:probe] + type = ComplexType::UNDEFINED + type = pin.typify(api_map) if options[:typify] + type = pin.probe(api_map) if options[:probe] && type.undefined? + print_type(type) + next + end + + print_pin(pin) + end + references.each do |key, refpin| + puts "\n# #{key.to_s.capitalize}:\n\n" + print_pin(refpin) + end + end + private # @param pin [Solargraph::Pin::Base] @@ -267,5 +349,25 @@ 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) + 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) + if options[:rbs] + puts pin.to_rbs + else + puts pin.inspect + end + end end end diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index ffd653d96..046c09b83 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -143,6 +143,16 @@ def rbs_collection_config_path end end + # @param name [String] + # @param version [String, nil] + # + # @return [Gem::Specification, nil] + def find_gem name, version = nil + Gem::Specification.find_by_name(name, version) + rescue Gem::MissingSpecError + nil + end + # Synchronize the workspace from the provided updater. # # @param updater [Source::Updater] diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index b03e573f0..2729ab095 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -8,8 +8,10 @@ Solargraph::PinCache.serialize_yard_gem(gemspec, yard_pins) end + let(:workspace) { Solargraph::Workspace.new(Dir.pwd) } + it 'generates pins from gems' do - doc_map = Solargraph::DocMap.new(['ast'], []) + doc_map = Solargraph::DocMap.new(['ast'], [], workspace) doc_map.cache_all!($stderr) node_pin = doc_map.pins.find { |pin| pin.path == 'AST::Node' } expect(node_pin).to be_a(Solargraph::Pin::Namespace) @@ -32,7 +34,6 @@ end it 'imports all gems when bundler/require used' do - workspace = Solargraph::Workspace.new(Dir.pwd) plain_doc_map = Solargraph::DocMap.new([], [], workspace) doc_map_with_bundler_require = Solargraph::DocMap.new(['bundler/require'], [], workspace) @@ -42,8 +43,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) Solargraph::DocMap.new(['set'], []) + expect(Solargraph.logger).not_to have_received(:warn).with(/path set/) end it 'ignores nil requires' do diff --git a/spec/language_server/host/diagnoser_spec.rb b/spec/language_server/host/diagnoser_spec.rb index d59a843f1..69ee0b866 100644 --- a/spec/language_server/host/diagnoser_spec.rb +++ b/spec/language_server/host/diagnoser_spec.rb @@ -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 diff --git a/spec/language_server/host/message_worker_spec.rb b/spec/language_server/host/message_worker_spec.rb index b9ce2a41f..5e5bef481 100644 --- a/spec/language_server/host/message_worker_spec.rb +++ b/spec/language_server/host/message_worker_spec.rb @@ -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 diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index 91f84b4c7..46d985c36 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: true + require 'tmpdir' require 'open3' describe Solargraph::Shell do + let(:shell) { described_class.new } let(:temp_dir) { Dir.mktmpdir } before do @@ -25,20 +28,259 @@ def bundle_exec(*cmd) FileUtils.rm_rf(temp_dir) end - describe "--version" do - it "returns a version when run" do - output = bundle_exec("solargraph", "--version") + describe '--version' do + let(:output) { bundle_exec('solargraph', '--version') } + it 'returns output' do expect(output).not_to be_empty + end + + it 'returns a version when run' do expect(output).to eq("#{Solargraph::VERSION}\n") end end - describe "uncache" do - it "uncaches without erroring out" do - output = bundle_exec("solargraph", "uncache", "solargraph") + describe 'uncache' do + it 'uncaches without erroring out' do + output = capture_stdout do + shell.uncache('backport') + end expect(output).to include('Clearing pin cache in') end + + it 'uncaches stdlib without erroring out' do + expect { shell.uncache('stdlib') }.not_to raise_error + end + + it 'uncaches core without erroring out' do + expect { shell.uncache('core') }.not_to raise_error + end + end + + describe 'scan' do + context 'with mocked dependencies' do + let(:api_map) { instance_double(Solargraph::ApiMap) } + + before do + allow(Solargraph::ApiMap).to receive(:load_with_cache).and_return(api_map) + end + + it 'scans without erroring out' do + allow(api_map).to receive(:pins).and_return([]) + output = capture_stdout do + shell.options = { directory: 'spec/fixtures/workspace' } + shell.scan + end + + expect(output).to include('Scanned ').and include(' seconds.') + end + end + end + + describe 'typecheck' do + context 'with mocked dependencies' do + let(:type_checker) { instance_double(Solargraph::TypeChecker) } + let(:api_map) { instance_double(Solargraph::ApiMap) } + + before do + allow(Solargraph::ApiMap).to receive(:load_with_cache).and_return(api_map) + allow(Solargraph::TypeChecker).to receive(:new).and_return(type_checker) + allow(type_checker).to receive(:problems).and_return([]) + end + + it 'typechecks without erroring out' do + output = capture_stdout do + shell.options = { level: 'normal', directory: '.' } + shell.typecheck('Gemfile') + end + + expect(output).to include('Typecheck finished in') + end + end + end + + describe 'gems' do + context 'without mocked ApiMap' do + it 'complains when gem does not exist' do + output = capture_both do + shell.gems('nonexistentgem') + end + + expect(output).to include("Gem 'nonexistentgem' not found") + end + + it 'caches core without erroring out' do + capture_both do + shell.uncache('core') + end + + expect { shell.cache('core') }.not_to raise_error + end + + it 'gives sensible error for gem that does not exist' do + output = capture_both do + shell.gems('solargraph123') + end + + expect(output).to include("Gem 'solargraph123' not found") + end + end + + context 'with mocked Workspace' do + let(:api_map) { instance_double(Solargraph::ApiMap) } + let(:workspace) { instance_double(Solargraph::Workspace) } + let(:gemspec) { instance_double(Gem::Specification, name: 'backport') } + + before do + allow(Solargraph::Workspace).to receive(:new).and_return(workspace) + allow(Solargraph::ApiMap).to receive(:load).with(Dir.pwd).and_return(api_map) + allow(api_map).to receive(:cache_gem) + end + + it 'caches all without erroring out' do + allow(api_map).to receive(:cache_all!) + + _output = capture_both { shell.gems } + + expect(api_map).to have_received(:cache_all!) + end + + it 'caches single gem without erroring out' do + allow(workspace).to receive(:find_gem).with('backport').and_return(gemspec) + + capture_both do + shell.options = { rebuild: false } + shell.gems('backport') + end + + expect(api_map).to have_received(:cache_gem).with(gemspec, out: an_instance_of(StringIO), rebuild: false) + end + end + end + + describe 'cache' do + it 'caches a stdlib gem without erroring out' do + expect { shell.cache('stringio') }.not_to raise_error + end + + context 'when gem does not exist' do + subject(:call) { shell.cache('nonexistentgem8675309') } + + it 'gives a good error message' do + # capture stderr output + expect { call }.to output(/not found/).to_stderr + end + end + end + + # @type cmd [Array] + # @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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 00cc6c8c3..59d107aa3 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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 From 07a4434c747939c0c441fc368863ba18c4f3a278 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 10:51:38 -0400 Subject: [PATCH 182/460] Fix merge --- lib/solargraph/rbs_map/core_map.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solargraph/rbs_map/core_map.rb b/lib/solargraph/rbs_map/core_map.rb index 310e67ee0..3fa34ae89 100644 --- a/lib/solargraph/rbs_map/core_map.rb +++ b/lib/solargraph/rbs_map/core_map.rb @@ -26,6 +26,8 @@ def pins out: $stderr else @pins.concat conversions.pins + @pins.concat RbsMap::CoreFills::ALL + # Avoid RBS::DuplicatedDeclarationError by loading in a different EnvironmentLoader fill_loader = RBS::EnvironmentLoader.new(core_root: nil, repository: RBS::Repository.new(no_stdlib: false)) fill_loader.add(path: Pathname(FILLS_DIRECTORY)) From 846a3acc526e4ac7403f572207294c4330f00815 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 10:54:12 -0400 Subject: [PATCH 183/460] Fix merge --- lib/solargraph/rbs_map/core_map.rb | 4 - spec/shell_spec.rb | 120 ++--------------------------- 2 files changed, 7 insertions(+), 117 deletions(-) diff --git a/lib/solargraph/rbs_map/core_map.rb b/lib/solargraph/rbs_map/core_map.rb index 3fa34ae89..6d72a549c 100644 --- a/lib/solargraph/rbs_map/core_map.rb +++ b/lib/solargraph/rbs_map/core_map.rb @@ -50,10 +50,6 @@ def cache_core out: $stderr pins out: out end - def loader - @loader ||= RBS::EnvironmentLoader.new(repository: RBS::Repository.new(no_stdlib: false)) - end - private # @return [RBS::EnvironmentLoader] diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index 0c4b36738..f21029b5e 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -16,11 +16,15 @@ raise "Failure installing bundle: #{output}" unless status.success? end + # @type cmd [Array] + # @return [String] def bundle_exec(*cmd) # run the command in the temporary directory with bundle exec - output, status = Open3.capture2e("bundle exec #{cmd.join(' ')}", chdir: temp_dir) - expect(status.success?).to be(true), "Command failed: #{output}" - output + 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 after do @@ -283,114 +287,4 @@ def bundle_exec(*cmd) end end end - - # @type cmd [Array] - # @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 From 238fc078334eec016bfe33ab2e99237db45f4e72 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 12:29:48 -0400 Subject: [PATCH 184/460] Factor out require_paths logic to its own class --- .rubocop.yml | 3 + .rubocop_todo.yml | 130 ++-------------------- lib/solargraph/workspace.rb | 63 +++-------- lib/solargraph/workspace/require_paths.rb | 98 ++++++++++++++++ spec/workspace/require_paths_spec.rb | 87 +++++++++++++++ 5 files changed, 211 insertions(+), 170 deletions(-) create mode 100644 lib/solargraph/workspace/require_paths.rb create mode 100644 spec/workspace/require_paths_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index a73324db2..c17a56410 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,6 +21,9 @@ AllCops: - "vendor/**/.*" TargetRubyVersion: 3.0 +RSpec/SpecFilePathFormat: + Enabled: false + Style/MethodDefParentheses: EnforcedStyle: require_no_parentheses diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b8d123824..89fd47c5d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.80.0. +# using RuboCop version 1.80.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -64,7 +64,6 @@ Layout/BlockAlignment: Layout/ClosingHeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment. @@ -225,7 +224,6 @@ Layout/HashAlignment: Layout/HeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - 'spec/yard_map/mapper/to_method_spec.rb' # This cop supports safe autocorrection (--autocorrect). @@ -920,9 +918,9 @@ RSpec/DescribeClass: - '**/spec/routing/**/*' - '**/spec/system/**/*' - '**/spec/views/**/*' + - 'spec/api_map_method_spec.rb' - 'spec/complex_type_spec.rb' - 'spec/source_map/node_processor_spec.rb' - - 'spec/api_map_method_spec.rb' # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. @@ -1045,7 +1043,6 @@ RSpec/ExampleLength: # DisallowedExamples: works RSpec/ExampleWording: Exclude: - - 'spec/convention/struct_definition_spec.rb' - 'spec/pin/base_spec.rb' - 'spec/pin/method_spec.rb' @@ -1086,7 +1083,6 @@ RSpec/ImplicitExpect: RSpec/InstanceVariable: Exclude: - 'spec/api_map/config_spec.rb' - - 'spec/api_map_spec.rb' - 'spec/diagnostics/require_not_found_spec.rb' - 'spec/language_server/host/dispatch_spec.rb' - 'spec/language_server/host_spec.rb' @@ -1106,10 +1102,13 @@ RSpec/LetBeforeExamples: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: . +# Configuration parameters: EnforcedStyle. # SupportedStyles: have_received, receive RSpec/MessageSpies: - EnforcedStyle: receive + Exclude: + - 'spec/doc_map_spec.rb' + - 'spec/language_server/host/diagnoser_spec.rb' + - 'spec/language_server/host/message_worker_spec.rb' RSpec/MissingExampleGroupArgument: Exclude: @@ -1263,102 +1262,6 @@ RSpec/ScatteredLet: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata. -RSpec/SpecFilePathFormat: - Exclude: - - '**/spec/routing/**/*' - - 'spec/api_map/cache_spec.rb' - - 'spec/api_map/config_spec.rb' - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/api_map/store_spec.rb' - - 'spec/api_map_spec.rb' - - 'spec/convention/activesupport_concern_spec.rb' - - 'spec/convention/struct_definition_spec.rb' - - 'spec/convention_spec.rb' - - 'spec/diagnostics/base_spec.rb' - - 'spec/diagnostics/require_not_found_spec.rb' - - 'spec/diagnostics/rubocop_helpers_spec.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/diagnostics/type_check_spec.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/diagnostics_spec.rb' - - 'spec/doc_map_spec.rb' - - 'spec/gem_pins_spec.rb' - - 'spec/language_server/host/diagnoser_spec.rb' - - 'spec/language_server/host/dispatch_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/message/text_document/formatting_spec.rb' - - 'spec/language_server/message/text_document/hover_spec.rb' - - 'spec/language_server/message/text_document/rename_spec.rb' - - 'spec/language_server/message/text_document/type_definition_spec.rb' - - 'spec/language_server/message/workspace/did_change_configuration_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/message_spec.rb' - - 'spec/language_server/transport/adapter_spec.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - - 'spec/language_server/uri_helpers_spec.rb' - - 'spec/library_spec.rb' - - 'spec/logging_spec.rb' - - 'spec/parser/flow_sensitive_typing_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/parser/node_processor_spec.rb' - - 'spec/parser_spec.rb' - - 'spec/pin/base_spec.rb' - - 'spec/pin/base_variable_spec.rb' - - 'spec/pin/block_spec.rb' - - 'spec/pin/constant_spec.rb' - - 'spec/pin/delegated_method_spec.rb' - - 'spec/pin/documenting_spec.rb' - - 'spec/pin/instance_variable_spec.rb' - - 'spec/pin/keyword_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/pin/namespace_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/pin/search_spec.rb' - - 'spec/pin/symbol_spec.rb' - - 'spec/position_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/rbs_map_spec.rb' - - 'spec/shell_spec.rb' - - 'spec/source/chain/array_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/chain/constant_spec.rb' - - 'spec/source/chain/global_variable_spec.rb' - - 'spec/source/chain/head_spec.rb' - - 'spec/source/chain/instance_variable_spec.rb' - - 'spec/source/chain/link_spec.rb' - - 'spec/source/chain/literal_spec.rb' - - 'spec/source/chain/z_super_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source/change_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source/updater_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' - - 'spec/type_checker/checks_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker/levels/strong_spec.rb' - - 'spec/type_checker/levels/typed_spec.rb' - - 'spec/type_checker/rules_spec.rb' - - 'spec/type_checker_spec.rb' - - 'spec/workspace/config_spec.rb' - - 'spec/workspace_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' - - 'spec/yard_map/mapper_spec.rb' - RSpec/StubbedMock: Exclude: - 'spec/language_server/host/message_worker_spec.rb' @@ -2225,12 +2128,6 @@ Style/RedundantFreeze: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/source_map/mapper.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowComments. -Style/RedundantInitialize: - Exclude: - - 'lib/solargraph/rbs_map/core_map.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: @@ -2246,6 +2143,7 @@ Style/RedundantParentheses: - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/parameter.rb' + - 'lib/solargraph/pin/search.rb' - 'lib/solargraph/source.rb' - 'lib/solargraph/type_checker.rb' @@ -2258,24 +2156,13 @@ Style/RedundantRegexpArgument: - 'spec/diagnostics/rubocop_spec.rb' - 'spec/language_server/host_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantRegexpCharacterClass: - Exclude: - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/source_chainer.rb' - # This cop supports safe autocorrection (--autocorrect). Style/RedundantRegexpEscape: Exclude: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/diagnostics/rubocop.rb' - 'lib/solargraph/language_server/uri_helpers.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source/change.rb' - - 'lib/solargraph/source/cursor.rb' - 'lib/solargraph/source_map/clip.rb' - 'lib/solargraph/source_map/mapper.rb' @@ -2638,7 +2525,6 @@ YARD/MismatchName: Exclude: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/gem_pins.rb' - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/language_server/host/dispatch.rb' - 'lib/solargraph/language_server/request.rb' diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index ffd653d96..a8325ea89 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -10,15 +10,11 @@ module Solargraph # class Workspace autoload :Config, 'solargraph/workspace/config' + autoload :RequirePaths, 'solargraph/workspace/require_paths' # @return [String] attr_reader :directory - # The require paths associated with the workspace. - # - # @return [Array] - attr_reader :require_paths - # @return [Array] attr_reader :gemnames alias source_gems gemnames @@ -32,10 +28,17 @@ def initialize directory = '', config = nil, server = {} @server = server load_sources @gemnames = [] - @require_paths = generate_require_paths require_plugins end + # The require paths associated with the workspace. + # + # @return [Array] + def require_paths + # @todo are the semantics of '*' the same as '', meaning 'don't send back any require paths'? + @require_paths ||= RequirePaths.new(directory_or_nil, config).generate + end + # @return [Solargraph::Workspace::Config] def config @config ||= Solargraph::Workspace::Config.new(directory) @@ -156,6 +159,12 @@ def command_path server['commandPath'] || 'solargraph' end + # @return [String, nil] + def directory_or_nil + return nil if directory.empty? || directory == '*' + directory + end + # True if the workspace has a root Gemfile. # # @todo Handle projects with custom Bundler/Gemfile setups (see DocMap#gemspecs_required_from_bundler) @@ -193,48 +202,6 @@ def load_sources end end - # Generate require paths from gemspecs if they exist or assume the default - # lib directory. - # - # @return [Array] - def generate_require_paths - return configured_require_paths unless gemspec? - result = [] - gemspecs.each do |file| - base = File.dirname(file) - # HACK: Evaluating gemspec files violates the goal of not running - # workspace code, but this is how Gem::Specification.load does it - # anyway. - cmd = ['ruby', '-e', "require 'rubygems'; require 'json'; spec = eval(File.read('#{file}'), TOPLEVEL_BINDING, '#{file}'); return unless Gem::Specification === spec; puts({name: spec.name, paths: spec.require_paths}.to_json)"] - o, e, s = Open3.capture3(*cmd) - if s.success? - begin - hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {} - next if hash.empty? - @gemnames.push hash['name'] - result.concat(hash['paths'].map { |path| File.join(base, path) }) - rescue StandardError => e - Solargraph.logger.warn "Error reading #{file}: [#{e.class}] #{e.message}" - end - else - Solargraph.logger.warn "Error reading #{file}" - Solargraph.logger.warn e - end - end - result.concat(config.require_paths.map { |p| File.join(directory, p) }) - result.push File.join(directory, 'lib') if result.empty? - result - end - - # Get additional require paths defined in the configuration. - # - # @return [Array] - def configured_require_paths - return ['lib'] if directory.empty? - return [File.join(directory, 'lib')] if config.require_paths.empty? - config.require_paths.map { |p| File.join(directory, p) } - end - # @return [void] def require_plugins config.plugins.each do |plugin| diff --git a/lib/solargraph/workspace/require_paths.rb b/lib/solargraph/workspace/require_paths.rb new file mode 100644 index 000000000..67adae9e6 --- /dev/null +++ b/lib/solargraph/workspace/require_paths.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require 'open3' + +module Solargraph + # A workspace consists of the files in a project's directory and the + # project's configuration. It provides a Source for each file to be used + # in an associated Library or ApiMap. + # + class Workspace + # Manages determining which gemspecs are available in a workspace + class RequirePaths + attr_reader :directory, :config + + # @param directory [String, nil] + # @param config [Config, nil] + def initialize directory, config + @directory = directory + @config = config + end + + # Generate require paths from gemspecs if they exist or assume the default + # lib directory. + # + # @return [Array] + def generate + result = require_paths_from_gemspec_files + return configured_require_paths if result.empty? + result.concat(config.require_paths.map { |p| File.join(directory, p) }) if config + result + end + + private + + # @return [Array] + def require_paths_from_gemspec_files + results = [] + gemspec_file_paths.each do |gemspec_file_path| + results.concat require_path_from_gemspec_file(gemspec_file_path) + end + results + end + + # Get an array of all gemspec files in the workspace. + # + # @return [Array] + def gemspec_file_paths + return [] if directory.nil? + @gemspec_file_paths ||= Dir[File.join(directory, '**/*.gemspec')].select do |gs| + config.nil? || config.allow?(gs) + end + end + + # Get additional require paths defined in the configuration. + # + # @return [Array] + def configured_require_paths + return ['lib'] unless directory + return [File.join(directory, 'lib')] if !config || config.require_paths.empty? + config.require_paths.map { |p| File.join(directory, p) } + end + + # Generate require paths from gemspecs if they exist or assume the default + # lib directory. + # + # @param gemspec_file_path [String] + # @return [Array] + def require_path_from_gemspec_file gemspec_file_path + base = File.dirname(gemspec_file_path) + # HACK: Evaluating gemspec files violates the goal of not running + # workspace code, but this is how Gem::Specification.load does it + # anyway. + cmd = ['ruby', '-e', + "require 'rubygems'; " \ + "require 'json'; " \ + "spec = eval(File.read('#{gemspec_file_path}'), TOPLEVEL_BINDING, '#{gemspec_file_path}'); " \ + 'return unless Gem::Specification === spec; ' \ + 'puts({name: spec.name, paths: spec.require_paths}.to_json)'] + # @sg-ignore Unresolved call to capture3 on Module + o, e, s = Open3.capture3(*cmd) + if s.success? + begin + hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {} + return [] if hash.empty? + hash['paths'].map { |path| File.join(base, path) } + rescue StandardError => e + Solargraph.logger.warn "Error reading #{gemspec_file_path}: [#{e.class}] #{e.message}" + [] + end + else + Solargraph.logger.warn "Error reading #{gemspec_file_path}" + Solargraph.logger.warn e + [] + end + end + end + end +end diff --git a/spec/workspace/require_paths_spec.rb b/spec/workspace/require_paths_spec.rb new file mode 100644 index 000000000..eb95d0c5b --- /dev/null +++ b/spec/workspace/require_paths_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require 'fileutils' +require 'tmpdir' + +describe Solargraph::Workspace::RequirePaths do + subject(:paths) { described_class.new(dir_path, config).generate } + + let(:config) { Solargraph::Workspace::Config.new(dir_path) } + + context 'with no config' do + let(:dir_path) { Dir.pwd } + let(:config) { nil } + + it 'includes the lib directory' do + expect(paths).to include(File.join(dir_path, 'lib')) + end + end + + context 'with config and no gemspec' do + let(:dir_path) { File.realpath(Dir.pwd) } + + let(:config) { instance_double(Solargraph::Workspace::Config, require_paths: [], allow?: true) } + + it 'includes the lib directory' do + expect(paths).to include(File.join(dir_path, 'lib')) + end + end + + context 'with current bundle' do + let(:dir_path) { Dir.pwd } + + it 'includes the lib directory' do + expect(paths).to include(File.join(dir_path, 'lib')) + end + + it 'queried via Open3.capture3' do + allow(Open3).to receive(:capture3).and_call_original + + paths + + expect(Open3).to have_received(:capture3) + end + end + + context 'with an invalid gemspec file' do + let(:dir_path) { File.realpath(Dir.mktmpdir) } + let(:gemspec_file) { File.join(dir_path, 'invalid.gemspec') } + + before do + File.write(gemspec_file, 'bogus') + end + + it 'includes the lib directory' do + expect(paths).to include(File.join(dir_path, 'lib')) + end + + it 'does not raise an error' do + expect { paths }.not_to raise_error + end + end + + context 'with a valid gemspec file that outputs to stdout' do + let(:dir_path) { File.realpath(Dir.mktmpdir) } + let(:gemspec_file) { File.join(dir_path, 'invalid.gemspec') } + + before do + File.write(gemspec_file, "print '{'; Gem::Specification.new") + end + + it 'includes the lib directory' do + expect(paths).to include(File.join(dir_path, 'lib')) + end + + it 'does not raise an error' do + expect { paths }.not_to raise_error + end + end + + context 'with no gemspec file' do + let(:dir_path) { File.realpath(Dir.mktmpdir) } + + it 'includes the lib directory' do + expect(paths).to include(File.join(dir_path, 'lib')) + end + end +end From f013ac037a752d39896dce351a2d1535e06a23f9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 12:56:56 -0400 Subject: [PATCH 185/460] Fix merge issue --- lib/solargraph/workspace/require_paths.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/workspace/require_paths.rb b/lib/solargraph/workspace/require_paths.rb index 67adae9e6..c8eea161b 100644 --- a/lib/solargraph/workspace/require_paths.rb +++ b/lib/solargraph/workspace/require_paths.rb @@ -76,7 +76,6 @@ def require_path_from_gemspec_file gemspec_file_path "spec = eval(File.read('#{gemspec_file_path}'), TOPLEVEL_BINDING, '#{gemspec_file_path}'); " \ 'return unless Gem::Specification === spec; ' \ 'puts({name: spec.name, paths: spec.require_paths}.to_json)'] - # @sg-ignore Unresolved call to capture3 on Module o, e, s = Open3.capture3(*cmd) if s.success? begin From f63248177198c73cb9474367bdcdd0adce9f0936 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 18:17:14 -0400 Subject: [PATCH 186/460] Centralize all pin-caching logic in PinCache --- .github/workflows/rspec.yml | 4 + .rubocop.yml | 3 + .rubocop_todo.yml | 166 +----- lib/solargraph/api_map.rb | 99 ++-- lib/solargraph/doc_map.rb | 360 ++++--------- lib/solargraph/gem_pins.rb | 13 +- lib/solargraph/library.rb | 41 +- .../parser/parser_gem/class_methods.rb | 3 + lib/solargraph/pin_cache.rb | 508 ++++++++++++++++-- lib/solargraph/rbs_map.rb | 91 +++- lib/solargraph/rbs_map/core_map.rb | 43 +- lib/solargraph/rbs_map/stdlib_map.rb | 25 +- lib/solargraph/shell.rb | 9 +- lib/solargraph/workspace.rb | 88 ++- lib/solargraph/yardoc.rb | 79 ++- spec/doc_map_spec.rb | 149 +++-- spec/gem_pins_spec.rb | 53 +- spec/language_server/host/diagnoser_spec.rb | 3 +- .../host/message_worker_spec.rb | 2 +- spec/library_spec.rb | 312 +++++++---- spec/pin_cache_spec.rb | 197 +++++++ spec/rbs_map_spec.rb | 13 +- spec/shell_spec.rb | 117 +++- spec/spec_helper.rb | 30 +- spec/type_checker/levels/normal_spec.rb | 6 +- spec/yard_map/mapper_spec.rb | 49 +- spec/yardoc_spec.rb | 111 ++++ 27 files changed, 1742 insertions(+), 832 deletions(-) create mode 100644 spec/pin_cache_spec.rb create mode 100644 spec/yardoc_spec.rb diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index ecc3d9771..7b2255b0e 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -48,6 +48,8 @@ jobs: run: | bundle install bundle update rbs # use latest available for this Ruby version + - name: Install types + run: bundle exec rbs collection update - name: Run tests run: bundle exec rake spec undercover: @@ -65,6 +67,8 @@ jobs: bundler-cache: false - name: Install gems run: bundle install + - name: Install types + run: bundle exec rbs collection update - name: Run tests run: bundle exec rake spec - name: Check PR coverage diff --git a/.rubocop.yml b/.rubocop.yml index a73324db2..c17a56410 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,6 +21,9 @@ AllCops: - "vendor/**/.*" TargetRubyVersion: 3.0 +RSpec/SpecFilePathFormat: + Enabled: false + Style/MethodDefParentheses: EnforcedStyle: require_no_parentheses diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b8d123824..f474d025b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.80.0. +# using RuboCop version 1.80.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -64,7 +64,6 @@ Layout/BlockAlignment: Layout/ClosingHeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment. @@ -225,7 +224,6 @@ Layout/HashAlignment: Layout/HeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - 'spec/yard_map/mapper/to_method_spec.rb' # This cop supports safe autocorrection (--autocorrect). @@ -241,7 +239,6 @@ Layout/IndentationWidth: - 'lib/solargraph/parser/parser_gem/node_processors/block_node.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/namespace.rb' - - 'lib/solargraph/rbs_map.rb' - 'lib/solargraph/shell.rb' - 'lib/solargraph/source/chain/call.rb' - 'lib/solargraph/source_map/clip.rb' @@ -295,7 +292,6 @@ Layout/MultilineMethodCallIndentation: # SupportedStyles: aligned, indented Layout/MultilineOperationIndentation: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/language_server/host/dispatch.rb' - 'lib/solargraph/source.rb' @@ -336,7 +332,6 @@ Layout/SpaceAroundOperators: - 'lib/solargraph/source_map/clip.rb' - 'lib/solargraph/workspace/config.rb' - 'spec/library_spec.rb' - - 'spec/yard_map/mapper_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. @@ -511,7 +506,6 @@ Lint/DuplicateMethods: - 'lib/solargraph/pin/base.rb' - 'lib/solargraph/pin/signature.rb' - 'lib/solargraph/rbs_map.rb' - - 'lib/solargraph/rbs_map/core_map.rb' - 'lib/solargraph/source/chain/link.rb' # Configuration parameters: AllowComments, AllowEmptyLambdas. @@ -632,7 +626,6 @@ Lint/UnusedMethodArgument: - 'lib/solargraph/convention/base.rb' - 'lib/solargraph/diagnostics/base.rb' - 'lib/solargraph/diagnostics/update_errors.rb' - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/pin/namespace.rb' - 'lib/solargraph/rbs_map/conversions.rb' - 'lib/solargraph/source.rb' @@ -652,12 +645,6 @@ Lint/UnusedMethodArgument: - 'lib/solargraph/source/chain/z_super.rb' - 'spec/doc_map_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. -Lint/UselessAccessModifier: - Exclude: - - 'lib/solargraph/api_map.rb' - # This cop supports safe autocorrection (--autocorrect). Lint/UselessAssignment: Exclude: @@ -700,7 +687,6 @@ Metrics/AbcSize: - 'lib/solargraph/api_map.rb' - 'lib/solargraph/api_map/source_to_yard.rb' - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/language_server/message/initialize.rb' - 'lib/solargraph/library.rb' @@ -770,7 +756,6 @@ Metrics/ModuleLength: Exclude: - 'lib/solargraph/complex_type/type_methods.rb' - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin_cache.rb' # Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters. Metrics/ParameterLists: @@ -817,7 +802,6 @@ Naming/MemoizedInstanceVariableName: - 'lib/solargraph/convention/gemfile.rb' - 'lib/solargraph/convention/gemspec.rb' - 'lib/solargraph/convention/rakefile.rb' - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/rbs_map.rb' - 'lib/solargraph/workspace.rb' @@ -871,7 +855,6 @@ Naming/VariableName: RSpec/Be: Exclude: - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/rbs_map_spec.rb' - 'spec/source/source_chainer_spec.rb' # This cop supports unsafe autocorrection (--autocorrect-all). @@ -894,7 +877,6 @@ RSpec/BeforeAfterAll: - '**/spec/rails_helper.rb' - '**/spec/support/**/*.rb' - 'spec/api_map_spec.rb' - - 'spec/doc_map_spec.rb' - 'spec/language_server/host/dispatch_spec.rb' - 'spec/language_server/protocol_spec.rb' @@ -907,7 +889,6 @@ RSpec/ContextWording: - 'spec/pin/method_spec.rb' - 'spec/pin/parameter_spec.rb' - 'spec/pin/symbol_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - 'spec/type_checker/levels/strict_spec.rb' - 'spec/type_checker/levels/strong_spec.rb' - 'spec/type_checker/levels/typed_spec.rb' @@ -920,9 +901,9 @@ RSpec/DescribeClass: - '**/spec/routing/**/*' - '**/spec/system/**/*' - '**/spec/views/**/*' + - 'spec/api_map_method_spec.rb' - 'spec/complex_type_spec.rb' - 'spec/source_map/node_processor_spec.rb' - - 'spec/api_map_method_spec.rb' # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. @@ -941,7 +922,6 @@ RSpec/DescribedClass: - 'spec/diagnostics/update_errors_spec.rb' - 'spec/diagnostics_spec.rb' - 'spec/doc_map_spec.rb' - - 'spec/gem_pins_spec.rb' - 'spec/language_server/host/diagnoser_spec.rb' - 'spec/language_server/host/dispatch_spec.rb' - 'spec/language_server/host/message_worker_spec.rb' @@ -1045,7 +1025,6 @@ RSpec/ExampleLength: # DisallowedExamples: works RSpec/ExampleWording: Exclude: - - 'spec/convention/struct_definition_spec.rb' - 'spec/pin/base_spec.rb' - 'spec/pin/method_spec.rb' @@ -1086,7 +1065,6 @@ RSpec/ImplicitExpect: RSpec/InstanceVariable: Exclude: - 'spec/api_map/config_spec.rb' - - 'spec/api_map_spec.rb' - 'spec/diagnostics/require_not_found_spec.rb' - 'spec/language_server/host/dispatch_spec.rb' - 'spec/language_server/host_spec.rb' @@ -1106,11 +1084,6 @@ RSpec/LetBeforeExamples: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: . -# SupportedStyles: have_received, receive -RSpec/MessageSpies: - EnforcedStyle: receive - RSpec/MissingExampleGroupArgument: Exclude: - 'spec/diagnostics/rubocop_helpers_spec.rb' @@ -1129,9 +1102,6 @@ RSpec/MultipleExpectations: - 'spec/diagnostics/type_check_spec.rb' - 'spec/diagnostics/update_errors_spec.rb' - 'spec/diagnostics_spec.rb' - - 'spec/doc_map_spec.rb' - - 'spec/gem_pins_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - 'spec/language_server/host_spec.rb' - 'spec/language_server/message/completion_item/resolve_spec.rb' - 'spec/language_server/message/initialize_spec.rb' @@ -1157,7 +1127,6 @@ RSpec/MultipleExpectations: - 'spec/rbs_map/core_map_spec.rb' - 'spec/rbs_map/stdlib_map_spec.rb' - 'spec/rbs_map_spec.rb' - - 'spec/shell_spec.rb' - 'spec/source/chain/call_spec.rb' - 'spec/source/chain/class_variable_spec.rb' - 'spec/source/chain/global_variable_spec.rb' @@ -1263,106 +1232,6 @@ RSpec/ScatteredLet: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata. -RSpec/SpecFilePathFormat: - Exclude: - - '**/spec/routing/**/*' - - 'spec/api_map/cache_spec.rb' - - 'spec/api_map/config_spec.rb' - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/api_map/store_spec.rb' - - 'spec/api_map_spec.rb' - - 'spec/convention/activesupport_concern_spec.rb' - - 'spec/convention/struct_definition_spec.rb' - - 'spec/convention_spec.rb' - - 'spec/diagnostics/base_spec.rb' - - 'spec/diagnostics/require_not_found_spec.rb' - - 'spec/diagnostics/rubocop_helpers_spec.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/diagnostics/type_check_spec.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/diagnostics_spec.rb' - - 'spec/doc_map_spec.rb' - - 'spec/gem_pins_spec.rb' - - 'spec/language_server/host/diagnoser_spec.rb' - - 'spec/language_server/host/dispatch_spec.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/language_server/message/completion_item/resolve_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - - 'spec/language_server/message/initialize_spec.rb' - - 'spec/language_server/message/text_document/definition_spec.rb' - - 'spec/language_server/message/text_document/formatting_spec.rb' - - 'spec/language_server/message/text_document/hover_spec.rb' - - 'spec/language_server/message/text_document/rename_spec.rb' - - 'spec/language_server/message/text_document/type_definition_spec.rb' - - 'spec/language_server/message/workspace/did_change_configuration_spec.rb' - - 'spec/language_server/message/workspace/did_change_watched_files_spec.rb' - - 'spec/language_server/message_spec.rb' - - 'spec/language_server/transport/adapter_spec.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - - 'spec/language_server/uri_helpers_spec.rb' - - 'spec/library_spec.rb' - - 'spec/logging_spec.rb' - - 'spec/parser/flow_sensitive_typing_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/parser/node_processor_spec.rb' - - 'spec/parser_spec.rb' - - 'spec/pin/base_spec.rb' - - 'spec/pin/base_variable_spec.rb' - - 'spec/pin/block_spec.rb' - - 'spec/pin/constant_spec.rb' - - 'spec/pin/delegated_method_spec.rb' - - 'spec/pin/documenting_spec.rb' - - 'spec/pin/instance_variable_spec.rb' - - 'spec/pin/keyword_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/pin/method_spec.rb' - - 'spec/pin/namespace_spec.rb' - - 'spec/pin/parameter_spec.rb' - - 'spec/pin/search_spec.rb' - - 'spec/pin/symbol_spec.rb' - - 'spec/position_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/rbs_map_spec.rb' - - 'spec/shell_spec.rb' - - 'spec/source/chain/array_spec.rb' - - 'spec/source/chain/call_spec.rb' - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/chain/constant_spec.rb' - - 'spec/source/chain/global_variable_spec.rb' - - 'spec/source/chain/head_spec.rb' - - 'spec/source/chain/instance_variable_spec.rb' - - 'spec/source/chain/link_spec.rb' - - 'spec/source/chain/literal_spec.rb' - - 'spec/source/chain/z_super_spec.rb' - - 'spec/source/chain_spec.rb' - - 'spec/source/change_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source/updater_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/source_map/mapper_spec.rb' - - 'spec/source_map_spec.rb' - - 'spec/source_spec.rb' - - 'spec/type_checker/checks_spec.rb' - - 'spec/type_checker/levels/normal_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - - 'spec/type_checker/levels/strong_spec.rb' - - 'spec/type_checker/levels/typed_spec.rb' - - 'spec/type_checker/rules_spec.rb' - - 'spec/type_checker_spec.rb' - - 'spec/workspace/config_spec.rb' - - 'spec/workspace_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' - - 'spec/yard_map/mapper_spec.rb' - -RSpec/StubbedMock: - Exclude: - - 'spec/language_server/host/message_worker_spec.rb' - # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. RSpec/VerifiedDoubles: Exclude: @@ -1833,7 +1702,6 @@ Style/FrozenStringLiteralComment: - 'spec/rbs_map/core_map_spec.rb' - 'spec/rbs_map/stdlib_map_spec.rb' - 'spec/rbs_map_spec.rb' - - 'spec/shell_spec.rb' - 'spec/source/chain/array_spec.rb' - 'spec/source/chain/call_spec.rb' - 'spec/source/chain/class_variable_spec.rb' @@ -1882,7 +1750,6 @@ Style/GuardClause: - 'lib/solargraph/api_map.rb' - 'lib/solargraph/library.rb' - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin_cache.rb' - 'lib/solargraph/range.rb' - 'lib/solargraph/rbs_map/conversions.rb' - 'lib/solargraph/source.rb' @@ -1932,16 +1799,13 @@ Style/IfInsideElse: # This cop supports safe autocorrection (--autocorrect). Style/IfUnlessModifier: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/language_server/message/completion_item/resolve.rb' - 'lib/solargraph/language_server/message/initialize.rb' - 'lib/solargraph/language_server/message/text_document/completion.rb' - 'lib/solargraph/language_server/message/text_document/hover.rb' - - 'lib/solargraph/library.rb' - 'lib/solargraph/parser/parser_gem/class_methods.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/parser/parser_gem/node_methods.rb' @@ -1963,7 +1827,6 @@ Style/IfUnlessModifier: - 'lib/solargraph/source_map/clip.rb' - 'lib/solargraph/source_map/mapper.rb' - 'lib/solargraph/type_checker.rb' - - 'lib/solargraph/workspace.rb' - 'lib/solargraph/workspace/config.rb' - 'lib/solargraph/yard_map/helpers.rb' - 'lib/solargraph/yard_map/mapper/to_method.rb' @@ -2013,7 +1876,6 @@ Style/MethodDefParentheses: - 'lib/solargraph/convention/struct_definition/struct_assignment_node.rb' - 'lib/solargraph/convention/struct_definition/struct_definition_node.rb' - 'lib/solargraph/diagnostics/rubocop_helpers.rb' - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/equality.rb' - 'lib/solargraph/gem_pins.rb' - 'lib/solargraph/language_server/host/message_worker.rb' @@ -2050,7 +1912,6 @@ Style/MethodDefParentheses: - 'lib/solargraph/type_checker.rb' - 'lib/solargraph/type_checker/checks.rb' - 'lib/solargraph/yard_map/helpers.rb' - - 'lib/solargraph/yardoc.rb' - 'spec/doc_map_spec.rb' - 'spec/fixtures/rdoc-lib/lib/example.rb' - 'spec/source_map_spec.rb' @@ -2225,12 +2086,6 @@ Style/RedundantFreeze: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/source_map/mapper.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowComments. -Style/RedundantInitialize: - Exclude: - - 'lib/solargraph/rbs_map/core_map.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: @@ -2246,6 +2101,7 @@ Style/RedundantParentheses: - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/parameter.rb' + - 'lib/solargraph/pin/search.rb' - 'lib/solargraph/source.rb' - 'lib/solargraph/type_checker.rb' @@ -2258,24 +2114,13 @@ Style/RedundantRegexpArgument: - 'spec/diagnostics/rubocop_spec.rb' - 'spec/language_server/host_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantRegexpCharacterClass: - Exclude: - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/source_chainer.rb' - # This cop supports safe autocorrection (--autocorrect). Style/RedundantRegexpEscape: Exclude: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/diagnostics/rubocop.rb' - 'lib/solargraph/language_server/uri_helpers.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source/change.rb' - - 'lib/solargraph/source/cursor.rb' - 'lib/solargraph/source_map/clip.rb' - 'lib/solargraph/source_map/mapper.rb' @@ -2285,7 +2130,6 @@ Style/RedundantReturn: Exclude: - 'lib/solargraph/api_map.rb' - 'lib/solargraph/complex_type/type_methods.rb' - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/parser/parser_gem/node_methods.rb' - 'lib/solargraph/source/chain/z_super.rb' @@ -2322,7 +2166,6 @@ Style/RescueStandardError: Style/SafeNavigation: Exclude: - 'lib/solargraph/api_map/index.rb' - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/language_server/message/completion_item/resolve.rb' - 'lib/solargraph/language_server/request.rb' - 'lib/solargraph/language_server/transport/data_reader.rb' @@ -2330,7 +2173,6 @@ Style/SafeNavigation: - 'lib/solargraph/pin/base.rb' - 'lib/solargraph/pin/conversions.rb' - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin_cache.rb' - 'lib/solargraph/range.rb' - 'lib/solargraph/type_checker.rb' @@ -2403,7 +2245,6 @@ Style/StringLiterals: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/unique_type.rb' - 'lib/solargraph/convention/struct_definition.rb' - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/language_server/message/extended/document_gems.rb' - 'lib/solargraph/language_server/message/extended/download_core.rb' @@ -2638,7 +2479,6 @@ YARD/MismatchName: Exclude: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/gem_pins.rb' - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/language_server/host/dispatch.rb' - 'lib/solargraph/language_server/request.rb' diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index f58633a0c..94b2c7fa0 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -29,6 +29,12 @@ def initialize pins: [] index pins end + # @param out [IO, nil] output stream for logging + # @return [void] + def self.reset_core out: nil + @@core_map = RbsMap::CoreMap.new + end + # # This is a mutable object, which is cached in the Chain class - # if you add any fields which change the results of calls (not @@ -46,6 +52,7 @@ def ==(other) self.eql?(other) end + # @return [Integer] def hash equality_fields.hash end @@ -95,11 +102,12 @@ def catalog bench end unresolved_requires = (bench.external_requires + implicit.requires + bench.workspace.config.required).to_a.compact.uniq recreate_docmap = @unresolved_requires != unresolved_requires || - @doc_map&.uncached_yard_gemspecs&.any? || - @doc_map&.uncached_rbs_collection_gemspecs&.any? || - @doc_map&.rbs_collection_path != bench.workspace.rbs_collection_path + workspace.rbs_collection_path != bench.workspace.rbs_collection_path || + @doc_map.any_uncached? + if recreate_docmap - @doc_map = DocMap.new(unresolved_requires, [], bench.workspace) # @todo Implement gem preferences + @doc_map = DocMap.new(unresolved_requires, [], bench.workspace, out: nil) # @todo Implement gem preferences + @gemspecs = @doc_map.workspace.gemspecs @unresolved_requires = @doc_map.unresolved_requires end @cache.clear if store.update(@@core_map.pins, @doc_map.pins, implicit.pins, iced_pins, live_pins) @@ -114,26 +122,14 @@ def catalog bench [self.class, @source_map_hash, implicit, @doc_map, @unresolved_requires] end - # @return [DocMap] - def doc_map - @doc_map ||= DocMap.new([], []) - end + # @return [DocMap, nil] + attr_reader :doc_map # @return [::Array] def uncached_gemspecs @doc_map&.uncached_gemspecs || [] end - # @return [::Array] - def uncached_rbs_collection_gemspecs - @doc_map.uncached_rbs_collection_gemspecs - end - - # @return [::Array] - def uncached_yard_gemspecs - @doc_map.uncached_yard_gemspecs - end - # @return [Enumerable] def core_pins @@core_map.pins @@ -177,6 +173,7 @@ def clip_at filename, position # Create an ApiMap with a workspace in the specified directory. # # @param directory [String] + # # @return [ApiMap] def self.load directory api_map = new @@ -190,8 +187,8 @@ def self.load directory # @param out [IO, nil] # @return [void] - def cache_all!(out) - @doc_map.cache_all!(out) + def cache_all_for_doc_map! out + @doc_map.cache_doc_map_gems!(out) end # @param gemspec [Gem::Specification] @@ -212,15 +209,16 @@ class << self # # @param directory [String] # @param out [IO] The output stream for messages + # # @return [ApiMap] - def self.load_with_cache directory, out + def self.load_with_cache directory, out = $stderr api_map = load(directory) if api_map.uncached_gemspecs.empty? logger.info { "All gems cached for #{directory}" } return api_map end - api_map.cache_all!(out) + api_map.cache_all_for_doc_map!(out) load(directory) end @@ -236,13 +234,6 @@ def keyword_pins store.pins_by_class(Pin::Keyword) end - # An array of namespace names defined in the ApiMap. - # - # @return [Set] - def namespaces - store.namespaces - end - # True if the namespace exists. # # @param name [String] The namespace to match @@ -326,7 +317,7 @@ def qualify tag, context_tag = '' # @param context_namespace [String] The context namespace in which the # tag was referenced; start from here to resolve the name # @return [String, nil] fully qualified namespace - def qualify_namespace(namespace, context_namespace = '') + def qualify_namespace namespace, context_namespace = '' cached = cache.get_qualified_namespace(namespace, context_namespace) return cached.clone unless cached.nil? result = if namespace.start_with?('::') @@ -356,7 +347,7 @@ def get_includes(fqns) # @param namespace [String] A fully qualified namespace # @param scope [Symbol] :instance or :class # @return [Array] - def get_instance_variable_pins(namespace, scope = :instance) + def get_instance_variable_pins namespace, scope = :instance result = [] used = [namespace] result.concat store.get_instance_variables(namespace, scope) @@ -369,8 +360,10 @@ def get_instance_variable_pins(namespace, scope = :instance) result end - # @sg-ignore Missing @return tag for Solargraph::ApiMap#visible_pins # @see Solargraph::Parser::FlowSensitiveTyping#visible_pins + # @param (see Solargraph::Parser::FlowSensitiveTyping#visible_pins) + # @sg-ignore Missing @return tag for Solargraph::ApiMap#visible_pins + # @return (see Solargraph::Parser::FlowSensitiveTyping#visible_pins) def visible_pins(*args, **kwargs, &blk) Solargraph::Parser::FlowSensitiveTyping.visible_pins(*args, **kwargs, &blk) end @@ -379,7 +372,7 @@ def visible_pins(*args, **kwargs, &blk) # # @param namespace [String] A fully qualified namespace # @return [Enumerable] - def get_class_variable_pins(namespace) + def get_class_variable_pins namespace prefer_non_nil_variables(store.get_class_variables(namespace)) end @@ -533,7 +526,8 @@ def get_complex_type_methods complex_type, context = '', internal = false # @param name [String] Method name to look up # @param scope [Symbol] :instance or :class # @param visibility [Array] :public, :protected, and/or :private - # @param preserve_generics [Boolean] + # @param preserve_generics [Boolean] True to preserve any + # unresolved generic parameters, false to erase them # @return [Array] def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private, :protected, :public], preserve_generics: false rooted_type = ComplexType.parse(rooted_tag) @@ -559,7 +553,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] + # @return [Array] def get_path_suggestions path return [] if path.nil? resolve_method_aliases store.get_path_pins(path) @@ -568,7 +562,7 @@ def get_path_suggestions path # Get an array of pins that match the specified path. # # @param path [String] - # @return [Enumerable] + # @return [Array] def get_path_pins path get_path_suggestions(path) end @@ -658,7 +652,7 @@ def bundled? filename # @param sup [String] The superclass # @param sub [String] The subclass # @return [Boolean] - def super_and_sub?(sup, sub) + def super_and_sub? sup, sub fqsup = qualify(sup) cls = qualify(sub) tested = [] @@ -677,7 +671,7 @@ def super_and_sub?(sup, sub) # @param module_ns [String] The module namespace (no type parameters) # # @return [Boolean] - def type_include?(host_ns, module_ns) + def type_include? host_ns, module_ns store.get_includes(host_ns).map { |inc_tag| ComplexType.parse(inc_tag).name }.include?(module_ns) end @@ -691,10 +685,17 @@ def resolve_method_aliases pins, visibility = [:public, :private, :protected] next nil if resolved.respond_to?(:visibility) && !visibility.include?(resolved.visibility) resolved end.compact - logger.debug { "ApiMap#resolve_method_aliases(pins=#{pins.map(&:name)}, visibility=#{visibility}) => #{with_resolved_aliases.map(&:name)}" } + logger.debug do + "ApiMap#resolve_method_aliases(pins=#{pins.map(&:name)}, visibility=#{visibility}) => #{with_resolved_aliases.map(&:name)}" + end GemPins.combine_method_pins_by_path(with_resolved_aliases) end + # @return [Workspace, nil] + def workspace + @doc_map&.workspace + end + # @param fq_reference_tag [String] A fully qualified whose method should be pulled in # @param namespace_pin [Pin::Base] Namespace pin for the rooted_type # parameter - used to pull generics information @@ -802,7 +803,8 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false end rooted_sc_tag = qualify_superclass(rooted_tag) unless rooted_sc_tag.nil? - result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, visibility, true, skip, no_core) + result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, + visibility, true, skip, no_core) end else logger.info { "ApiMap#inner_get_methods(#{fqns}, #{scope}, #{visibility}, #{deep}, #{skip}) - looking for get_extends() from #{fqns}" } @@ -812,7 +814,8 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false end rooted_sc_tag = qualify_superclass(rooted_tag) unless rooted_sc_tag.nil? - result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, visibility, true, skip, true) + result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, + visibility, true, skip, true) end unless no_core || fqns.empty? type = get_namespace_type(fqns) @@ -846,9 +849,7 @@ def inner_get_constants fqns, visibility, skip result.concat inner_get_constants(qualify(is, fqns), [:public], skip) end fqsc = qualify_superclass(fqns) - unless %w[Object BasicObject].include?(fqsc) - result.concat inner_get_constants(fqsc, [:public], skip) - end + result.concat inner_get_constants(fqsc, [:public], skip) unless %w[Object BasicObject].include?(fqsc) result end @@ -951,8 +952,6 @@ def prefer_non_nil_variables pins include Logging - private - # @param alias_pin [Pin::MethodAlias] # @return [Pin::Method, nil] def resolve_method_alias(alias_pin) @@ -1027,7 +1026,7 @@ def create_resolved_alias_pin(alias_pin, original) # @param rooted_type [ComplexType] # @param pins [Enumerable] # @return [Array] - def erase_generics(namespace_pin, rooted_type, pins) + def erase_generics namespace_pin, rooted_type, pins return pins unless should_erase_generics_when_done?(namespace_pin, rooted_type) logger.debug("Erasing generics on namespace_pin=#{namespace_pin} / rooted_type=#{rooted_type}") @@ -1038,18 +1037,18 @@ def erase_generics(namespace_pin, rooted_type, pins) # @param namespace_pin [Pin::Namespace] # @param rooted_type [ComplexType] - def should_erase_generics_when_done?(namespace_pin, rooted_type) + def should_erase_generics_when_done? namespace_pin, rooted_type has_generics?(namespace_pin) && !can_resolve_generics?(namespace_pin, rooted_type) end # @param namespace_pin [Pin::Namespace] - def has_generics?(namespace_pin) + def has_generics? namespace_pin namespace_pin && !namespace_pin.generics.empty? end # @param namespace_pin [Pin::Namespace] # @param rooted_type [ComplexType] - def can_resolve_generics?(namespace_pin, rooted_type) + def can_resolve_generics? namespace_pin, rooted_type has_generics?(namespace_pin) && !rooted_type.all_params.empty? end end diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 5fe5e03f9..880451592 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -5,122 +5,86 @@ require 'open3' module Solargraph - # A collection of pins generated from required gems. + # A collection of pins generated from specific 'require' statements + # in code. Multiple can be created per workspace, to represent the + # pins available in different files based on their particular + # 'require' lines. # class DocMap include Logging - # @return [Array] - attr_reader :requires - alias required requires + # @return [Workspace] + attr_reader :workspace # @return [Array] attr_reader :preferences - # @return [Array] - attr_reader :pins - - # @return [Array] - def uncached_gemspecs - uncached_yard_gemspecs.concat(uncached_rbs_collection_gemspecs) - .sort - .uniq { |gemspec| "#{gemspec.name}:#{gemspec.version}" } - end - - # @return [Array] - attr_reader :uncached_yard_gemspecs - - # @return [Array] - attr_reader :uncached_rbs_collection_gemspecs - - # @return [String, nil] - attr_reader :rbs_collection_path - - # @return [String, nil] - attr_reader :rbs_collection_config_path - - # @return [Workspace, nil] - attr_reader :workspace - - # @return [Environ] - attr_reader :environ - # @param requires [Array] # @param preferences [Array] - # @param workspace [Workspace, nil] - def initialize(requires, preferences, workspace = nil) - @requires = requires.compact + # @param workspace [Workspace] + # @param out [IO, nil] output stream for logging + def initialize requires, preferences, workspace, out: $stderr + @provided_requires = requires.compact @preferences = preferences.compact @workspace = workspace - @rbs_collection_path = workspace&.rbs_collection_path - @rbs_collection_config_path = workspace&.rbs_collection_config_path - @environ = Convention.for_global(self) - @requires.concat @environ.requires if @environ - load_serialized_gem_pins - pins.concat @environ.pins + @out = out end - # @param out [IO] - # @return [void] - def cache_all!(out) - # if we log at debug level: - if logger.info? - gem_desc = uncached_gemspecs.map { |gemspec| "#{gemspec.name}:#{gemspec.version}" }.join(', ') - logger.info "Caching pins for gems: #{gem_desc}" unless uncached_gemspecs.empty? - end - logger.debug { "Caching for YARD: #{uncached_yard_gemspecs.map(&:name)}" } - logger.debug { "Caching for RBS collection: #{uncached_rbs_collection_gemspecs.map(&:name)}" } - load_serialized_gem_pins - uncached_gemspecs.each do |gemspec| - cache(gemspec, out: out) + # @return [Array] + def requires + @requires ||= @provided_requires + (workspace.global_environ&.requires || []) + end + alias required requires + + # @return [Array] + def uncached_gemspecs + if @uncached_gemspecs.nil? + @uncached_gemspecs = [] + pins # force lazy-loaded pin lookup end - load_serialized_gem_pins - @uncached_rbs_collection_gemspecs = [] - @uncached_yard_gemspecs = [] + @uncached_gemspecs end - # @param gemspec [Gem::Specification] - # @param out [IO] - # @return [void] - def cache_yard_pins(gemspec, out) - pins = GemPins.build_yard_pins(yard_plugins, gemspec) - PinCache.serialize_yard_gem(gemspec, pins) - logger.info { "Cached #{pins.length} YARD pins for gem #{gemspec.name}:#{gemspec.version}" } unless pins.empty? + # @return [Array] + def pins + @pins ||= load_serialized_gem_pins + (workspace.global_environ&.pins || []) end - # @param gemspec [Gem::Specification] - # @param out [IO] # @return [void] - def cache_rbs_collection_pins(gemspec, out) - rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path) - pins = rbs_map.pins - rbs_version_cache_key = rbs_map.cache_key - # cache pins even if result is zero, so we don't retry building pins - pins ||= [] - PinCache.serialize_rbs_collection_gem(gemspec, rbs_version_cache_key, pins) - logger.info { "Cached #{pins.length} RBS collection pins for gem #{gemspec.name} #{gemspec.version} with cache_key #{rbs_version_cache_key.inspect}" unless pins.empty? } + def reset_pins! + @uncached_gemspecs = nil + @pins = nil end - # @param gemspec [Gem::Specification] - # @param rebuild [Boolean] whether to rebuild the pins even if they are cached + # @return [Solargraph::PinCache] + def pin_cache + @pin_cache ||= workspace.fresh_pincache + end + + def any_uncached? + uncached_gemspecs.any? + end + + # Cache all pins needed for the sources in this doc_map # @param out [IO, nil] output stream for logging # @return [void] - def cache(gemspec, rebuild: false, out: nil) - build_yard = uncached_yard_gemspecs.include?(gemspec) || rebuild - build_rbs_collection = uncached_rbs_collection_gemspecs.include?(gemspec) || rebuild - if build_yard || build_rbs_collection - type = [] - type << 'YARD' if build_yard - type << 'RBS collection' if build_rbs_collection - out.puts("Caching #{type.join(' and ')} pins for gem #{gemspec.name}:#{gemspec.version}") if out + def cache_doc_map_gems! out + unless uncached_gemspecs.empty? + logger.info do + gem_desc = uncached_gemspecs.map { |gemspec| "#{gemspec.name}:#{gemspec.version}" }.join(', ') + "Caching pins for gems: #{gem_desc}" + end end - cache_yard_pins(gemspec, out) if build_yard - cache_rbs_collection_pins(gemspec, out) if build_rbs_collection - end - - # @return [Array] - def gemspecs - @gemspecs ||= required_gems_map.values.compact.flatten + time = Benchmark.measure do + uncached_gemspecs.each do |gemspec| + cache(gemspec, out: out) + end + end + milliseconds = (time.real * 1000).round + if (milliseconds > 500) && uncached_gemspecs.any? && out && uncached_gemspecs.any? + out.puts "Built #{uncached_gemspecs.length} gems in #{milliseconds} ms" + end + reset_pins! end # @return [Array] @@ -128,77 +92,81 @@ def unresolved_requires @unresolved_requires ||= required_gems_map.select { |_, gemspecs| gemspecs.nil? }.keys end - # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version - def self.all_yard_gems_in_memory - @yard_gems_in_memory ||= {} - end - - # @return [Hash{String => Hash{Array(String, String) => Array}}] stored by RBS collection path - def self.all_rbs_collection_gems_in_memory - @rbs_collection_gems_in_memory ||= {} - end - - # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version - def yard_pins_in_memory - self.class.all_yard_gems_in_memory - end - - # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version - def rbs_collection_pins_in_memory - self.class.all_rbs_collection_gems_in_memory[rbs_collection_path] ||= {} - end - - # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version - def self.all_combined_pins_in_memory - @combined_pins_in_memory ||= {} + # @return [Set] + # @param out [IO] + def dependencies out: $stderr + @dependencies ||= + begin + all_deps = gemspecs.flat_map { |spec| fetch_dependencies(spec, out: out) } + existing_gems = gemspecs.map(&:name) + all_deps.reject { |gemspec| existing_gems.include? gemspec.name }.to_set + end end - # @todo this should also include an index by the hash of the RBS collection - # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version - def combined_pins_in_memory - self.class.all_combined_pins_in_memory + # Cache gem documentation if needed for this doc_map + # + # @param gemspec [Gem::Specification] + # @param rebuild [Boolean] whether to rebuild the pins even if they are cached + # @param out [IO, nil] output stream for logging + # + # @return [void] + def cache gemspec, rebuild: false, out: nil + pin_cache.cache_gem(gemspec: gemspec, + rebuild: rebuild, + out: out) end - # @return [Array] - def yard_plugins - @environ.yard_plugins - end + private - # @return [Set] - def dependencies - @dependencies ||= (gemspecs.flat_map { |spec| fetch_dependencies(spec) } - gemspecs).to_set + # @return [Array] + def gemspecs + @gemspecs ||= required_gems_map.values.compact.flatten end - private - - # @return [void] - def load_serialized_gem_pins - @pins = [] - @uncached_yard_gemspecs = [] - @uncached_rbs_collection_gemspecs = [] + # @param out [IO, nil] + # @return [Array] + def load_serialized_gem_pins out: @out + serialized_pins = [] with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v } - # @sg-ignore Wrong argument type for Hash.[]: arg_0 expected _ToHash), undefined>, received Array)> + # @sg-ignore Need support for RBS duck interfaces like _ToHash # @type [Array] - paths = Hash[without_gemspecs].keys - # @sg-ignore Wrong argument type for Hash.[]: arg_0 expected _ToHash), undefined>, received Array)> + missing_paths = Hash[without_gemspecs].keys + # @sg-ignore Need support for RBS duck interfaces like _ToHash # @type [Array] - gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a - - paths.each do |path| - rbs_pins = deserialize_stdlib_rbs_map path + gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies(out: out).to_a + + missing_paths.each do |path| + # this will load from disk if needed; no need to manage + # uncached_gemspecs to trigger that later + stdlib_name_guess = path.split('/').first + + # try to resolve the stdlib name + deps = workspace.stdlib_dependencies(stdlib_name_guess) || [] + [stdlib_name_guess, *deps].compact.each do |potential_stdlib_name| + rbs_pins = pin_cache.cache_stdlib_rbs_map potential_stdlib_name + serialized_pins.concat rbs_pins if rbs_pins + end end - logger.debug { "DocMap#load_serialized_gem_pins: Combining pins..." } + existing_pin_count = serialized_pins.length time = Benchmark.measure do gemspecs.each do |gemspec| - pins = deserialize_combined_pin_cache gemspec - @pins.concat pins if pins + # only deserializes already-cached gems + gemspec_pins = pin_cache.deserialize_combined_pin_cache gemspec + if gemspec_pins + serialized_pins.concat gemspec_pins + else + uncached_gemspecs << gemspec + end end end - logger.info { "DocMap#load_serialized_gem_pins: Loaded and processed serialized pins together in #{time.real} seconds" } - @uncached_yard_gemspecs.uniq! - @uncached_rbs_collection_gemspecs.uniq! - nil + pins_processed = serialized_pins.length - existing_pin_count + milliseconds = (time.real * 1000).round + if (milliseconds > 500) && out && gemspecs.any? + out.puts "Deserialized #{serialized_pins.length} gem pins from #{PinCache.base_dir} in #{milliseconds} ms" + end + uncached_gemspecs.uniq! { |gemspec| "#{gemspec.name}:#{gemspec.version}" } + serialized_pins end # @return [Hash{String => Array}] @@ -211,102 +179,6 @@ def preference_map @preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] } end - # @param gemspec [Gem::Specification] - # @return [Array] - def deserialize_yard_pin_cache gemspec - if yard_pins_in_memory.key?([gemspec.name, gemspec.version]) - return yard_pins_in_memory[[gemspec.name, gemspec.version]] - end - - cached = PinCache.deserialize_yard_gem(gemspec) - if cached - logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" } - yard_pins_in_memory[[gemspec.name, gemspec.version]] = cached - cached - else - logger.debug "No YARD pin cache for #{gemspec.name}:#{gemspec.version}" - @uncached_yard_gemspecs.push gemspec - nil - end - end - - # @param gemspec [Gem::Specification] - # @return [void] - def deserialize_combined_pin_cache(gemspec) - unless combined_pins_in_memory[[gemspec.name, gemspec.version]].nil? - return combined_pins_in_memory[[gemspec.name, gemspec.version]] - end - - rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path) - rbs_version_cache_key = rbs_map.cache_key - - cached = PinCache.deserialize_combined_gem(gemspec, rbs_version_cache_key) - if cached - logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" } - combined_pins_in_memory[[gemspec.name, gemspec.version]] = cached - return combined_pins_in_memory[[gemspec.name, gemspec.version]] - end - - rbs_collection_pins = deserialize_rbs_collection_cache gemspec, rbs_version_cache_key - - yard_pins = deserialize_yard_pin_cache gemspec - - if !rbs_collection_pins.nil? && !yard_pins.nil? - logger.debug { "Combining pins for #{gemspec.name}:#{gemspec.version}" } - combined_pins = GemPins.combine(yard_pins, rbs_collection_pins) - PinCache.serialize_combined_gem(gemspec, rbs_version_cache_key, combined_pins) - combined_pins_in_memory[[gemspec.name, gemspec.version]] = combined_pins - logger.info { "Generated #{combined_pins_in_memory[[gemspec.name, gemspec.version]].length} combined pins for #{gemspec.name} #{gemspec.version}" } - return combined_pins - end - - if !yard_pins.nil? - logger.debug { "Using only YARD pins for #{gemspec.name}:#{gemspec.version}" } - combined_pins_in_memory[[gemspec.name, gemspec.version]] = yard_pins - return combined_pins_in_memory[[gemspec.name, gemspec.version]] - elsif !rbs_collection_pins.nil? - logger.debug { "Using only RBS collection pins for #{gemspec.name}:#{gemspec.version}" } - combined_pins_in_memory[[gemspec.name, gemspec.version]] = rbs_collection_pins - return combined_pins_in_memory[[gemspec.name, gemspec.version]] - else - logger.debug { "Pins not yet cached for #{gemspec.name}:#{gemspec.version}" } - return nil - end - end - - # @param path [String] require path that might be in the RBS stdlib collection - # @return [void] - def deserialize_stdlib_rbs_map path - map = RbsMap::StdlibMap.load(path) - if map.resolved? - logger.debug { "Loading stdlib pins for #{path}" } - @pins.concat map.pins - logger.debug { "Loaded #{map.pins.length} stdlib pins for #{path}" } - map.pins - else - # @todo Temporarily ignoring unresolved `require 'set'` - logger.debug { "Require path #{path} could not be resolved in RBS" } unless path == 'set' - nil - end - end - - # @param gemspec [Gem::Specification] - # @param rbs_version_cache_key [String] - # @return [Array, nil] - def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key - return if rbs_collection_pins_in_memory.key?([gemspec, rbs_version_cache_key]) - cached = PinCache.deserialize_rbs_collection_gem(gemspec, rbs_version_cache_key) - if cached - logger.info { "Loaded #{cached.length} pins from RBS collection cache for #{gemspec.name}:#{gemspec.version}" } unless cached.empty? - rbs_collection_pins_in_memory[[gemspec, rbs_version_cache_key]] = cached - cached - else - logger.debug "No RBS collection pin cache for #{gemspec.name} #{gemspec.version}" - @uncached_rbs_collection_gemspecs.push gemspec - nil - end - end - # @param path [String] # @return [::Array, nil] def resolve_path_to_gemspecs path @@ -354,8 +226,10 @@ def change_gemspec_version gemspec, version end # @param gemspec [Gem::Specification] + # @param out [IO, nil] + # # @return [Array] - def fetch_dependencies gemspec + def fetch_dependencies gemspec, out: nil # @param spec [Gem::Dependency] only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps| Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}" diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index a193a8a39..3b61b6a65 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -41,15 +41,6 @@ def self.combine_method_pins(*pins) out end - # @param yard_plugins [Array] The names of YARD plugins to use. - # @param gemspec [Gem::Specification] - # @return [Array] - def self.build_yard_pins(yard_plugins, gemspec) - Yardoc.cache(yard_plugins, gemspec) unless Yardoc.cached?(gemspec) - yardoc = Yardoc.load!(gemspec) - YardMap::Mapper.new(yardoc, gemspec).map - end - # @param yard_pins [Array] # @param rbs_pins [Array] # @@ -68,7 +59,9 @@ def self.combine(yard_pins, rbs_pins) end out = combine_method_pins(rbs_pin, yard_pin) - logger.debug { "GemPins.combine: Combining yard.path=#{yard_pin.path} - rbs=#{rbs_pin.inspect} with yard=#{yard_pin.inspect} into #{out}" } + logger.debug do + "GemPins.combine: Combining yard.path=#{yard_pin.path} - rbs=#{rbs_pin.inspect} with yard=#{yard_pin.inspect} into #{out}" + end out end in_rbs_only = rbs_pins.select do |pin| diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 9d5162431..9b4072d69 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -1,9 +1,16 @@ # frozen_string_literal: true +require 'rubygems' require 'pathname' require 'observer' require 'open3' +# @!parse +# class ::Gem::Specification +# # @return [String] +# def name; end +# end + module Solargraph # A Library handles coordination between a Workspace and an ApiMap. # @@ -273,12 +280,12 @@ def references_from filename, line, column, strip: false, only: false # HACK: for language clients that exclude special characters from the start of variable names if strip && match = cursor.word.match(/^[^a-z0-9_]+/i) found.map! do |loc| - Solargraph::Location.new(loc.filename, Solargraph::Range.from_to(loc.range.start.line, loc.range.start.column + match[0].length, loc.range.ending.line, loc.range.ending.column)) + Solargraph::Location.new(loc.filename, + Solargraph::Range.from_to(loc.range.start.line, loc.range.start.column + match[0].length, loc.range.ending.line, + loc.range.ending.column)) end end - result.concat(found.sort do |a, b| - a.range.start.line <=> b.range.start.line - end) + result.concat(found.sort { |a, b| a.range.start.line <=> b.range.start.line }) end result.uniq end @@ -303,9 +310,7 @@ def locate_ref location return nil if pin.nil? # @param full [String] return_if_match = proc do |full| - if source_map_hash.key?(full) - return Location.new(full, Solargraph::Range.from_to(0, 0, 0, 0)) - end + return Location.new(full, Solargraph::Range.from_to(0, 0, 0, 0)) if source_map_hash.key?(full) end workspace.require_paths.each do |path| full = File.join path, pin.name @@ -500,7 +505,12 @@ def external_requires private - # @return [Hash{String => Array}] + # @return [PinCache] + def pin_cache + workspace.pin_cache + end + + # @return [Hash{String => Set}] def source_map_external_require_hash @source_map_external_require_hash ||= {} end @@ -580,12 +590,13 @@ def cache_errors def cache_next_gemspec return if @cache_progress + # @type [Gem::Specification] spec = cacheable_specs.first return end_cache_progress unless spec pending = api_map.uncached_gemspecs.length - cache_errors.length - 1 - if Yardoc.processing?(spec) + if pin_cache.yardoc_processing?(spec) logger.info "Enqueuing cache of #{spec.name} #{spec.version} (already being processed)" queued_gemspec_cache.push(spec) return if pending - queued_gemspec_cache.length < 1 @@ -596,7 +607,11 @@ def cache_next_gemspec logger.info "Caching #{spec.name} #{spec.version}" Thread.new do report_cache_progress spec.name, pending - _o, e, s = Open3.capture3(workspace.command_path, 'cache', spec.name, spec.version.to_s) + kwargs = {} + kwargs[:chdir] = workspace.directory.to_s if workspace.directory && !workspace.directory.empty? + # @sg-ignore Unresolved call to capture3 on Module + _o, e, s = Open3.capture3(workspace.command_path, 'cache', spec.name, spec.version.to_s, + **kwargs) if s.success? logger.info "Cached #{spec.name} #{spec.version}" else @@ -613,8 +628,7 @@ def cache_next_gemspec # @return [Array] def cacheable_specs - cacheable = api_map.uncached_yard_gemspecs + - api_map.uncached_rbs_collection_gemspecs - + cacheable = api_map.uncached_gemspecs + queued_gemspec_cache - cache_errors.to_a return cacheable unless cacheable.empty? @@ -673,8 +687,7 @@ def sync_catalog source_map_hash.values.each { |map| find_external_requires(map) } api_map.catalog bench logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)" - logger.info "#{api_map.uncached_yard_gemspecs.length} uncached YARD gemspecs" - logger.info "#{api_map.uncached_rbs_collection_gemspecs.length} uncached RBS collection gemspecs" + logger.info "#{api_map.uncached_gemspecs.length} uncached gemspecs" cache_next_gemspec @sync_count = 0 end diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index ddc742bd8..50c537531 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -32,6 +32,9 @@ def parse code, filename = nil, line = 0 buffer = ::Parser::Source::Buffer.new(filename, line) buffer.source = code parser.parse(buffer) + # @sg-ignore Unresolved type Parser::SyntaxError, + # Parser::UnknownEncodingInMagicComment for variable e + # https://github.com/castwide/solargraph/pull/1005 rescue ::Parser::SyntaxError, ::Parser::UnknownEncodingInMagicComment => e raise Parser::SyntaxError, e.message end diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 2a0ec4639..8c47cfef4 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -1,12 +1,424 @@ -require 'yard-activesupport-concern' require 'fileutils' require 'rbs' +require 'rubygems' module Solargraph - module PinCache + class PinCache + include Logging + + attr_reader :directory, :rbs_collection_path, :rbs_collection_config_path, :yard_plugins + + # @param rbs_collection_path [String, nil] + # @param rbs_collection_config_path [String, nil] + # @param directory [String, nil] + # @param yard_plugins [Array] + def initialize rbs_collection_path:, rbs_collection_config_path:, + directory:, + yard_plugins: + @rbs_collection_path = rbs_collection_path + @rbs_collection_config_path = rbs_collection_config_path + @directory = directory + @yard_plugins = yard_plugins + end + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + def cached? gemspec + rbs_version_cache_key = lookup_rbs_version_cache_key(gemspec) + combined_gem?(gemspec, rbs_version_cache_key) + end + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param rebuild [Boolean] whether to rebuild the cache regardless of whether it already exists + # @param out [IO, nil] output stream for logging + # @return [void] + def cache_gem gemspec:, rebuild: false, out: nil + rbs_version_cache_key = lookup_rbs_version_cache_key(gemspec) + + build_yard, build_rbs_collection, build_combined = + calculate_build_needs(gemspec, + rebuild: rebuild, + rbs_version_cache_key: rbs_version_cache_key) + + return unless build_yard || build_rbs_collection || build_combined + + build_combine_and_cache(gemspec, + rbs_version_cache_key, + build_yard: build_yard, + build_rbs_collection: build_rbs_collection, + build_combined: build_combined, + out: out) + end + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param rbs_version_cache_key [String] + def suppress_yard_cache? gemspec, rbs_version_cache_key + if gemspec.name == 'parser' && rbs_version_cache_key != RbsMap::CACHE_KEY_UNRESOLVED + # parser takes forever to build YARD pins, but has excellent RBS collection pins + return true + end + false + end + + # @param out [IO, nil] output stream for logging + # + # @return [void] + def cache_all_stdlibs out: $stderr + possible_stdlibs.each do |stdlib| + RbsMap::StdlibMap.new(stdlib, out: out) + end + end + + # @param path [String] require path that might be in the RBS stdlib collection + # @return [void] + def cache_stdlib_rbs_map path + # these are held in memory in RbsMap::StdlibMap + map = RbsMap::StdlibMap.load(path) + if map.resolved? + logger.debug { "Loading stdlib pins for #{path}" } + pins = map.pins + logger.debug { "Loaded #{pins.length} stdlib pins for #{path}" } + pins + else + # @todo Temporarily ignoring unresolved `require 'set'` + logger.debug { "Require path #{path} could not be resolved in RBS" } unless path == 'set' + nil + end + end + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # + # @return [String] + def lookup_rbs_version_cache_key gemspec + rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path) + rbs_map.cache_key + end + + # @param gemspec [Gem::Specification] + # @param rbs_version_cache_key [String] + # @param yard_pins [Array] + # @param rbs_collection_pins [Array] + # @return [void] + def cache_combined_pins gemspec, rbs_version_cache_key, yard_pins, rbs_collection_pins + combined_pins = GemPins.combine(yard_pins, rbs_collection_pins) + serialize_combined_gem(gemspec, rbs_version_cache_key, combined_pins) + end + + # @param gemspec [Gem::Specification] + # @return [Array] + def deserialize_combined_pin_cache gemspec + rbs_version_cache_key = lookup_rbs_version_cache_key(gemspec) + + load_combined_gem(gemspec, rbs_version_cache_key) + end + + # @param gemspec [Gem::Specification] + # @param out [IO, nil] + # @return [void] + def uncache_gem gemspec, out: nil + PinCache.uncache(yardoc_path(gemspec), out: out) + PinCache.uncache(yard_gem_path(gemspec), out: out) + uncache_by_prefix(rbs_collection_pins_path_prefix(gemspec), out: out) + uncache_by_prefix(combined_path_prefix(gemspec), out: out) + combined_pins_in_memory.delete([gemspec.name, gemspec.version]) + end + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + def yardoc_processing? gemspec + Yardoc.processing?(yardoc_path(gemspec)) + end + + # @return [Array] a list of possible standard library names + def possible_stdlibs + # all dirs and .rb files in Gem::RUBYGEMS_DIR + Dir.glob(File.join(Gem::RUBYGEMS_DIR, '*')).map do |file_or_dir| + basename = File.basename(file_or_dir) + # remove .rb + basename = basename[0..-4] if basename.end_with?('.rb') + basename + end.sort.uniq + rescue StandardError => e + logger.info { "Failed to get possible stdlibs: #{e.message}" } + logger.debug { e.backtrace.join("\n") } + [] + end + + private + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param rebuild [Boolean] whether to rebuild the cache regardless of whether it already exists + # @param rbs_version_cache_key [String, nil] the cache key for the gem in the RBS collection + # + # @return [Array(Boolean, Boolean, Boolean)] whether to build YARD + # pins, RBS collection pins, and combined pins + def calculate_build_needs gemspec, rebuild:, rbs_version_cache_key: + if rebuild + build_yard = true + build_rbs_collection = true + build_combined = true + else + build_yard = !yard_gem?(gemspec) + build_rbs_collection = !rbs_collection_pins?(gemspec, rbs_version_cache_key) + build_combined = !combined_gem?(gemspec, rbs_version_cache_key) || build_yard || build_rbs_collection + end + + build_yard = false if suppress_yard_cache?(gemspec, rbs_version_cache_key) + + [build_yard, build_rbs_collection, build_combined] + end + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param rbs_version_cache_key [String, nil] + # @param build_yard [Boolean] + # @param build_rbs_collection [Boolean] + # @param build_combined [Boolean] + # @param out [IO, nil] + # + # @return [void] + def build_combine_and_cache gemspec, + rbs_version_cache_key, + build_yard:, + build_rbs_collection:, + build_combined:, + out: + log_cache_info(gemspec, rbs_version_cache_key, + build_yard: build_yard, + build_rbs_collection: build_rbs_collection, + build_combined: build_combined, + out: out) + cache_yard_pins(gemspec, out) if build_yard + # this can be nil even if we aren't told to build it - see suppress_yard_cache? + yard_pins = deserialize_yard_pin_cache(gemspec) || [] + cache_rbs_collection_pins(gemspec, out) if build_rbs_collection + rbs_collection_pins = deserialize_rbs_collection_cache(gemspec, rbs_version_cache_key) || [] + cache_combined_pins(gemspec, rbs_version_cache_key, yard_pins, rbs_collection_pins) if build_combined + end + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param rbs_version_cache_key [String, nil] + # @param build_yard [Boolean] + # @param build_rbs_collection [Boolean] + # @param build_combined [Boolean] + # @param out [IO, nil] + # + # @return [void] + def log_cache_info gemspec, + rbs_version_cache_key, + build_yard:, + build_rbs_collection:, + build_combined:, + out: + type = [] + type << 'YARD' if build_yard + rbs_source_desc = RbsMap.rbs_source_desc(rbs_version_cache_key) + type << rbs_source_desc if build_rbs_collection && !rbs_source_desc.nil? + # we'll build it anyway, but it won't take long to build with + # only a single source + + # 'combining' is awkward terminology in this case + just_yard = build_yard && rbs_source_desc.nil? + + type << 'combined' if build_combined && !just_yard + out&.puts("Caching #{type.join(' and ')} pins for gem #{gemspec.name}:#{gemspec.version}") + end + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param out [IO, nil] + # @return [Array] + def cache_yard_pins gemspec, out + gem_yardoc_path = yardoc_path(gemspec) + Yardoc.build_docs(gem_yardoc_path, yard_plugins, gemspec) unless Yardoc.docs_built?(gem_yardoc_path) + pins = Yardoc.build_pins(gem_yardoc_path, gemspec, out: out) + serialize_yard_gem(gemspec, pins) + logger.info { "Cached #{pins.length} YARD pins for gem #{gemspec.name}:#{gemspec.version}" } unless pins.empty? + pins + end + + # @return [Hash{Array(String, String, String) => Array}] + def combined_pins_in_memory + PinCache.all_combined_pins_in_memory[yard_plugins] ||= {} + end + + # @param gemspec [Gem::Specification] + # @param _out [IO, nil] + # @return [Array] + def cache_rbs_collection_pins gemspec, _out + rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path) + pins = rbs_map.pins + rbs_version_cache_key = rbs_map.cache_key + # cache pins even if result is zero, so we don't retry building pins + pins ||= [] + serialize_rbs_collection_pins(gemspec, rbs_version_cache_key, pins) + logger.info do + unless pins.empty? + "Cached #{pins.length} RBS collection pins for gem #{gemspec.name} #{gemspec.version} with " \ + "cache_key #{rbs_version_cache_key.inspect}" + end + end + pins + end + + # @param gemspec [Gem::Specification] + # @return [Array] + def deserialize_yard_pin_cache gemspec + cached = load_yard_gem(gemspec) + if cached + cached + else + logger.debug "No YARD pin cache for #{gemspec.name}:#{gemspec.version}" + nil + end + end + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param rbs_version_cache_key [String] + # @return [Array] + def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key + cached = load_rbs_collection_pins(gemspec, rbs_version_cache_key) + Solargraph.assert_or_log(:pin_cache_rbs_collection, 'Asked for non-existent rbs collection') if cached.nil? + logger.info do + "Loaded #{cached&.length} pins from RBS collection cache for #{gemspec.name}:#{gemspec.version}" + end + cached + end + + # @return [Array] + def yard_path_components + ["yard-#{YARD::VERSION}", + yard_plugins.sort.uniq.join('-')] + end + + # @param gemspec [Gem::Specification] + # @return [String] + def yardoc_path gemspec + File.join(PinCache.base_dir, + *yard_path_components, + "#{gemspec.name}-#{gemspec.version}.yardoc") + end + + # @param gemspec [Gem::Specification] + # @return [String] + def yard_gem_path gemspec + File.join(PinCache.work_dir, *yard_path_components, "#{gemspec.name}-#{gemspec.version}.ser") + end + + # @param gemspec [Gem::Specification] + # @return [Array, nil] + def load_yard_gem gemspec + PinCache.load(yard_gem_path(gemspec)) + end + + # @param gemspec [Gem::Specification] + # @param pins [Array] + # @return [void] + def serialize_yard_gem gemspec, pins + PinCache.save(yard_gem_path(gemspec), pins) + end + + # @param gemspec [Gem::Specification] + # @return [Boolean] + def yard_gem? gemspec + exist?(yard_gem_path(gemspec)) + end + + # @param gemspec [Gem::Specification] + # @param hash [String, nil] + # @return [String] + def rbs_collection_pins_path gemspec, hash + rbs_collection_pins_path_prefix(gemspec) + "#{hash || 0}.ser" + end + + # @param gemspec [Gem::Specification] + # @return [String] + def rbs_collection_pins_path_prefix gemspec + File.join(PinCache.work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-") + end + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param hash [String] + # + # @return [Array, nil] + def load_rbs_collection_pins gemspec, hash + PinCache.load(rbs_collection_pins_path(gemspec, hash)) + end + + # @param gemspec [Gem::Specification] + # @param hash [String, nil] + # @param pins [Array] + # @return [void] + def serialize_rbs_collection_pins gemspec, hash, pins + PinCache.save(rbs_collection_pins_path(gemspec, hash), pins) + end + + # @param gemspec [Gem::Specification] + # @param hash [String, nil] + # @return [String] + def combined_path gemspec, hash + File.join(combined_path_prefix(gemspec) + "-#{hash || 0}.ser") + end + + # @param gemspec [Gem::Specification] + # @return [String] + def combined_path_prefix gemspec + File.join(PinCache.work_dir, 'combined', yard_plugins.sort.join('-'), "#{gemspec.name}-#{gemspec.version}") + end + + # @param gemspec [Gem::Specification] + # @param hash [String, nil] + # @param pins [Array] + # @return [void] + def serialize_combined_gem gemspec, hash, pins + PinCache.save(combined_path(gemspec, hash), pins) + end + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param hash [String] + def combined_gem? gemspec, hash + exist?(combined_path(gemspec, hash)) + end + + # @param gemspec [Gem::Specification] + # @param hash [String, nil] + # @return [Array, nil] + def load_combined_gem gemspec, hash + PinCache.load(combined_path(gemspec, hash)) + end + + # @param gemspec [Gem::Specification] + # @param hash [String] + def rbs_collection_pins? gemspec, hash + exist?(rbs_collection_pins_path(gemspec, hash)) + end + + include Logging + + # @param path [String] + def exist? *path + File.file? File.join(*path) + end + + # @return [void] + # @param path_segments [Array] + def uncache_by_prefix *path_segments, out: nil + path = File.join(*path_segments) + glob = "#{path}*" + out&.puts "Clearing pin cache in #{glob}" + Dir.glob(glob).each do |file| + next unless File.file?(file) + FileUtils.rm_rf file, secure: true + out&.puts "Clearing pin cache in #{file}" + end + end + class << self include Logging + # @return [Hash{Array => Hash{Array(String, String) => + # Array}}] yard plugins, then gemspec name and + # version + def all_combined_pins_in_memory + @all_combined_pins_in_memory ||= {} + end + # The base directory where cached YARD documentation and serialized pins are serialized # # @return [String] @@ -18,6 +430,32 @@ def base_dir File.join(Dir.home, '.cache', 'solargraph') end + # @param path_segments [Array] + # @return [void] + def uncache *path_segments, out: nil + path = File.join(*path_segments) + if File.exist?(path) + FileUtils.rm_rf path, secure: true + out&.puts "Clearing pin cache in #{path}" + else + out&.puts "Pin cache file #{path} does not exist" + end + end + + # @param out [IO, nil] + # @return [void] + def uncache_core out: nil + uncache(core_path, out: out) + # ApiMap keep this in memory + ApiMap.reset_core(out: out) + end + + # @param out [IO, nil] + # @return [void] + def uncache_stdlib out: nil + uncache(stdlib_path, out: out) + end + # The working directory for the current Ruby, RBS, and Solargraph versions. # # @return [String] @@ -27,15 +465,6 @@ def work_dir File.join(base_dir, "ruby-#{RUBY_VERSION}", "rbs-#{RBS::VERSION}", "solargraph-#{Solargraph::VERSION}") end - # @param gemspec [Gem::Specification] - # @return [String] - def yardoc_path gemspec - File.join(base_dir, - "yard-#{YARD::VERSION}", - "yard-activesupport-concern-#{YARD::ActiveSupport::Concern::VERSION}", - "#{gemspec.name}-#{gemspec.version}.yardoc") - end - # @return [String] def stdlib_path File.join(work_dir, 'stdlib') @@ -164,33 +593,11 @@ def has_rbs_collection?(gemspec, hash) exist?(rbs_collection_path(gemspec, hash)) end - # @return [void] - def uncache_core - uncache(core_path) - end - - # @return [void] - def uncache_stdlib - uncache(stdlib_path) - end - - # @param gemspec [Gem::Specification] - # @param out [IO, nil] - # @return [void] - def uncache_gem(gemspec, out: nil) - uncache(yardoc_path(gemspec), out: out) - uncache_by_prefix(rbs_collection_path_prefix(gemspec), out: out) - uncache(yard_gem_path(gemspec), out: out) - uncache_by_prefix(combined_path_prefix(gemspec), out: out) - end - # @return [void] def clear FileUtils.rm_rf base_dir, secure: true end - private - # @param file [String] # @return [Array, nil] def load file @@ -202,11 +609,6 @@ def load file nil end - # @param path [String] - def exist? *path - File.file? File.join(*path) - end - # @param file [String] # @param pins [Array] # @return [void] @@ -218,27 +620,19 @@ def save file, pins logger.debug { "Cache#save: Saved #{pins.length} pins to #{file}" } end - # @param path_segments [Array] - # @return [void] - def uncache *path_segments, out: nil - path = File.join(*path_segments) - if File.exist?(path) - FileUtils.rm_rf path, secure: true - out.puts "Clearing pin cache in #{path}" unless out.nil? - end + def core? + File.file?(core_path) end - # @return [void] - # @param path_segments [Array] - def uncache_by_prefix *path_segments, out: nil - path = File.join(*path_segments) - glob = "#{path}*" - out.puts "Clearing pin cache in #{glob}" unless out.nil? - Dir.glob(glob).each do |file| - next unless File.file?(file) - FileUtils.rm_rf file, secure: true - out.puts "Clearing pin cache in #{file}" unless out.nil? - end + # @param out [IO, nil] + # @return [Array] + def cache_core out: $stderr + RbsMap::CoreMap.new.cache_core(out: out) + end + + # @param path [String] + def exist? *path + File.file? File.join(*path) end end end diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index c6c10bac6..f6309bb55 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -23,10 +23,11 @@ class RbsMap attr_reader :rbs_collection_config_path # @param library [String] - # @param version [String, nil + # @param version [String, nil] # @param rbs_collection_config_path [String, Pathname, nil] # @param rbs_collection_paths [Array] - def initialize library, version = nil, rbs_collection_config_path: nil, rbs_collection_paths: [] + # @param out [IO, nil] where to log messages + def initialize library, version = nil, rbs_collection_config_path: nil, rbs_collection_paths: [], out: $stderr if rbs_collection_config_path.nil? && !rbs_collection_paths.empty? raise 'Please provide rbs_collection_config_path if you provide rbs_collection_paths' end @@ -37,6 +38,28 @@ def initialize library, version = nil, rbs_collection_config_path: nil, rbs_coll add_library loader, library, version end + CACHE_KEY_GEM_EXPORT = 'gem-export' + CACHE_KEY_UNRESOLVED = 'unresolved' + CACHE_KEY_STDLIB = 'stdlib' + CACHE_KEY_LOCAL = 'local' + + # @param cache_key [String] + # @return [String, nil] a description of the source of the RBS info + def self.rbs_source_desc cache_key + case cache_key + when CACHE_KEY_GEM_EXPORT + 'RBS gem export' + when CACHE_KEY_UNRESOLVED + nil + when CACHE_KEY_STDLIB + 'RBS standard library' + when CACHE_KEY_LOCAL + 'local RBS shims' + else + 'RBS collection' + end + end + # @return [RBS::EnvironmentLoader] def loader @loader ||= RBS::EnvironmentLoader.new(core_root: nil, repository: repository) @@ -47,9 +70,13 @@ def loader # updated upstream for the same library and version. May change # if the config for where information comes form changes. def cache_key + return CACHE_KEY_UNRESOLVED unless resolved? + @hextdigest ||= begin # @type [String, nil] data = nil + # @type gem_config [nil, Hash{String => Hash{String => String}}] + gem_config = nil if rbs_collection_config_path lockfile_path = RBS::Collection::Config.to_lockfile_path(Pathname.new(rbs_collection_config_path)) if lockfile_path.exist? @@ -58,21 +85,26 @@ def cache_key data = gem_config&.to_s end end - if data.nil? || data.empty? - if resolved? - # definitely came from the gem itself and not elsewhere - - # only one version per gem - 'gem-export' + if gem_config.nil? + CACHE_KEY_STDLIB + else + # @type [String] + source = gem_config.dig('source', 'type') + case source + when 'rubygems' + CACHE_KEY_GEM_EXPORT + when 'local' + CACHE_KEY_LOCAL + when 'stdlib' + CACHE_KEY_STDLIB else - 'unresolved' + Digest::SHA1.hexdigest(data) end - else - Digest::SHA1.hexdigest(data) end end end - # @param gemspec [Gem::Specification] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @param rbs_collection_path [String, Pathname, nil] # @param rbs_collection_config_path [String, Pathname, nil] # @return [RbsMap] @@ -83,14 +115,24 @@ def self.from_gemspec gemspec, rbs_collection_path, rbs_collection_config_path return rbs_map if rbs_map.resolved? # try any version of the gem in the collection - RbsMap.new(gemspec.name, nil, - rbs_collection_paths: [rbs_collection_path].compact, - rbs_collection_config_path: rbs_collection_config_path) + rbs_map = RbsMap.new(gemspec.name, nil, + rbs_collection_paths: [rbs_collection_path].compact, + rbs_collection_config_path: rbs_collection_config_path) + + return rbs_map if rbs_map.resolved? + + StdlibMap.new(gemspec.name) end + # @param out [IO, nil] where to log messages # @return [Array] - def pins - @pins ||= resolved? ? conversions.pins : [] + def pins out: $stderr + @pins ||= if resolved? + loader.libs.each { |lib| log_caching(lib, out: out) } + conversions.pins + else + [] + end end # @generic T @@ -140,15 +182,22 @@ def conversions @conversions ||= Conversions.new(loader: loader) end + # @param lib [RBS::EnvironmentLoader::Library] + # @param out [IO, nil] where to log messages + # @return [void] + def log_caching lib, out:; end + # @param loader [RBS::EnvironmentLoader] # @param library [String] - # @param version [String, nil] + # @param version [String, nil] the version of the library to load, or nil for any + # @param out [IO, nil] where to log messages # @return [Boolean] true if adding the library succeeded - def add_library loader, library, version + def add_library loader, library, version, out: $stderr @resolved = if loader.has_library?(library: library, version: version) - loader.add library: library, version: version - logger.debug { "#{short_name} successfully loaded library #{library}:#{version}" } - true + # we find our own dependencies from gemfile.lock + loader.add library: library, version: version, resolve_dependencies: false + logger.debug { "#{short_name} successfully loaded library #{library}:#{version}" } + true else logger.info { "#{short_name} did not find data for library #{library}:#{version}" } false diff --git a/lib/solargraph/rbs_map/core_map.rb b/lib/solargraph/rbs_map/core_map.rb index 8c3d7dbdd..a9b647b20 100644 --- a/lib/solargraph/rbs_map/core_map.rb +++ b/lib/solargraph/rbs_map/core_map.rb @@ -14,28 +14,37 @@ def resolved? def initialize; end + # @param out [IO, nil] output stream for logging # @return [Enumerable] - def pins + def pins out: $stderr return @pins if @pins + @pins = cache_core(out: out) + end - @pins = [] + # @param out [IO, nil] output stream for logging + # @return [Array] + def cache_core out: $stderr + new_pins = [] cache = PinCache.deserialize_core - if cache - @pins.replace cache - else - loader.add(path: Pathname(FILLS_DIRECTORY)) - @pins = conversions.pins - @pins.concat RbsMap::CoreFills::ALL - processed = ApiMap::Store.new(pins).pins.reject { |p| p.is_a?(Solargraph::Pin::Reference::Override) } - @pins.replace processed - - PinCache.serialize_core @pins - end - @pins - end + return cache if cache - def loader - @loader ||= RBS::EnvironmentLoader.new(repository: RBS::Repository.new(no_stdlib: false)) + new_pins.concat conversions.pins + + # Avoid RBS::DuplicatedDeclarationError by loading in a different EnvironmentLoader + fill_loader = RBS::EnvironmentLoader.new(core_root: nil, repository: RBS::Repository.new(no_stdlib: false)) + fill_loader.add(path: Pathname(FILLS_DIRECTORY)) + out&.puts 'Caching RBS pins for Ruby core' + fill_conversions = Conversions.new(loader: fill_loader) + new_pins.concat fill_conversions.pins + + new_pins.concat RbsMap::CoreFills::ALL + + processed = ApiMap::Store.new(new_pins).pins.reject { |p| p.is_a?(Solargraph::Pin::Reference::Override) } + new_pins.replace processed + + PinCache.serialize_core new_pins + + new_pins end private diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index b6804157f..e7891bfe3 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -12,8 +12,13 @@ class StdlibMap < RbsMap # @type [Hash{String => RbsMap}] @stdlib_maps_hash = {} + def log_caching lib, out: $stderr + out&.puts("Caching RBS pins for standard library #{lib.name}") + end + # @param library [String] - def initialize library + # @param out [IO, nil] where to log messages + def initialize library, out: $stderr cached_pins = PinCache.deserialize_stdlib_require library if cached_pins @pins = cached_pins @@ -24,7 +29,7 @@ def initialize library super unless resolved? @pins = [] - logger.info { "Could not resolve #{library.inspect}" } + logger.debug { "StdlibMap could not resolve #{library.inspect}" } return end generated_pins = pins @@ -33,6 +38,22 @@ def initialize library end end + # @return [RBS::Collection::Sources::Stdlib] + def self.source + @source ||= RBS::Collection::Sources::Stdlib.instance + end + + # @param name [String] + # @param version [String, nil] + # @return [Array String}>, nil] + def self.stdlib_dependencies name, version = nil + if source.has?(name, version) + source.dependencies_of(name, version) + else + [] + end + end + # @param library [String] # @return [StdlibMap] def self.load library diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index a005f600b..735e6c290 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -3,6 +3,7 @@ require 'benchmark' require 'thor' require 'yard' +require 'yaml' module Solargraph class Shell < Thor @@ -118,19 +119,21 @@ def cache gem, version = nil # @return [void] def uncache *gems raise ArgumentError, 'No gems specified.' if gems.empty? + workspace = Solargraph::Workspace.new(Dir.pwd) + gems.each do |gem| if gem == 'core' - PinCache.uncache_core + PinCache.uncache_core(out: $stdout) next end if gem == 'stdlib' - PinCache.uncache_stdlib + PinCache.uncache_stdlib(out: $stdout) next end spec = Gem::Specification.find_by_name(gem) - PinCache.uncache_gem(spec, out: $stdout) + workspace.uncache_gem(spec, out: $stdout) end end diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index ffd653d96..92be0c8c3 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -2,6 +2,7 @@ require 'open3' require 'json' +require 'yaml' module Solargraph # A workspace consists of the files in a project's directory and the @@ -9,7 +10,10 @@ module Solargraph # in an associated Library or ApiMap. # class Workspace + include Logging + autoload :Config, 'solargraph/workspace/config' + autoload :RequirePaths, 'solargraph/workspace/require_paths' # @return [String] attr_reader :directory @@ -19,14 +23,12 @@ class Workspace # @return [Array] attr_reader :require_paths - # @return [Array] - attr_reader :gemnames - alias source_gems gemnames - - # @param directory [String] + # @param directory [String] TODO: Document and test '' and '*' semantics # @param config [Config, nil] # @param server [Hash] def initialize directory = '', config = nil, server = {} + raise ArgumentError, 'directory must be a String' unless directory.is_a?(String) + @directory = directory @config = config @server = server @@ -41,6 +43,56 @@ def config @config ||= Solargraph::Workspace::Config.new(directory) end + # @return [Solargraph::PinCache] + def pin_cache + @pin_cache ||= fresh_pincache + end + + # @param stdlib_name [String] + # + # @return [Array] + def stdlib_dependencies stdlib_name + deps = RbsMap::StdlibMap.stdlib_dependencies(stdlib_name, nil) || [] + deps.map { |dep| dep['name'] }.compact + end + + # @return [Environ] + def global_environ + # empty docmap, since the result needs to work in any possible + # context here + @global_environ ||= Convention.for_global(DocMap.new([], [], self)) + end + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param out [IO, nil] output stream for logging + # @param rebuild [Boolean] whether to rebuild the pins even if they are cached + # + # @return [void] + def cache_gem gemspec, out: nil, rebuild: false + pin_cache.cache_gem(gemspec: gemspec, out: out, rebuild: rebuild) + end + + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param out [IO, nil] output stream for logging + # + # @return [void] + def uncache_gem gemspec, out: nil + pin_cache.uncache_gem(gemspec, out: out) + end + + # @return [Solargraph::PinCache] + def fresh_pincache + PinCache.new(rbs_collection_path: rbs_collection_path, + rbs_collection_config_path: rbs_collection_config_path, + yard_plugins: yard_plugins, + directory: directory) + end + + # @return [Array] + def yard_plugins + @yard_plugins ||= global_environ.yard_plugins.sort.uniq + end + # Merge the source. A merge will update the existing source for the file # or add it to the sources if the workspace is configured to include it. # The source is ignored if the configuration excludes it. @@ -135,12 +187,23 @@ def rbs_collection_path # @return [String, nil] def rbs_collection_config_path - @rbs_collection_config_path ||= begin - unless directory.empty? || directory == '*' - yaml_file = File.join(directory, 'rbs_collection.yaml') - yaml_file if File.file?(yaml_file) + @rbs_collection_config_path ||= + begin + unless directory.empty? || directory == '*' + yaml_file = File.join(directory, 'rbs_collection.yaml') + yaml_file if File.file?(yaml_file) + end end - end + end + + # @param name [String] + # @param version [String, nil] + # + # @return [Gem::Specification, nil] + def find_gem name, version = nil + Gem::Specification.find_by_name(name, version) + rescue Gem::MissingSpecError + nil end # Synchronize the workspace from the provided updater. @@ -182,7 +245,10 @@ def load_sources source_hash.clear unless directory.empty? || directory == '*' size = config.calculated.length - raise WorkspaceTooLargeError, "The workspace is too large to index (#{size} files, #{config.max_files} max)" if config.max_files > 0 and size > config.max_files + if config.max_files > 0 and size > config.max_files + raise WorkspaceTooLargeError, + "The workspace is too large to index (#{size} files, #{config.max_files} max)" + end config.calculated.each do |filename| begin source_hash[filename] = Solargraph::Source.load(filename) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 625e41ce4..ffe7da4c3 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -8,45 +8,55 @@ module Solargraph module Yardoc module_function - # Build and cache a gem's yardoc and return the path. If the cache already - # exists, do nothing and return the path. + # Build and save a gem's yardoc into a given path. # - # @param yard_plugins [Array] The names of YARD plugins to use. + # @param gem_yardoc_path [String] the path to the yardoc cache of a particular gem + # @param yard_plugins [Array] # @param gemspec [Gem::Specification] - # @return [String] The path to the cached yardoc. - def cache(yard_plugins, gemspec) - path = PinCache.yardoc_path gemspec - return path if cached?(gemspec) + # + # @return [void] + def build_docs gem_yardoc_path, yard_plugins, gemspec + return if docs_built?(gem_yardoc_path) - Solargraph.logger.info "Caching yardoc for #{gemspec.name} #{gemspec.version}" - cmd = "yardoc --db #{path} --no-output --plugin solargraph" + Solargraph.logger.info "Saving yardoc for #{gemspec.name} #{gemspec.version} into #{gem_yardoc_path}" + cmd = "yardoc --db #{gem_yardoc_path} --no-output --plugin solargraph" yard_plugins.each { |plugin| cmd << " --plugin #{plugin}" } Solargraph.logger.debug { "Running: #{cmd}" } # @todo set these up to run in parallel - # - # @sg-ignore RBS gem doesn't reflect that Open3.* also include - # kwopts from Process.spawn() - stdout_and_stderr_str, status = Open3.capture2e(cmd, chdir: gemspec.gem_dir) - unless status.success? - Solargraph.logger.warn { "YARD failed running #{cmd.inspect} in #{gemspec.gem_dir}" } - Solargraph.logger.info stdout_and_stderr_str + unless File.exist?(gemspec.gem_dir) + Solargraph.logger.info { "Bad info from gemspec - #{gemspec.gem_dir} does not exist" } + return end - path + + # @sg-ignore + stdout_and_stderr_str, status = Open3.capture2e(current_bundle_env_tweaks, cmd, chdir: gemspec.gem_dir) + return if status.success? + Solargraph.logger.warn { "YARD failed running #{cmd.inspect} in #{gemspec.gem_dir}" } + Solargraph.logger.info stdout_and_stderr_str + end + + # @param gem_yardoc_path [String] the path to the yardoc cache of a particular gem + # @param gemspec [Gem::Specification] + # @param out [IO, nil] where to log messages + # @return [Array] + def build_pins gem_yardoc_path, gemspec, out: $stderr + yardoc = load!(gem_yardoc_path) + YardMap::Mapper.new(yardoc, gemspec).map end # True if the gem yardoc is cached. # - # @param gemspec [Gem::Specification] - def cached?(gemspec) - yardoc = File.join(PinCache.yardoc_path(gemspec), 'complete') + # @param gem_yardoc_path [String] + def docs_built? gem_yardoc_path + yardoc = File.join(gem_yardoc_path, 'complete') File.exist?(yardoc) end # True if another process is currently building the yardoc cache. # - # @param gemspec [Gem::Specification] - def processing?(gemspec) - yardoc = File.join(PinCache.yardoc_path(gemspec), 'processing') + # @param gem_yardoc_path [String] the path to the yardoc cache of a particular gem + def processing? gem_yardoc_path + yardoc = File.join(gem_yardoc_path, 'processing') File.exist?(yardoc) end @@ -54,11 +64,28 @@ def processing?(gemspec) # # @note This method modifies the global YARD registry. # - # @param gemspec [Gem::Specification] + # @param gem_yardoc_path [String] the path to the yardoc cache of a particular gem # @return [Array] - def load!(gemspec) - YARD::Registry.load! PinCache.yardoc_path gemspec + def load! gem_yardoc_path + YARD::Registry.load! gem_yardoc_path YARD::Registry.all end + + # If the BUNDLE_GEMFILE environment variable is set, we need to + # make sure it's an absolute path, as we'll be changing + # directories. + # + # 'bundle exec' sets an absolute path here, but at least the + # overcommit gem does not, breaking on-the-fly documention with a + # spawned yardoc command from our current bundle + # + # @return [Hash{String => String}] a hash of environment variables to override + def current_bundle_env_tweaks + tweaks = {} + if ENV['BUNDLE_GEMFILE'] && !ENV['BUNDLE_GEMFILE'].empty? + tweaks['BUNDLE_GEMFILE'] = File.expand_path(ENV['BUNDLE_GEMFILE']) + end + tweaks + end end end diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index b03e573f0..b87970d48 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -1,80 +1,129 @@ # frozen_string_literal: true +require 'bundler' +require 'benchmark' + describe Solargraph::DocMap do - before :all do - # We use ast here because it's a known dependency. - gemspec = Gem::Specification.find_by_name('ast') - yard_pins = Solargraph::GemPins.build_yard_pins([], gemspec) - Solargraph::PinCache.serialize_yard_gem(gemspec, yard_pins) + subject(:doc_map) do + described_class.new(requires, [], workspace, out: out) end - it 'generates pins from gems' do - doc_map = Solargraph::DocMap.new(['ast'], []) - doc_map.cache_all!($stderr) - node_pin = doc_map.pins.find { |pin| pin.path == 'AST::Node' } - expect(node_pin).to be_a(Solargraph::Pin::Namespace) + let(:out) { StringIO.new } + let(:pre_cache) { true } + let(:requires) { [] } + + let(:workspace) do + Solargraph::Workspace.new(Dir.pwd) end - it 'tracks unresolved requires' do - doc_map = Solargraph::DocMap.new(['not_a_gem'], []) - expect(doc_map.unresolved_requires).to include('not_a_gem') + let(:plain_doc_map) { described_class.new([], [], workspace, out: nil) } + + before do + doc_map.cache_doc_map_gems!(nil) if pre_cache end - it 'tracks uncached_gemspecs' do - gemspec = Gem::Specification.new do |spec| - spec.name = 'not_a_gem' - spec.version = '1.0.0' + context 'with a require in solargraph test bundle' do + let(:requires) do + ['ast'] + end + + it 'generates pins from gems' do + node_pin = doc_map.pins.find { |pin| pin.path == 'AST::Node' } + expect(node_pin).to be_a(Solargraph::Pin::Namespace) end - allow(Gem::Specification).to receive(:find_by_path).and_return(gemspec) - doc_map = Solargraph::DocMap.new(['not_a_gem'], [gemspec]) - expect(doc_map.uncached_yard_gemspecs).to eq([gemspec]) - expect(doc_map.uncached_rbs_collection_gemspecs).to eq([gemspec]) end - it 'imports all gems when bundler/require used' do - workspace = Solargraph::Workspace.new(Dir.pwd) - plain_doc_map = Solargraph::DocMap.new([], [], workspace) - doc_map_with_bundler_require = Solargraph::DocMap.new(['bundler/require'], [], workspace) + context 'when deserialization takes a while' do + let(:pre_cache) { false } + let(:requires) { ['backport'] } - expect(doc_map_with_bundler_require.pins.length - plain_doc_map.pins.length).to be_positive + before do + # proxy this method to simulate a long-running deserialization + allow(Benchmark).to receive(:measure) do |&block| + block.call + 5.0 + end + end + + it 'logs timing' do + # force lazy evaluation + _pins = doc_map.pins + expect(out.string).to include('Deserialized ').and include(' gem pins ').and include(' ms') + end end - 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/) - Solargraph::DocMap.new(['set'], []) + context 'with an uncached but valid gemspec' do + let(:requires) { ['uncached_gem'] } + let(:pre_cache) { false } + let(:workspace) { instance_double(Solargraph::Workspace) } + + it 'tracks uncached_gemspecs' do + pincache = instance_double(Solargraph::PinCache) + uncached_gemspec = Gem::Specification.new('uncached_gem', '1.0.0') + allow(workspace).to receive_messages(fresh_pincache: pincache) + allow(Gem::Specification).to receive(:find_by_path).with('uncached_gem').and_return(uncached_gemspec) + allow(workspace).to receive(:global_environ).and_return(Solargraph::Environ.new) + allow(pincache).to receive(:deserialize_combined_pin_cache).with(uncached_gemspec).and_return(nil) + expect(doc_map.uncached_gemspecs).to eq([uncached_gemspec]) + end end - it 'ignores nil requires' do - expect { Solargraph::DocMap.new([nil], []) }.not_to raise_error + context 'with require as bundle/require' do + it 'imports all gems when bundler/require used' do + doc_map_with_bundler_require = described_class.new(['bundler/require'], [], workspace, out: nil) + doc_map_with_bundler_require.cache_doc_map_gems!(nil) + expect(doc_map_with_bundler_require.pins.length - plain_doc_map.pins.length).to be_positive + end end - it 'ignores empty requires' do - expect { Solargraph::DocMap.new([''], []) }.not_to raise_error + context 'with a require not needed by Ruby core' do + let(:requires) { ['set'] } + + it 'does not warn' 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. + allow(Solargraph.logger).to receive(:warn) + doc_map + expect(Solargraph.logger).not_to have_received(:warn).with(/path set/) + end end - it 'collects dependencies' do - doc_map = Solargraph::DocMap.new(['rspec'], []) - expect(doc_map.dependencies.map(&:name)).to include('rspec-core') + context 'with a nil require' do + let(:requires) { [nil] } + + it 'does not raise error' do + expect { doc_map }.not_to raise_error + end end - it 'includes convention requires from environ' do - dummy_convention = Class.new(Solargraph::Convention::Base) do - def global(doc_map) - Solargraph::Environ.new( - requires: ['convention_gem1', 'convention_gem2'] - ) - end + context 'with an empty require' do + let(:requires) { [''] } + + it 'does not raise error' do + expect { doc_map }.not_to raise_error end + end - Solargraph::Convention.register dummy_convention + context 'with convention' do + let(:pre_cache) { false } - doc_map = Solargraph::DocMap.new(['original_gem'], []) + it 'includes convention requires from environ' do + dummy_convention = Class.new(Solargraph::Convention::Base) do + def global(doc_map) + Solargraph::Environ.new( + requires: ['convention_gem1', 'convention_gem2'] + ) + end + end - expect(doc_map.requires).to include('original_gem', 'convention_gem1', 'convention_gem2') + Solargraph::Convention.register dummy_convention - # Clean up the registered convention - Solargraph::Convention.deregister dummy_convention + doc_map = Solargraph::DocMap.new(['original_gem'], [], workspace) + + expect(doc_map.requires).to include('original_gem', 'convention_gem1', 'convention_gem2') + ensure + # Clean up the registered convention + Solargraph::Convention.deregister dummy_convention + end end end diff --git a/spec/gem_pins_spec.rb b/spec/gem_pins_spec.rb index d630784cf..4d2bb4ff5 100644 --- a/spec/gem_pins_spec.rb +++ b/spec/gem_pins_spec.rb @@ -1,14 +1,49 @@ # frozen_string_literal: true describe Solargraph::GemPins do - it 'can merge YARD and RBS' do - gemspec = Gem::Specification.find_by_name('rbs') - yard_pins = Solargraph::GemPins.build_yard_pins([], gemspec) - rbs_map = Solargraph::RbsMap.from_gemspec(gemspec, nil, nil) - pins = Solargraph::GemPins.combine yard_pins, rbs_map.pins - - core_root = pins.find { |pin| pin.path == 'RBS::EnvironmentLoader#core_root' } - expect(core_root.return_type.to_s).to eq('Pathname, nil') - expect(core_root.location.filename).to end_with('environment_loader.rb') + let(:workspace) { Solargraph::Workspace.new(Dir.pwd) } + let(:doc_map) { Solargraph::DocMap.new(requires, [], workspace, out: nil) } + let(:pin) { doc_map.pins.find { |pin| pin.path == path } } + + before do + doc_map.cache_doc_map_gems!(STDERR) # rubocop:disable Style/GlobalStdStream + end + + context 'with a combined method pin' do + let(:path) { 'RBS::EnvironmentLoader#core_root' } + let(:requires) { ['rbs'] } + + it 'can merge YARD and RBS' do + expect(pin.source).to eq(:combined) + end + + it 'finds types from RBS' do + expect(pin.return_type.to_s).to eq('Pathname, nil') + end + + it 'finds locations from YARD' do + expect(pin.location.filename).to end_with('environment_loader.rb') + end + end + + context 'with a YARD-only pin' do + let(:requires) { ['rake'] } + let(:path) { 'Rake::Task#prerequisites' } + + it 'found a pin' do + expect(pin.source).not_to be_nil + end + + it 'can merge YARD and RBS' do + expect(pin.source).to eq(:yardoc) + end + + it 'does not find types from YARD in this case' do + expect(pin.return_type.to_s).to eq('undefined') + end + + it 'finds locations from YARD' do + expect(pin.location.filename).to end_with('task.rb') + end end end diff --git a/spec/language_server/host/diagnoser_spec.rb b/spec/language_server/host/diagnoser_spec.rb index d59a843f1..69ee0b866 100644 --- a/spec/language_server/host/diagnoser_spec.rb +++ b/spec/language_server/host/diagnoser_spec.rb @@ -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 diff --git a/spec/language_server/host/message_worker_spec.rb b/spec/language_server/host/message_worker_spec.rb index b9ce2a41f..f7c17cb6d 100644 --- a/spec/language_server/host/message_worker_spec.rb +++ b/spec/language_server/host/message_worker_spec.rb @@ -2,7 +2,7 @@ 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) diff --git a/spec/library_spec.rb b/spec/library_spec.rb index 34de9e1f0..f7daafdf4 100644 --- a/spec/library_spec.rb +++ b/spec/library_spec.rb @@ -26,6 +26,32 @@ expect(completion.pins.map(&:name)).to include('x') end + context 'with a require from a not-yet-cached external gem' do + before do + Solargraph::Shell.new.uncache('backport') + end + + it "returns a Completion", time_limit_seconds: 50 do + library = Solargraph::Library.new(Solargraph::Workspace.new(Dir.pwd, + Solargraph::Workspace::Config.new)) + library.attach Solargraph::Source.load_string(%( + require 'backport' + + # @param adapter [Backport::Adapter] + def foo(adapter) + adapter.remo + end + ), 'file.rb', 0) + completion = nil + # give Solargraph time to cache the gem + while (completion = library.completions_at('file.rb', 5, 19)).pins.empty? + sleep 0.25 + end + expect(completion).to be_a(Solargraph::SourceMap::Completion) + expect(completion.pins.map(&:name)).to include('remote') + end + end + context 'with a require from an already-cached external gem' do before do Solargraph::Shell.new.gems('backport') @@ -161,10 +187,47 @@ def bar expect(pins.map(&:path)).to include('Foo#bar') end - it "collects references to an instance method symbol" do - workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) - src1 = Solargraph::Source.load_string(%( + describe '#references_from' do + it "collects references to a new method on a constant from assignment of Class.new" do + workspace = Solargraph::Workspace.new('*') + library = Solargraph::Library.new(workspace) + src1 = Solargraph::Source.load_string(%( + Foo.new + ), 'file1.rb', 0) + library.merge src1 + src2 = Solargraph::Source.load_string(%( + Foo = Class.new + ), 'file2.rb', 0) + library.merge src2 + library.catalog + locs = library.references_from('file1.rb', 1, 12) + expect(locs.map { |l| [l.filename, l.range.start.line] }) + .to eq([["file1.rb", 1]]) + end + + it "collects references to a new method to a constant from assignment" do + workspace = Solargraph::Workspace.new('*') + library = Solargraph::Library.new(workspace) + src1 = Solargraph::Source.load_string(%( + Foo.new + ), 'file1.rb', 0) + library.merge src1 + src2 = Solargraph::Source.load_string(%( + class Foo + end + blah = Foo.new + ), 'file2.rb', 0) + library.merge src2 + library.catalog + locs = library.references_from('file2.rb', 3, 21) + expect(locs.map { |l| [l.filename, l.range.start.line] }) + .to eq([["file1.rb", 1], ["file2.rb", 3]]) + end + + it "collects references to an instance method symbol" do + workspace = Solargraph::Workspace.new('*') + library = Solargraph::Library.new(workspace) + src1 = Solargraph::Source.load_string(%( class Foo def bar end @@ -172,8 +235,8 @@ def bar Foo.new.bar ), 'file1.rb', 0) - library.merge src1 - src2 = Solargraph::Source.load_string(%( + library.merge src1 + src2 = Solargraph::Source.load_string(%( foo = Foo.new foo.bar class Other @@ -181,17 +244,17 @@ def bar; end end Other.new.bar ), 'file2.rb', 0) - library.merge src2 - library.catalog - locs = library.references_from('file2.rb', 2, 11) - expect(locs.length).to eq(3) - expect(locs.select{|l| l.filename == 'file2.rb' && l.range.start.line == 6}).to be_empty - end + library.merge src2 + library.catalog + locs = library.references_from('file2.rb', 2, 11) + expect(locs.length).to eq(3) + expect(locs.select{|l| l.filename == 'file2.rb' && l.range.start.line == 6}).to be_empty + end - it "collects references to a class method symbol" do - workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) - src1 = Solargraph::Source.load_string(%( + it "collects references to a class method symbol" do + workspace = Solargraph::Workspace.new('*') + library = Solargraph::Library.new(workspace) + src1 = Solargraph::Source.load_string(%( class Foo def self.bar end @@ -203,8 +266,8 @@ def bar Foo.bar Foo.new.bar ), 'file1.rb', 0) - library.merge src1 - src2 = Solargraph::Source.load_string(%( + library.merge src1 + src2 = Solargraph::Source.load_string(%( Foo.bar Foo.new.bar class Other @@ -214,48 +277,48 @@ def bar; end Other.bar Other.new.bar ), 'file2.rb', 0) - library.merge src2 - library.catalog - locs = library.references_from('file2.rb', 1, 11) - expect(locs.length).to eq(3) - expect(locs.select{|l| l.filename == 'file1.rb' && l.range.start.line == 2}).not_to be_empty - expect(locs.select{|l| l.filename == 'file1.rb' && l.range.start.line == 9}).not_to be_empty - expect(locs.select{|l| l.filename == 'file2.rb' && l.range.start.line == 1}).not_to be_empty - end + library.merge src2 + library.catalog + locs = library.references_from('file2.rb', 1, 11) + expect(locs.length).to eq(3) + expect(locs.select{|l| l.filename == 'file1.rb' && l.range.start.line == 2}).not_to be_empty + expect(locs.select{|l| l.filename == 'file1.rb' && l.range.start.line == 9}).not_to be_empty + expect(locs.select{|l| l.filename == 'file2.rb' && l.range.start.line == 1}).not_to be_empty + end - it "collects stripped references to constant symbols" do - workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) - src1 = Solargraph::Source.load_string(%( + it "collects stripped references to constant symbols" do + workspace = Solargraph::Workspace.new('*') + library = Solargraph::Library.new(workspace) + src1 = Solargraph::Source.load_string(%( class Foo def bar end end Foo.new.bar ), 'file1.rb', 0) - library.merge src1 - src2 = Solargraph::Source.load_string(%( + library.merge src1 + src2 = Solargraph::Source.load_string(%( class Other foo = Foo.new foo.bar end ), 'file2.rb', 0) - library.merge src2 - library.catalog - locs = library.references_from('file1.rb', 1, 12, strip: true) - expect(locs.length).to eq(3) - locs.each do |l| - code = library.read_text(l.filename) - o1 = Solargraph::Position.to_offset(code, l.range.start) - o2 = Solargraph::Position.to_offset(code, l.range.ending) - expect(code[o1..o2-1]).to eq('Foo') + library.merge src2 + library.catalog + locs = library.references_from('file1.rb', 1, 12, strip: true) + expect(locs.length).to eq(3) + locs.each do |l| + code = library.read_text(l.filename) + o1 = Solargraph::Position.to_offset(code, l.range.start) + o2 = Solargraph::Position.to_offset(code, l.range.ending) + expect(code[o1..o2-1]).to eq('Foo') + end end - end - it 'rejects new references from different classes' do - workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) - source = Solargraph::Source.load_string(%( + it 'rejects new references from different classes' do + workspace = Solargraph::Workspace.new('*') + library = Solargraph::Library.new(workspace) + source = Solargraph::Source.load_string(%( class Foo def bar end @@ -263,106 +326,131 @@ def bar Foo.new Array.new ), 'test.rb') - library.merge source - library.catalog - foo_new_locs = library.references_from('test.rb', 5, 10) - expect(foo_new_locs).to eq([Solargraph::Location.new('test.rb', Solargraph::Range.from_to(5, 10, 5, 13))]) - obj_new_locs = library.references_from('test.rb', 6, 12) - expect(obj_new_locs).to eq([Solargraph::Location.new('test.rb', Solargraph::Range.from_to(6, 12, 6, 15))]) - end + library.merge source + library.catalog + foo_new_locs = library.references_from('test.rb', 5, 10) + expect(foo_new_locs).to eq([Solargraph::Location.new('test.rb', Solargraph::Range.from_to(5, 10, 5, 13))]) + obj_new_locs = library.references_from('test.rb', 6, 12) + expect(obj_new_locs).to eq([Solargraph::Location.new('test.rb', Solargraph::Range.from_to(6, 12, 6, 15))]) + end - it "searches the core for queries" do - library = Solargraph::Library.new - result = library.search('String') - expect(result).not_to be_empty - end + it "searches the core for queries" do + library = Solargraph::Library.new + result = library.search('String') + expect(result).not_to be_empty + end - it "returns YARD documentation from the core" do - library = Solargraph::Library.new - api_map, result = library.document('String') - expect(result).not_to be_empty - expect(result.first).to be_a(Solargraph::Pin::Base) - end + it "returns YARD documentation from the core" do + library = Solargraph::Library.new + api_map, result = library.document('String') + expect(result).not_to be_empty + expect(result.first).to be_a(Solargraph::Pin::Base) + end - it "returns YARD documentation from sources" do - library = Solargraph::Library.new - src = Solargraph::Source.load_string(%( + it "returns YARD documentation from sources" do + library = Solargraph::Library.new + src = Solargraph::Source.load_string(%( class Foo # My bar method def bar; end end ), 'test.rb', 0) - library.attach src - api_map, result = library.document('Foo#bar') - expect(result).not_to be_empty - expect(result.first).to be_a(Solargraph::Pin::Base) - end + library.attach src + api_map, result = library.document('Foo#bar') + expect(result).not_to be_empty + expect(result.first).to be_a(Solargraph::Pin::Base) + end - it "synchronizes sources from updaters" do - library = Solargraph::Library.new - src = Solargraph::Source.load_string(%( + it "synchronizes sources from updaters" do + library = Solargraph::Library.new + src = Solargraph::Source.load_string(%( class Foo end ), 'test.rb', 1) - library.attach src - repl = %( + library.attach src + repl = %( class Foo def bar; end end ) - updater = Solargraph::Source::Updater.new( - 'test.rb', - 2, - [Solargraph::Source::Change.new(nil, repl)] - ) - library.attach src.synchronize(updater) - expect(library.current.code).to eq(repl) - end + updater = Solargraph::Source::Updater.new( + 'test.rb', + 2, + [Solargraph::Source::Change.new(nil, repl)] + ) + library.attach src.synchronize(updater) + expect(library.current.code).to eq(repl) + end - it "finds unique references" do - library = Solargraph::Library.new(Solargraph::Workspace.new('*')) - src1 = Solargraph::Source.load_string(%( + it "finds unique references" do + library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + src1 = Solargraph::Source.load_string(%( class Foo end ), 'src1.rb', 1) - library.merge src1 - src2 = Solargraph::Source.load_string(%( + library.merge src1 + src2 = Solargraph::Source.load_string(%( foo = Foo.new ), 'src2.rb', 1) - library.merge src2 - library.catalog - refs = library.references_from('src2.rb', 1, 12) - expect(refs.length).to eq(2) - end + library.merge src2 + library.catalog + refs = library.references_from('src2.rb', 1, 12) + expect(refs.length).to eq(2) + end - it "includes method parameters in references" do - library = Solargraph::Library.new(Solargraph::Workspace.new('*')) - source = Solargraph::Source.load_string(%( + it "includes method parameters in references" do + library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + source = Solargraph::Source.load_string(%( class Foo def bar(baz) baz.upcase end end ), 'test.rb', 1) - library.attach source - from_def = library.references_from('test.rb', 2, 16) - expect(from_def.length).to eq(2) - from_ref = library.references_from('test.rb', 3, 10) - expect(from_ref.length).to eq(2) - end + library.attach source + from_def = library.references_from('test.rb', 2, 16) + expect(from_def.length).to eq(2) + from_ref = library.references_from('test.rb', 3, 10) + expect(from_ref.length).to eq(2) + end - it "includes block parameters in references" do - library = Solargraph::Library.new(Solargraph::Workspace.new('*')) - source = Solargraph::Source.load_string(%( + it "lies about names when client can't handle the truth" do + library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + source = Solargraph::Source.load_string(%( + class Foo + def 🤦🏻foo♀️; 123; end + end + ), 'test.rb', 1) + library.attach source + from_def = library.references_from('test.rb', 2, 16, strip: true) + expect(from_def.first.range.start.column).to eq(14) + end + + it "tells the truth about names when client can handle the truth" do + library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + source = Solargraph::Source.load_string(%( + class Foo + def 🤦🏻foo♀️; 123; end + end + ), 'test.rb', 1) + library.attach source + from_def = library.references_from('test.rb', 2, 16, strip: false) + expect(from_def.first.range.start.column).to eq(12) + end + + it "includes block parameters in references" do + library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + source = Solargraph::Source.load_string(%( 100.times do |foo| puts foo end ), 'test.rb', 1) - library.attach source - from_def = library.references_from('test.rb', 1, 20) - expect(from_def.length).to eq(2) - from_ref = library.references_from('test.rb', 2, 13) - expect(from_ref.length).to eq(2) + library.attach source + from_def = library.references_from('test.rb', 1, 20) + expect(from_def.length).to eq(2) + from_ref = library.references_from('test.rb', 2, 13) + expect(from_ref.length).to eq(2) + end end it 'defines YARD tags' do diff --git a/spec/pin_cache_spec.rb b/spec/pin_cache_spec.rb new file mode 100644 index 000000000..0a11686f5 --- /dev/null +++ b/spec/pin_cache_spec.rb @@ -0,0 +1,197 @@ +# frozen_string_literal: true + +require 'bundler' +require 'benchmark' + +describe Solargraph::PinCache do + subject(:pin_cache) do + described_class.new(rbs_collection_path: '.gem_rbs_collection', + rbs_collection_config_path: 'rbs_collection.yaml', + directory: Dir.pwd, + yard_plugins: ['activesupport-concern']) + end + + describe '#cached?' do + it 'returns true for a gem that is cached' do + allow(File).to receive(:file?).with(%r{.*stdlib/backport.ser$}).and_return(false) + allow(File).to receive(:file?).with(%r{.*combined/.*/backport-.*.ser$}).and_return(true) + + gemspec = Gem::Specification.find_by_name('backport') + expect(pin_cache.cached?(gemspec)).to be true + end + + it 'returns false for a gem that is not cached' do + gemspec = Gem::Specification.new.tap do |spec| + spec.name = 'nonexistent' + spec.version = '0.0.1' + end + expect(pin_cache.cached?(gemspec)).to be false + end + end + + describe '.core?' do + it 'returns true when core pins exist' do + allow(File).to receive(:file?).with(%r{.*/core.ser$}).and_return(true) + + expect(described_class.core?).to be true + end + + it "returns true when core pins don't" do + allow(File).to receive(:file?).with(%r{.*/core.ser$}).and_return(false) + + expect(described_class.core?).to be false + end + end + + describe '#possible_stdlibs' do + it 'is tolerant of less usual Ruby installations' do + stub_const('Gem::RUBYGEMS_DIR', nil) + + expect(pin_cache.possible_stdlibs).to eq([]) + end + end + + describe '#cache_all_stdlibs' do + it 'creates stdlibmaps' do + allow(Solargraph::RbsMap::StdlibMap).to receive(:new).and_return(instance_double(Solargraph::RbsMap::StdlibMap)) + + pin_cache.cache_all_stdlibs + + expect(Solargraph::RbsMap::StdlibMap).to have_received(:new).at_least(:once) + end + end + + describe '#cache_gem' do + context 'with an already in-memory gem' do + let(:backport_gemspec) { Gem::Specification.find_by_name('backport') } + + before do + pin_cache.cache_gem(gemspec: backport_gemspec, out: nil) + end + + it 'does not load the gem again' do + allow(Marshal).to receive(:load).and_call_original + + pin_cache.cache_gem(gemspec: backport_gemspec, out: nil) + + expect(Marshal).not_to have_received(:load).with(anything) + end + end + + context 'with the parser gem' do + before do + Solargraph::Shell.new.uncache('parser') + allow(Solargraph::Yardoc).to receive(:build_docs) + end + + it 'chooses not to use YARD' do + parser_gemspec = Gem::Specification.find_by_name('parser') + pin_cache.cache_gem(gemspec: parser_gemspec, out: nil) + # if this fails, you may not have run `bundle exec rbs collection update` + expect(Solargraph::Yardoc).not_to have_received(:build_docs).with(any_args) + end + end + + context 'with an installed gem' do + before do + Solargraph::Shell.new.gems('kramdown') + end + + it 'uncaches when asked' do + gemspec = Gem::Specification.find_by_name('kramdown') + expect do + pin_cache.uncache_gem(gemspec, out: nil) + end.not_to raise_error + end + end + + context 'with the rebuild flag' do + before do + allow(Solargraph::Yardoc).to receive(:build_docs) + end + + it 'chooses not to use YARD' do + parser_gemspec = Gem::Specification.find_by_name('parser') + pin_cache.cache_gem(gemspec: parser_gemspec, rebuild: true, out: nil) + # if this fails, you may not have run `bundle exec rbs collection update` + expect(Solargraph::Yardoc).not_to have_received(:build_docs).with(any_args) + end + end + + context 'with a stdlib gem' do + let(:gem_name) { 'logger' } + + before do + Solargraph::Shell.new.uncache(gem_name) + end + + it 'caches' do + yaml_gemspec = Gem::Specification.find_by_name(gem_name) + allow(File).to receive(:write).and_call_original + + pin_cache.cache_gem(gemspec: yaml_gemspec, out: nil) + + # match arguments with regexp using rspec-matchers syntax + expect(File).to have_received(:write).with(%r{combined/.*/logger-.*-stdlib.ser$}, any_args).once + end + end + + context 'with gem packaged with its own RBS gem' do + let(:gem_name) { 'base64' } + + before do + Solargraph::Shell.new.uncache(gem_name) + end + + it 'caches' do + yaml_gemspec = Gem::Specification.find_by_name(gem_name) + allow(File).to receive(:write).and_call_original + + pin_cache.cache_gem(gemspec: yaml_gemspec, out: nil) + + # match arguments with regexp using rspec-matchers syntax + expect(File).to have_received(:write).with(%r{combined/.*/base64-.*-export.ser$}, any_args, mode: 'wb').once + end + end + end + + describe '#uncache_gem' do + subject(:call) { pin_cache.uncache_gem(gemspec, out: out) } + + let(:out) { StringIO.new } + + before do + allow(FileUtils).to receive(:rm_rf) + end + + context 'with an already cached gem' do + let(:gemspec) { Gem::Specification.find_by_name('backport') } + + it 'deletes files' do + call + + expect(FileUtils).to have_received(:rm_rf).at_least(:once) + end + end + + context 'with a non-existent gem' do + let(:gemspec) { instance_double(Gem::Specification, name: 'nonexistent', version: '0.0.1') } + + it 'does not raise an error' do + expect { call }.not_to raise_error + end + + it 'logs a message' do + call + + expect(out.string).to include('does not exist') + end + + it 'does not delete files' do + call + + expect(FileUtils).not_to have_received(:rm_rf) + end + end + end +end diff --git a/spec/rbs_map_spec.rb b/spec/rbs_map_spec.rb index b06c975d1..f3ca90a36 100644 --- a/spec/rbs_map_spec.rb +++ b/spec/rbs_map_spec.rb @@ -3,7 +3,18 @@ spec = Gem::Specification.find_by_name('rbs') rbs_map = Solargraph::RbsMap.from_gemspec(spec, nil, nil) pin = rbs_map.path_pin('RBS::EnvironmentLoader#add_collection') - expect(pin).to be + expect(pin).not_to be_nil + end + + it 'fails if it does not find data from gemspec' do + spec = Gem::Specification.find_by_name('backport') + rbs_map = Solargraph::RbsMap.from_gemspec(spec, nil, nil) + expect(rbs_map).not_to be_resolved + end + + it 'fails if it does not find data from name' do + rbs_map = Solargraph::RbsMap.new('lskdflaksdfjl') + expect(rbs_map.pins).to be_empty end it 'converts constants and aliases to correct types' do diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index 91f84b4c7..6e2a83074 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: true + require 'tmpdir' require 'open3' describe Solargraph::Shell do + let(:shell) { described_class.new } let(:temp_dir) { Dir.mktmpdir } before do @@ -25,20 +28,122 @@ def bundle_exec(*cmd) FileUtils.rm_rf(temp_dir) end - describe "--version" do - it "returns a version when run" do - output = bundle_exec("solargraph", "--version") + describe '--version' do + let(:output) { bundle_exec('solargraph', '--version') } + it 'returns output' do expect(output).not_to be_empty + end + + it 'returns a version when run' do expect(output).to eq("#{Solargraph::VERSION}\n") end end - describe "uncache" do - it "uncaches without erroring out" do - output = bundle_exec("solargraph", "uncache", "solargraph") + describe 'uncache' do + it 'uncaches without erroring out' do + output = capture_stdout do + shell.uncache('backport') + end expect(output).to include('Clearing pin cache in') end + + it 'uncaches stdlib without erroring out' do + expect { shell.uncache('stdlib') }.not_to raise_error + end + + it 'uncaches core without erroring out' do + expect { shell.uncache('core') }.not_to raise_error + end + end + + describe 'scan' do + context 'with mocked dependencies' do + let(:api_map) { instance_double(Solargraph::ApiMap) } + + before do + allow(Solargraph::ApiMap).to receive(:load_with_cache).and_return(api_map) + end + + it 'scans without erroring out' do + allow(api_map).to receive(:pins).and_return([]) + output = capture_stdout do + shell.options = { directory: 'spec/fixtures/workspace' } + shell.scan + end + + expect(output).to include('Scanned ').and include(' seconds.') + end + end + end + + describe 'typecheck' do + context 'with mocked dependencies' do + let(:type_checker) { instance_double(Solargraph::TypeChecker) } + let(:api_map) { instance_double(Solargraph::ApiMap) } + + before do + allow(Solargraph::ApiMap).to receive(:load_with_cache).and_return(api_map) + allow(Solargraph::TypeChecker).to receive(:new).and_return(type_checker) + allow(type_checker).to receive(:problems).and_return([]) + end + + it 'typechecks without erroring out' do + output = capture_stdout do + shell.options = { level: 'normal', directory: '.' } + shell.typecheck('Gemfile') + end + + expect(output).to include('Typecheck finished in') + end + end + end + + describe 'gems' do + context 'without mocked ApiMap' do + it 'complains when gem does not exist' do + output = capture_both do + shell.gems('nonexistentgem') + end + + expect(output).to include("Gem 'nonexistentgem' not found") + end + + it 'caches core without erroring out' do + pending('https://github.com/castwide/solargraph/pull/1061') + + capture_both do + shell.uncache('core') + end + + expect { shell.cache('core') }.not_to raise_error + end + + it 'gives sensible error for gem that does not exist' do + output = capture_both do + shell.gems('solargraph123') + end + + expect(output).to include("Gem 'solargraph123' not found") + end + end + end + + describe 'cache' do + it 'caches a stdlib gem without erroring out' do + expect { shell.cache('stringio') }.not_to raise_error + end + + context 'when gem does not exist' do + subject(:call) { shell.cache('nonexistentgem8675309') } + + it 'gives a good error message' do + pending('https://github.com/castwide/solargraph/pull/1061') + + # capture stderr output + expect { call }.to output(/not found/).to_stderr + end + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 00cc6c8c3..0a0c1dde4 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -26,7 +26,9 @@ c.example_status_persistence_file_path = 'rspec-examples.txt' end require 'solargraph' -# Suppress logger output in specs (if possible) +# execute any logging blocks to make sure they don't blow up +Solargraph::Logging.logger.sev_threshold = Logger::DEBUG +# ...but still suppress logger output in specs (if possible) if Solargraph::Logging.logger.respond_to?(:reopen) && !ENV.key?('SOLARGRAPH_LOG') Solargraph::Logging.logger.reopen(File::NULL) end @@ -43,3 +45,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 diff --git a/spec/type_checker/levels/normal_spec.rb b/spec/type_checker/levels/normal_spec.rb index 3b38f55d8..0b3024f62 100644 --- a/spec/type_checker/levels/normal_spec.rb +++ b/spec/type_checker/levels/normal_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::TypeChecker do - context 'normal level' do + context 'when checking at normal level' do def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :normal) end @@ -221,9 +221,9 @@ def bar; end # @todo This test uses kramdown-parser-gfm because it's a gem dependency known to # lack typed methods. A better test wouldn't depend on the state of # vendored code. + workspace = Solargraph::Workspace.new(Dir.pwd) gemspec = Gem::Specification.find_by_name('kramdown-parser-gfm') - yard_pins = Solargraph::GemPins.build_yard_pins([], gemspec) - Solargraph::PinCache.serialize_yard_gem(gemspec, yard_pins) + workspace.cache_gem(gemspec) checker = type_checker(%( require 'kramdown-parser-gfm' diff --git a/spec/yard_map/mapper_spec.rb b/spec/yard_map/mapper_spec.rb index 6b00e5c33..3afa3a4ca 100644 --- a/spec/yard_map/mapper_spec.rb +++ b/spec/yard_map/mapper_spec.rb @@ -1,4 +1,14 @@ describe Solargraph::YardMap::Mapper do + before :all do # rubocop:disable RSpec/BeforeAfterAll + @api_map = Solargraph::ApiMap.load('.') + end + + def pins_with require + doc_map = Solargraph::DocMap.new([require], [], @api_map.workspace, out: nil) # rubocop:disable RSpec/InstanceVariable + doc_map.cache_doc_map_gems!(nil) + doc_map.pins + end + it 'converts nil docstrings to empty strings' do dir = File.absolute_path(File.join('spec', 'fixtures', 'yard_map')) Dir.chdir dir do @@ -14,50 +24,33 @@ it 'marks explicit methods' do # Using rspec-expectations because it's a known dependency - rspec = Gem::Specification.find_by_name('rspec-expectations') - Solargraph::Yardoc.cache([], rspec) - Solargraph::Yardoc.load!(rspec) - pins = Solargraph::YardMap::Mapper.new(YARD::Registry.all).map - pin = pins.find { |pin| pin.path == 'RSpec::Matchers#be_truthy' } + pin = pins_with('rspec/expectations').find { |pin| pin.path == 'RSpec::Matchers#be_truthy' } + expect(pin).not_to be_nil expect(pin.explicit?).to be(true) end it 'marks correct return type from Logger.new' do # Using logger because it's a known dependency - logger = Gem::Specification.find_by_name('logger') - Solargraph::Yardoc.cache([], logger) - registry = Solargraph::Yardoc.load!(logger) - pins = Solargraph::YardMap::Mapper.new(registry).map - pins = pins.select { |pin| pin.path == 'Logger.new' } + pins = pins_with('logger').select { |pin| pin.path == 'Logger.new' } expect(pins.map(&:return_type).uniq.map(&:to_s)).to eq(['self']) end it 'marks correct return type from RuboCop::Options.new' do # Using rubocop because it's a known dependency - rubocop = Gem::Specification.find_by_name('rubocop') - Solargraph::Yardoc.cache([], rubocop) - Solargraph::Yardoc.load!(rubocop) - pins = Solargraph::YardMap::Mapper.new(YARD::Registry.all).map - pins = pins.select { |pin| pin.path == 'RuboCop::Options.new' } + pins = pins_with('rubocop').select { |pin| pin.path == 'RuboCop::Options.new' } expect(pins.map(&:return_type).uniq.map(&:to_s)).to eq(['self']) expect(pins.flat_map(&:signatures).map(&:return_type).uniq.map(&:to_s)).to eq(['self']) end it 'marks non-explicit methods' do # Using rspec-expectations because it's a known dependency - rspec = Gem::Specification.find_by_name('rspec-expectations') - Solargraph::Yardoc.load!(rspec) - pins = Solargraph::YardMap::Mapper.new(YARD::Registry.all).map - pin = pins.find { |pin| pin.path == 'RSpec::Matchers#expect' } + pin = pins_with('rspec/expectations').find { |pin| pin.path == 'RSpec::Matchers#expect' } expect(pin.explicit?).to be(false) end it 'adds superclass references' do # Asssuming the yard gem exists because it's a known dependency - gemspec = Gem::Specification.find_by_name('yard') - Solargraph::Yardoc.cache([], gemspec) - pins = Solargraph::YardMap::Mapper.new(Solargraph::Yardoc.load!(gemspec)).map - pin = pins.find do |pin| + pin = pins_with('yard').find do |pin| pin.is_a?(Solargraph::Pin::Reference::Superclass) && pin.name == 'YARD::CodeObjects::NamespaceObject' end expect(pin.closure.path).to eq('YARD::CodeObjects::ClassObject') @@ -65,10 +58,7 @@ it 'adds include references' do # Asssuming the ast gem exists because it's a known dependency - gemspec = Gem::Specification.find_by_name('ast') - Solargraph::Yardoc.cache([], gemspec) - pins = Solargraph::YardMap::Mapper.new(Solargraph::Yardoc.load!(gemspec)).map - inc= pins.find do |pin| + inc = pins_with('ast').find do |pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.name == 'AST::Processor::Mixin' && pin.closure.path == 'AST::Processor' end expect(inc).to be_a(Solargraph::Pin::Reference::Include) @@ -76,10 +66,7 @@ it 'adds extend references' do # Asssuming the yard gem exists because it's a known dependency - gemspec = Gem::Specification.find_by_name('yard') - Solargraph::Yardoc.cache([], gemspec) - pins = Solargraph::YardMap::Mapper.new(Solargraph::Yardoc.load!(gemspec)).map - ext = pins.find do |pin| + ext = pins_with('yard').find do |pin| pin.is_a?(Solargraph::Pin::Reference::Extend) && pin.name == 'Enumerable' && pin.closure.path == 'YARD::Registry' end expect(ext).to be_a(Solargraph::Pin::Reference::Extend) diff --git a/spec/yardoc_spec.rb b/spec/yardoc_spec.rb new file mode 100644 index 000000000..2e821498e --- /dev/null +++ b/spec/yardoc_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'tmpdir' +require 'open3' + +describe Solargraph::Yardoc do + around do |testobj| + @tmpdir = Dir.mktmpdir + + testobj.run + ensure + FileUtils.remove_entry(@tmpdir) # rubocop:disable RSpec/InstanceVariable + end + + let(:gem_yardoc_path) do + File.join(@tmpdir, 'solargraph', 'yardoc', 'test_gem') # rubocop:disable RSpec/InstanceVariable + end + + before do + FileUtils.mkdir_p(gem_yardoc_path) + end + + describe '#processing?' do + it 'returns true if the yardoc is being processed' do + FileUtils.touch(File.join(gem_yardoc_path, 'processing')) + expect(described_class.processing?(gem_yardoc_path)).to be(true) + end + + it 'returns false if the yardoc is not being processed' do + expect(described_class.processing?(gem_yardoc_path)).to be(false) + end + end + + describe '#load!' do + it 'does not blow up when called on empty directory' do + expect { described_class.load!(gem_yardoc_path) }.not_to raise_error + end + end + + describe '#build_docs' do + let(:workspace) { Solargraph::Workspace.new(Dir.pwd) } + let(:gemspec) { workspace.find_gem('rubocop') } + let(:output) { '' } + + before do + allow(Solargraph.logger).to receive(:warn) + allow(Solargraph.logger).to receive(:info) + FileUtils.rm_rf(gem_yardoc_path) + end + + it 'builds docs for a gem' do + described_class.build_docs(gem_yardoc_path, [], gemspec) + expect(File.exist?(File.join(gem_yardoc_path, 'complete'))).to be true + end + + it 'bails quietly if directory given does not exist' do + allow(File).to receive(:exist?).and_return(false) + + expect do + described_class.build_docs(gem_yardoc_path, [], gemspec) + end.not_to raise_error + end + + it 'is idempotent' do + described_class.build_docs(gem_yardoc_path, [], gemspec) + described_class.build_docs(gem_yardoc_path, [], gemspec) # second time + expect(File.exist?(File.join(gem_yardoc_path, 'complete'))).to be true + end + + context 'with an error from yard' do + before do + allow(Open3).to receive(:capture2e).and_return([output, result]) + end + + let(:result) { instance_double(Process::Status) } + + it 'does not raise on error from yard' do + allow(result).to receive(:success?).and_return(false) + + expect do + described_class.build_docs(gem_yardoc_path, [], gemspec) + end.not_to raise_error + end + end + + context 'when given a relative BUNDLE_GEMFILE path' do + around do |example| + # turn absolute BUNDLE_GEMFILE path into relative + existing_gemfile = ENV.fetch('BUNDLE_GEMFILE', nil) + current_dir = Dir.pwd + # remove prefix current_dir from path + ENV['BUNDLE_GEMFILE'] = existing_gemfile.sub("#{current_dir}/", '') + raise 'could not figure out relative path' if Pathname.new(ENV.fetch('BUNDLE_GEMFILE', nil)).absolute? + example.run + ENV['BUNDLE_GEMFILE'] = existing_gemfile + end + + it 'sends Open3 an absolute path' do + called_with = nil + allow(Open3).to receive(:capture2e) do |*args| + called_with = args + ['output', instance_double(Process::Status, success?: true)] + end + + described_class.build_docs(gem_yardoc_path, [], gemspec) + + expect(called_with[0]['BUNDLE_GEMFILE']).to start_with('/') + end + end + end +end From c7b66cf1ae782dda517f6d262551772b4b19af76 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 20:47:37 -0400 Subject: [PATCH 187/460] Exclude more scenarios that RBS does not support --- .github/workflows/rspec.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 7b2255b0e..4038be21a 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -29,7 +29,15 @@ jobs: rbs-version: '3.9.4' - ruby-version: '3.0' rbs-version: '4.0.0.dev.4' - steps: + # Missing require in 'rbs collection update' - hopefully + # fixed in next RBS dev 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 uses: ruby/setup-ruby@v1 From eb676b70da386a704f0b8cc7b09a5a26e67787d1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 20:49:09 -0400 Subject: [PATCH 188/460] Fix indentation --- .github/workflows/rspec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 4038be21a..4846551ea 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -37,7 +37,7 @@ jobs: rbs-version: '3.9.4' - ruby-version: 'head' rbs-version: '3.6.1' - steps: + steps: - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 From e9e66ed1d141b51fde2cac52c1080a8313f9059c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 21:32:09 -0400 Subject: [PATCH 189/460] Update rubocop todo --- .rubocop_todo.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2ebedbf98..0b5bb7b22 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1095,6 +1095,7 @@ RSpec/MultipleExpectations: - 'spec/diagnostics/type_check_spec.rb' - 'spec/diagnostics/update_errors_spec.rb' - 'spec/diagnostics_spec.rb' + - 'spec/language_server/host/message_worker_spec.rb' - 'spec/language_server/host_spec.rb' - 'spec/language_server/message/completion_item/resolve_spec.rb' - 'spec/language_server/message/initialize_spec.rb' @@ -1799,7 +1800,6 @@ Style/IfUnlessModifier: - 'lib/solargraph/language_server/message/initialize.rb' - 'lib/solargraph/language_server/message/text_document/completion.rb' - 'lib/solargraph/language_server/message/text_document/hover.rb' - - 'lib/solargraph/parser/parser_gem/class_methods.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/parser/parser_gem/node_methods.rb' - 'lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb' @@ -2558,7 +2558,6 @@ Layout/LineLength: - 'lib/solargraph/source_map/clip.rb' - 'lib/solargraph/source_map/mapper.rb' - 'lib/solargraph/type_checker.rb' - - 'lib/solargraph/workspace.rb' - 'lib/solargraph/workspace/config.rb' - 'lib/solargraph/yard_map/mapper/to_method.rb' - 'spec/api_map_spec.rb' From d04ae87ab0f8f0e656986c1b59b081b4c43b8472 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 21:33:04 -0400 Subject: [PATCH 190/460] Add missing files from merge --- spec/pin_cache_spec.rb | 197 +++++++++++++++++++++++++++++++++++++++++ spec/yardoc_spec.rb | 111 +++++++++++++++++++++++ 2 files changed, 308 insertions(+) create mode 100644 spec/pin_cache_spec.rb create mode 100644 spec/yardoc_spec.rb diff --git a/spec/pin_cache_spec.rb b/spec/pin_cache_spec.rb new file mode 100644 index 000000000..0a11686f5 --- /dev/null +++ b/spec/pin_cache_spec.rb @@ -0,0 +1,197 @@ +# frozen_string_literal: true + +require 'bundler' +require 'benchmark' + +describe Solargraph::PinCache do + subject(:pin_cache) do + described_class.new(rbs_collection_path: '.gem_rbs_collection', + rbs_collection_config_path: 'rbs_collection.yaml', + directory: Dir.pwd, + yard_plugins: ['activesupport-concern']) + end + + describe '#cached?' do + it 'returns true for a gem that is cached' do + allow(File).to receive(:file?).with(%r{.*stdlib/backport.ser$}).and_return(false) + allow(File).to receive(:file?).with(%r{.*combined/.*/backport-.*.ser$}).and_return(true) + + gemspec = Gem::Specification.find_by_name('backport') + expect(pin_cache.cached?(gemspec)).to be true + end + + it 'returns false for a gem that is not cached' do + gemspec = Gem::Specification.new.tap do |spec| + spec.name = 'nonexistent' + spec.version = '0.0.1' + end + expect(pin_cache.cached?(gemspec)).to be false + end + end + + describe '.core?' do + it 'returns true when core pins exist' do + allow(File).to receive(:file?).with(%r{.*/core.ser$}).and_return(true) + + expect(described_class.core?).to be true + end + + it "returns true when core pins don't" do + allow(File).to receive(:file?).with(%r{.*/core.ser$}).and_return(false) + + expect(described_class.core?).to be false + end + end + + describe '#possible_stdlibs' do + it 'is tolerant of less usual Ruby installations' do + stub_const('Gem::RUBYGEMS_DIR', nil) + + expect(pin_cache.possible_stdlibs).to eq([]) + end + end + + describe '#cache_all_stdlibs' do + it 'creates stdlibmaps' do + allow(Solargraph::RbsMap::StdlibMap).to receive(:new).and_return(instance_double(Solargraph::RbsMap::StdlibMap)) + + pin_cache.cache_all_stdlibs + + expect(Solargraph::RbsMap::StdlibMap).to have_received(:new).at_least(:once) + end + end + + describe '#cache_gem' do + context 'with an already in-memory gem' do + let(:backport_gemspec) { Gem::Specification.find_by_name('backport') } + + before do + pin_cache.cache_gem(gemspec: backport_gemspec, out: nil) + end + + it 'does not load the gem again' do + allow(Marshal).to receive(:load).and_call_original + + pin_cache.cache_gem(gemspec: backport_gemspec, out: nil) + + expect(Marshal).not_to have_received(:load).with(anything) + end + end + + context 'with the parser gem' do + before do + Solargraph::Shell.new.uncache('parser') + allow(Solargraph::Yardoc).to receive(:build_docs) + end + + it 'chooses not to use YARD' do + parser_gemspec = Gem::Specification.find_by_name('parser') + pin_cache.cache_gem(gemspec: parser_gemspec, out: nil) + # if this fails, you may not have run `bundle exec rbs collection update` + expect(Solargraph::Yardoc).not_to have_received(:build_docs).with(any_args) + end + end + + context 'with an installed gem' do + before do + Solargraph::Shell.new.gems('kramdown') + end + + it 'uncaches when asked' do + gemspec = Gem::Specification.find_by_name('kramdown') + expect do + pin_cache.uncache_gem(gemspec, out: nil) + end.not_to raise_error + end + end + + context 'with the rebuild flag' do + before do + allow(Solargraph::Yardoc).to receive(:build_docs) + end + + it 'chooses not to use YARD' do + parser_gemspec = Gem::Specification.find_by_name('parser') + pin_cache.cache_gem(gemspec: parser_gemspec, rebuild: true, out: nil) + # if this fails, you may not have run `bundle exec rbs collection update` + expect(Solargraph::Yardoc).not_to have_received(:build_docs).with(any_args) + end + end + + context 'with a stdlib gem' do + let(:gem_name) { 'logger' } + + before do + Solargraph::Shell.new.uncache(gem_name) + end + + it 'caches' do + yaml_gemspec = Gem::Specification.find_by_name(gem_name) + allow(File).to receive(:write).and_call_original + + pin_cache.cache_gem(gemspec: yaml_gemspec, out: nil) + + # match arguments with regexp using rspec-matchers syntax + expect(File).to have_received(:write).with(%r{combined/.*/logger-.*-stdlib.ser$}, any_args).once + end + end + + context 'with gem packaged with its own RBS gem' do + let(:gem_name) { 'base64' } + + before do + Solargraph::Shell.new.uncache(gem_name) + end + + it 'caches' do + yaml_gemspec = Gem::Specification.find_by_name(gem_name) + allow(File).to receive(:write).and_call_original + + pin_cache.cache_gem(gemspec: yaml_gemspec, out: nil) + + # match arguments with regexp using rspec-matchers syntax + expect(File).to have_received(:write).with(%r{combined/.*/base64-.*-export.ser$}, any_args, mode: 'wb').once + end + end + end + + describe '#uncache_gem' do + subject(:call) { pin_cache.uncache_gem(gemspec, out: out) } + + let(:out) { StringIO.new } + + before do + allow(FileUtils).to receive(:rm_rf) + end + + context 'with an already cached gem' do + let(:gemspec) { Gem::Specification.find_by_name('backport') } + + it 'deletes files' do + call + + expect(FileUtils).to have_received(:rm_rf).at_least(:once) + end + end + + context 'with a non-existent gem' do + let(:gemspec) { instance_double(Gem::Specification, name: 'nonexistent', version: '0.0.1') } + + it 'does not raise an error' do + expect { call }.not_to raise_error + end + + it 'logs a message' do + call + + expect(out.string).to include('does not exist') + end + + it 'does not delete files' do + call + + expect(FileUtils).not_to have_received(:rm_rf) + end + end + end +end diff --git a/spec/yardoc_spec.rb b/spec/yardoc_spec.rb new file mode 100644 index 000000000..2e821498e --- /dev/null +++ b/spec/yardoc_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'tmpdir' +require 'open3' + +describe Solargraph::Yardoc do + around do |testobj| + @tmpdir = Dir.mktmpdir + + testobj.run + ensure + FileUtils.remove_entry(@tmpdir) # rubocop:disable RSpec/InstanceVariable + end + + let(:gem_yardoc_path) do + File.join(@tmpdir, 'solargraph', 'yardoc', 'test_gem') # rubocop:disable RSpec/InstanceVariable + end + + before do + FileUtils.mkdir_p(gem_yardoc_path) + end + + describe '#processing?' do + it 'returns true if the yardoc is being processed' do + FileUtils.touch(File.join(gem_yardoc_path, 'processing')) + expect(described_class.processing?(gem_yardoc_path)).to be(true) + end + + it 'returns false if the yardoc is not being processed' do + expect(described_class.processing?(gem_yardoc_path)).to be(false) + end + end + + describe '#load!' do + it 'does not blow up when called on empty directory' do + expect { described_class.load!(gem_yardoc_path) }.not_to raise_error + end + end + + describe '#build_docs' do + let(:workspace) { Solargraph::Workspace.new(Dir.pwd) } + let(:gemspec) { workspace.find_gem('rubocop') } + let(:output) { '' } + + before do + allow(Solargraph.logger).to receive(:warn) + allow(Solargraph.logger).to receive(:info) + FileUtils.rm_rf(gem_yardoc_path) + end + + it 'builds docs for a gem' do + described_class.build_docs(gem_yardoc_path, [], gemspec) + expect(File.exist?(File.join(gem_yardoc_path, 'complete'))).to be true + end + + it 'bails quietly if directory given does not exist' do + allow(File).to receive(:exist?).and_return(false) + + expect do + described_class.build_docs(gem_yardoc_path, [], gemspec) + end.not_to raise_error + end + + it 'is idempotent' do + described_class.build_docs(gem_yardoc_path, [], gemspec) + described_class.build_docs(gem_yardoc_path, [], gemspec) # second time + expect(File.exist?(File.join(gem_yardoc_path, 'complete'))).to be true + end + + context 'with an error from yard' do + before do + allow(Open3).to receive(:capture2e).and_return([output, result]) + end + + let(:result) { instance_double(Process::Status) } + + it 'does not raise on error from yard' do + allow(result).to receive(:success?).and_return(false) + + expect do + described_class.build_docs(gem_yardoc_path, [], gemspec) + end.not_to raise_error + end + end + + context 'when given a relative BUNDLE_GEMFILE path' do + around do |example| + # turn absolute BUNDLE_GEMFILE path into relative + existing_gemfile = ENV.fetch('BUNDLE_GEMFILE', nil) + current_dir = Dir.pwd + # remove prefix current_dir from path + ENV['BUNDLE_GEMFILE'] = existing_gemfile.sub("#{current_dir}/", '') + raise 'could not figure out relative path' if Pathname.new(ENV.fetch('BUNDLE_GEMFILE', nil)).absolute? + example.run + ENV['BUNDLE_GEMFILE'] = existing_gemfile + end + + it 'sends Open3 an absolute path' do + called_with = nil + allow(Open3).to receive(:capture2e) do |*args| + called_with = args + ['output', instance_double(Process::Status, success?: true)] + end + + described_class.build_docs(gem_yardoc_path, [], gemspec) + + expect(called_with[0]['BUNDLE_GEMFILE']).to start_with('/') + end + end + end +end From 737b56eff660fb3ca0a976afa4d61731cf6f505f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 22:25:21 -0400 Subject: [PATCH 191/460] Fix type --- lib/solargraph/library.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index b91dd29c8..80a213568 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -518,7 +518,7 @@ def source_map_external_require_hash # @param source_map [SourceMap] # @return [void] def find_external_requires source_map - # @type [Set] + # @type [Enumerable] new_set = source_map.requires.map(&:name).to_set # return if new_set == source_map_external_require_hash[source_map.filename] _filenames = nil From 92b1a96a3d067eb60662f2d64563d3dc73918517 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 22:40:16 -0400 Subject: [PATCH 192/460] Fix type --- lib/solargraph/library.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 80a213568..abd5eb8d1 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -510,7 +510,7 @@ def pin_cache workspace.pin_cache end - # @return [Hash{String => Set}] + # @return [Hash{String => Enumerable}] def source_map_external_require_hash @source_map_external_require_hash ||= {} end From 8ea210411fce0b2fcb8cf4d0b392a98454a2baad Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 23:02:00 -0400 Subject: [PATCH 193/460] Adjust comment location --- .github/workflows/rspec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 4846551ea..0c83e8434 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -23,8 +23,8 @@ jobs: matrix: ruby-version: ['3.0', '3.1', '3.2', '3.3', '3.4', 'head'] rbs-version: ['3.6.1', '3.9.4', '4.0.0.dev.4'] - # Ruby 3.0 doesn't work with RBS 3.9.4 or 4.0.0.dev.4 exclude: + # Ruby 3.0 doesn't work with RBS 3.9.4 or 4.0.0.dev.4 - ruby-version: '3.0' rbs-version: '3.9.4' - ruby-version: '3.0' From 2b3152e32da43a8fafe3b6435ba6e1035d5a06a8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 23:03:04 -0400 Subject: [PATCH 194/460] Ensure using latest RBS version in undercover --- .github/workflows/rspec.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 0c83e8434..6fe05a9b9 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -74,7 +74,9 @@ jobs: ruby-version: '3.4' bundler-cache: false - name: Install gems - run: bundle install + run: | + bundle install + bundle update rbs # use latest available for this Ruby version - name: Install types run: bundle exec rbs collection update - name: Run tests From b10cdd19a1936c13b504540cda4ce527b4faf0c4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 4 Sep 2025 08:36:01 -0400 Subject: [PATCH 195/460] Drop broken 'namespaces' method These unused methods call into ApiMap::Index#namespaces, which does not exist. --- lib/solargraph/api_map.rb | 7 ------- lib/solargraph/api_map/store.rb | 5 ----- 2 files changed, 12 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index f58633a0c..c44ceea93 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -236,13 +236,6 @@ def keyword_pins store.pins_by_class(Pin::Keyword) end - # An array of namespace names defined in the ApiMap. - # - # @return [Set] - def namespaces - store.namespaces - end - # True if the namespace exists. # # @param name [String] The namespace to match diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index f3e2ed278..a4148f867 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -142,11 +142,6 @@ def namespace_exists?(fqns) fqns_pins(fqns).any? end - # @return [Set] - def namespaces - index.namespaces - end - # @return [Enumerable] def namespace_pins pins_by_class(Solargraph::Pin::Namespace) From 1b644b48452e7875233765bc0f2c40361cf84308 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 4 Sep 2025 09:09:19 -0400 Subject: [PATCH 196/460] Add regression specs --- spec/api_map/index_spec.rb | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 spec/api_map/index_spec.rb diff --git a/spec/api_map/index_spec.rb b/spec/api_map/index_spec.rb new file mode 100644 index 000000000..8afb74759 --- /dev/null +++ b/spec/api_map/index_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +describe Solargraph::ApiMap::Index do + subject(:output_pins) { described_class.new(input_pins).pins } + + describe '#map_overrides' do + let(:foo_class) do + Solargraph::Pin::Namespace.new(name: 'Foo') + end + + let(:foo_initialize) do + init = Solargraph::Pin::Method.new(name: 'initialize', + scope: :instance, + parameters: [], + closure: foo_class) + # no return type specified + param = Solargraph::Pin::Parameter.new(name: 'bar', + closure: init) + init.parameters << param + init + end + + let(:foo_new) do + init = Solargraph::Pin::Method.new(name: 'new', + scope: :class, + parameters: [], + closure: foo_class) + # no return type specified + param = Solargraph::Pin::Parameter.new(name: 'bar', + closure: init) + init.parameters << param + init + end + + let(:foo_override) do + Solargraph::Pin::Reference::Override.from_comment('Foo#initialize', + '@param [String] bar') + end + + let(:input_pins) do + [ + foo_initialize, + foo_new, + foo_override + ] + end + + it 'has a docstring to process on override' do + expect(foo_override.docstring.tags).to be_empty + end + + it 'overrides .new method' do + method_pin = output_pins.find { |pin| pin.path == 'Foo.new' } + first_parameter = method_pin.parameters.first + expect(first_parameter.return_type.tag).to eq('String') + end + + it 'overrides #initialize method in signature' do + method_pin = output_pins.find { |pin| pin.path == 'Foo#initialize' } + first_parameter = method_pin.parameters.first + expect(first_parameter.return_type.tag).to eq('String') + end + end +end From aafaa5d867458d3bb2895387cfc80d067ccd50ad Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 4 Sep 2025 10:49:29 -0400 Subject: [PATCH 197/460] Ensure overrides are handled properly regardless of state of pin --- lib/solargraph/api_map/index.rb | 12 +++++++----- lib/solargraph/pin/callable.rb | 5 +++++ lib/solargraph/pin/parameter.rb | 5 +++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index a5870ff50..8234e4718 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -169,10 +169,13 @@ def map_overrides ovr.tags.each do |tag| pin.docstring.add_tag(tag) redefine_return_type pin, tag - if new_pin - new_pin.docstring.add_tag(tag) - redefine_return_type new_pin, tag - end + pin.reset_generated! + + next unless new_pin + + new_pin.docstring.add_tag(tag) + redefine_return_type new_pin, tag + new_pin.reset_generated! end end end @@ -189,7 +192,6 @@ def redefine_return_type pin, tag pin.signatures.each do |sig| sig.instance_variable_set(:@return_type, ComplexType.try_parse(tag.type)) end - pin.reset_generated! end end end diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 504dd4862..7d9fd69be 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -205,6 +205,11 @@ def arity_matches? arguments, with_block true end + def reset_generated! + super + @parameters.each(&:reset_generated!) + end + # @return [Integer] def mandatory_positional_param_count parameters.count(&:arg?) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 512ee0ead..9f58ec067 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -123,6 +123,11 @@ def full_name end end + def reset_generated! + super + @return_type = nil if @return_type&.undefined? + end + # @return [String] def full full_name + case decl From e2ba56923e5e4e26d997a89447cf8fdd8c6433e8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 4 Sep 2025 11:03:01 -0400 Subject: [PATCH 198/460] Lint fixes --- spec/yard_map/mapper_spec.rb | 2 +- spec/yardoc_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/yard_map/mapper_spec.rb b/spec/yard_map/mapper_spec.rb index 3afa3a4ca..770d9e9ef 100644 --- a/spec/yard_map/mapper_spec.rb +++ b/spec/yard_map/mapper_spec.rb @@ -4,7 +4,7 @@ end def pins_with require - doc_map = Solargraph::DocMap.new([require], [], @api_map.workspace, out: nil) # rubocop:disable RSpec/InstanceVariable + doc_map = Solargraph::DocMap.new([require], [], @api_map.workspace, out: nil) doc_map.cache_doc_map_gems!(nil) doc_map.pins end diff --git a/spec/yardoc_spec.rb b/spec/yardoc_spec.rb index 2e821498e..6e7171afe 100644 --- a/spec/yardoc_spec.rb +++ b/spec/yardoc_spec.rb @@ -9,11 +9,11 @@ testobj.run ensure - FileUtils.remove_entry(@tmpdir) # rubocop:disable RSpec/InstanceVariable + FileUtils.remove_entry(@tmpdir) end let(:gem_yardoc_path) do - File.join(@tmpdir, 'solargraph', 'yardoc', 'test_gem') # rubocop:disable RSpec/InstanceVariable + File.join(@tmpdir, 'solargraph', 'yardoc', 'test_gem') end before do From 5b6a4af7d6474be1cbad660932d95fff6071a098 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 4 Sep 2025 12:46:07 -0400 Subject: [PATCH 199/460] Extract gemspec, bundle and dependency management into its own class --- .rubocop_todo.yml | 31 +---- lib/solargraph/api_map.rb | 30 +++-- lib/solargraph/doc_map.rb | 142 ++------------------- lib/solargraph/workspace.rb | 39 ++++-- lib/solargraph/workspace/gemspecs.rb | 184 +++++++++++++++++++++++++++ spec/doc_map_spec.rb | 23 ++-- spec/workspace_spec.rb | 4 +- 7 files changed, 259 insertions(+), 194 deletions(-) create mode 100644 lib/solargraph/workspace/gemspecs.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b8d123824..0b3e1fd0e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.80.0. +# using RuboCop version 1.80.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -64,7 +64,6 @@ Layout/BlockAlignment: Layout/ClosingHeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment. @@ -98,7 +97,6 @@ Layout/ElseAlignment: # Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines. Layout/EmptyLineBetweenDefs: Exclude: - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/language_server/message/initialize.rb' - 'lib/solargraph/pin/delegated_method.rb' @@ -107,7 +105,6 @@ Layout/EmptyLines: Exclude: - 'lib/solargraph/bench.rb' - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - 'lib/solargraph/language_server/message/initialize.rb' - 'lib/solargraph/pin/delegated_method.rb' @@ -225,7 +222,6 @@ Layout/HashAlignment: Layout/HeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - 'spec/yard_map/mapper/to_method_spec.rb' # This cop supports safe autocorrection (--autocorrect). @@ -920,9 +916,9 @@ RSpec/DescribeClass: - '**/spec/routing/**/*' - '**/spec/system/**/*' - '**/spec/views/**/*' + - 'spec/api_map_method_spec.rb' - 'spec/complex_type_spec.rb' - 'spec/source_map/node_processor_spec.rb' - - 'spec/api_map_method_spec.rb' # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. @@ -1045,7 +1041,6 @@ RSpec/ExampleLength: # DisallowedExamples: works RSpec/ExampleWording: Exclude: - - 'spec/convention/struct_definition_spec.rb' - 'spec/pin/base_spec.rb' - 'spec/pin/method_spec.rb' @@ -1086,7 +1081,6 @@ RSpec/ImplicitExpect: RSpec/InstanceVariable: Exclude: - 'spec/api_map/config_spec.rb' - - 'spec/api_map_spec.rb' - 'spec/diagnostics/require_not_found_spec.rb' - 'spec/language_server/host/dispatch_spec.rb' - 'spec/language_server/host_spec.rb' @@ -2225,12 +2219,6 @@ Style/RedundantFreeze: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/source_map/mapper.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowComments. -Style/RedundantInitialize: - Exclude: - - 'lib/solargraph/rbs_map/core_map.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: @@ -2246,6 +2234,7 @@ Style/RedundantParentheses: - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/parameter.rb' + - 'lib/solargraph/pin/search.rb' - 'lib/solargraph/source.rb' - 'lib/solargraph/type_checker.rb' @@ -2258,24 +2247,13 @@ Style/RedundantRegexpArgument: - 'spec/diagnostics/rubocop_spec.rb' - 'spec/language_server/host_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantRegexpCharacterClass: - Exclude: - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/source_chainer.rb' - # This cop supports safe autocorrection (--autocorrect). Style/RedundantRegexpEscape: Exclude: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/diagnostics/rubocop.rb' - 'lib/solargraph/language_server/uri_helpers.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/parameter.rb' - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source/change.rb' - - 'lib/solargraph/source/cursor.rb' - 'lib/solargraph/source_map/clip.rb' - 'lib/solargraph/source_map/mapper.rb' @@ -2337,7 +2315,7 @@ Style/SafeNavigation: # Configuration parameters: Max. Style/SafeNavigationChainLength: Exclude: - - 'lib/solargraph/doc_map.rb' + - 'lib/solargraph/workspace/gemspecs.rb' # This cop supports unsafe autocorrection (--autocorrect-all). Style/SlicingWithRange: @@ -2638,7 +2616,6 @@ YARD/MismatchName: Exclude: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/gem_pins.rb' - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/language_server/host/dispatch.rb' - 'lib/solargraph/language_server/request.rb' diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index f58633a0c..c2e0967f7 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -22,6 +22,9 @@ class ApiMap # @return [Array] attr_reader :missing_docs + # @return [Solargraph::Workspace::Gemspecs] + attr_reader :gemspecs + # @param pins [Array] def initialize pins: [] @source_map_hash = {} @@ -99,7 +102,7 @@ def catalog bench @doc_map&.uncached_rbs_collection_gemspecs&.any? || @doc_map&.rbs_collection_path != bench.workspace.rbs_collection_path if recreate_docmap - @doc_map = DocMap.new(unresolved_requires, [], bench.workspace) # @todo Implement gem preferences + @doc_map = DocMap.new(unresolved_requires, bench.workspace) # @todo Implement gem preferences @unresolved_requires = @doc_map.unresolved_requires end @cache.clear if store.update(@@core_map.pins, @doc_map.pins, implicit.pins, iced_pins, live_pins) @@ -116,7 +119,7 @@ def catalog bench # @return [DocMap] def doc_map - @doc_map ||= DocMap.new([], []) + @doc_map ||= DocMap.new([], Workspace.new('.')) end # @return [::Array] @@ -212,6 +215,7 @@ class << self # # @param directory [String] # @param out [IO] The output stream for messages + # # @return [ApiMap] def self.load_with_cache directory, out api_map = load(directory) @@ -533,7 +537,8 @@ def get_complex_type_methods complex_type, context = '', internal = false # @param name [String] Method name to look up # @param scope [Symbol] :instance or :class # @param visibility [Array] :public, :protected, and/or :private - # @param preserve_generics [Boolean] + # @param preserve_generics [Boolean] True to preserve any + # unresolved generic parameters, false to erase them # @return [Array] def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private, :protected, :public], preserve_generics: false rooted_type = ComplexType.parse(rooted_tag) @@ -559,7 +564,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] + # @return [Array] def get_path_suggestions path return [] if path.nil? resolve_method_aliases store.get_path_pins(path) @@ -568,7 +573,7 @@ def get_path_suggestions path # Get an array of pins that match the specified path. # # @param path [String] - # @return [Enumerable] + # @return [Array] def get_path_pins path get_path_suggestions(path) end @@ -658,7 +663,7 @@ def bundled? filename # @param sup [String] The superclass # @param sub [String] The subclass # @return [Boolean] - def super_and_sub?(sup, sub) + def super_and_sub? sup, sub fqsup = qualify(sup) cls = qualify(sub) tested = [] @@ -677,7 +682,7 @@ def super_and_sub?(sup, sub) # @param module_ns [String] The module namespace (no type parameters) # # @return [Boolean] - def type_include?(host_ns, module_ns) + def type_include? host_ns, module_ns store.get_includes(host_ns).map { |inc_tag| ComplexType.parse(inc_tag).name }.include?(module_ns) end @@ -695,6 +700,11 @@ def resolve_method_aliases pins, visibility = [:public, :private, :protected] GemPins.combine_method_pins_by_path(with_resolved_aliases) end + # @return [Workspace, nil] + def workspace + @doc_map&.workspace + end + # @param fq_reference_tag [String] A fully qualified whose method should be pulled in # @param namespace_pin [Pin::Base] Namespace pin for the rooted_type # parameter - used to pull generics information @@ -1038,18 +1048,18 @@ def erase_generics(namespace_pin, rooted_type, pins) # @param namespace_pin [Pin::Namespace] # @param rooted_type [ComplexType] - def should_erase_generics_when_done?(namespace_pin, rooted_type) + def should_erase_generics_when_done? namespace_pin, rooted_type has_generics?(namespace_pin) && !can_resolve_generics?(namespace_pin, rooted_type) end # @param namespace_pin [Pin::Namespace] - def has_generics?(namespace_pin) + def has_generics? namespace_pin namespace_pin && !namespace_pin.generics.empty? end # @param namespace_pin [Pin::Namespace] # @param rooted_type [ComplexType] - def can_resolve_generics?(namespace_pin, rooted_type) + def can_resolve_generics? namespace_pin, rooted_type has_generics?(namespace_pin) && !rooted_type.all_params.empty? end end diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 5fe5e03f9..306dcfcf4 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -5,7 +5,10 @@ require 'open3' module Solargraph - # A collection of pins generated from required gems. + # A collection of pins generated from specific 'require' statements + # in code. Multiple can be created per workspace, to represent the + # pins available in different files based on their particular + # 'require' lines. # class DocMap include Logging @@ -14,9 +17,6 @@ class DocMap attr_reader :requires alias required requires - # @return [Array] - attr_reader :preferences - # @return [Array] attr_reader :pins @@ -46,11 +46,10 @@ def uncached_gemspecs attr_reader :environ # @param requires [Array] - # @param preferences [Array] # @param workspace [Workspace, nil] - def initialize(requires, preferences, workspace = nil) + # @param out [IO, nil] output stream for logging + def initialize requires, workspace, out: $stderr @requires = requires.compact - @preferences = preferences.compact @workspace = workspace @rbs_collection_path = workspace&.rbs_collection_path @rbs_collection_config_path = workspace&.rbs_collection_config_path @@ -166,7 +165,7 @@ def yard_plugins # @return [Set] def dependencies - @dependencies ||= (gemspecs.flat_map { |spec| fetch_dependencies(spec) } - gemspecs).to_set + @dependencies ||= (gemspecs.flat_map { |spec| workspace.fetch_dependencies(spec) } - gemspecs).to_set end private @@ -203,12 +202,7 @@ def load_serialized_gem_pins # @return [Hash{String => Array}] def required_gems_map - @required_gems_map ||= requires.to_h { |path| [path, resolve_path_to_gemspecs(path)] } - end - - # @return [Hash{String => Gem::Specification}] - def preference_map - @preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] } + @required_gems_map ||= requires.to_h { |path| [path, workspace.resolve_require(path)] } end # @param gemspec [Gem::Specification] @@ -307,128 +301,8 @@ def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key end end - # @param path [String] - # @return [::Array, nil] - def resolve_path_to_gemspecs path - return nil if path.empty? - return gemspecs_required_from_bundler if path == 'bundler/require' - - # @type [Gem::Specification, nil] - gemspec = Gem::Specification.find_by_path(path) - if gemspec.nil? - gem_name_guess = path.split('/').first - begin - # this can happen when the gem is included via a local path in - # a Gemfile; Gem doesn't try to index the paths in that case. - # - # See if we can make a good guess: - potential_gemspec = Gem::Specification.find_by_name(gem_name_guess) - file = "lib/#{path}.rb" - gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file } - rescue Gem::MissingSpecError - logger.debug { "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}" } - [] - end - end - return nil if gemspec.nil? - [gemspec_or_preference(gemspec)] - end - - # @param gemspec [Gem::Specification] - # @return [Gem::Specification] - def gemspec_or_preference gemspec - return gemspec unless preference_map.key?(gemspec.name) - return gemspec if gemspec.version == preference_map[gemspec.name].version - - change_gemspec_version gemspec, preference_map[by_path.name].version - end - - # @param gemspec [Gem::Specification] - # @param version [Gem::Version] - # @return [Gem::Specification] - def change_gemspec_version gemspec, version - Gem::Specification.find_by_name(gemspec.name, "= #{version}") - rescue Gem::MissingSpecError - Solargraph.logger.info "Gem #{gemspec.name} version #{version} not found. Using #{gemspec.version} instead" - gemspec - end - - # @param gemspec [Gem::Specification] - # @return [Array] - def fetch_dependencies gemspec - # @param spec [Gem::Dependency] - only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps| - Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}" - dep = Gem.loaded_specs[spec.name] - # @todo is next line necessary? - dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement) - deps.merge fetch_dependencies(dep) if deps.add?(dep) - rescue Gem::MissingSpecError - Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found in RubyGems." - end.to_a - end - - # @param gemspec [Gem::Specification] - # @return [Array] - def only_runtime_dependencies gemspec - gemspec.dependencies - gemspec.development_dependencies - end - - def inspect self.class.inspect end - - # @return [Array] - def gemspecs_required_from_bundler - # @todo Handle projects with custom Bundler/Gemfile setups - return unless workspace.gemfile? - - if workspace.gemfile? && Bundler.definition&.lockfile&.to_s&.start_with?(workspace.directory) - # Find only the gems bundler is now using - Bundler.definition.locked_gems.specs.flat_map do |lazy_spec| - logger.info "Handling #{lazy_spec.name}:#{lazy_spec.version}" - [Gem::Specification.find_by_name(lazy_spec.name, lazy_spec.version)] - rescue Gem::MissingSpecError => e - logger.info("Could not find #{lazy_spec.name}:#{lazy_spec.version} with find_by_name, falling back to guess") - # can happen in local filesystem references - specs = resolve_path_to_gemspecs lazy_spec.name - logger.warn "Gem #{lazy_spec.name} #{lazy_spec.version} from bundle not found: #{e}" if specs.nil? - next specs - end.compact - else - logger.info 'Fetching gemspecs required from Bundler (bundler/require)' - gemspecs_required_from_external_bundle - end - end - - # @return [Array] - def gemspecs_required_from_external_bundle - logger.info 'Fetching gemspecs required from external bundle' - return [] unless workspace&.directory - - Solargraph.with_clean_env do - cmd = [ - 'ruby', '-e', - "require 'bundler'; require 'json'; Dir.chdir('#{workspace&.directory}') { puts Bundler.definition.locked_gems.specs.map { |spec| [spec.name, spec.version] }.to_h.to_json }" - ] - o, e, s = Open3.capture3(*cmd) - if s.success? - Solargraph.logger.debug "External bundle: #{o}" - hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {} - hash.flat_map do |name, version| - Gem::Specification.find_by_name(name, version) - rescue Gem::MissingSpecError => e - logger.info("Could not find #{name}:#{version} with find_by_name, falling back to guess") - # can happen in local filesystem references - specs = resolve_path_to_gemspecs name - logger.warn "Gem #{name} #{version} from bundle not found: #{e}" if specs.nil? - next specs - end.compact - else - Solargraph.logger.warn "Failed to load gems from bundle at #{workspace&.directory}: #{e}" - end - end - end end end diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index ffd653d96..e907a7e43 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -9,7 +9,10 @@ module Solargraph # in an associated Library or ApiMap. # class Workspace + include Logging + autoload :Config, 'solargraph/workspace/config' + autoload :Gemspecs, 'solargraph/workspace/gemspecs' # @return [String] attr_reader :directory @@ -41,6 +44,19 @@ def config @config ||= Solargraph::Workspace::Config.new(directory) end + # @param out [IO, nil] output stream for logging + # @param gemspec [Gem::Specification] + # @return [Array] + def fetch_dependencies gemspec, out: $stderr + gemspecs.fetch_dependencies(gemspec, out: out) + end + + # @param require [String] The string sent to 'require' in the code to resolve, e.g. 'rails', 'bundler/require' + # @return [Array] + def resolve_require require + gemspecs.resolve_require(require) + end + # Merge the source. A merge will update the existing source for the file # or add it to the sources if the workspace is configured to include it. # The source is ignored if the configuration excludes it. @@ -115,15 +131,15 @@ def would_require? path # # @return [Boolean] def gemspec? - !gemspecs.empty? + !gemspec_files.empty? end # Get an array of all gemspec files in the workspace. # # @return [Array] - def gemspecs + def gemspec_files return [] if directory.empty? || directory == '*' - @gemspecs ||= Dir[File.join(directory, '**/*.gemspec')].select do |gs| + @gemspec_files ||= Dir[File.join(directory, '**/*.gemspec')].select do |gs| config.allow? gs end end @@ -156,12 +172,15 @@ def command_path server['commandPath'] || 'solargraph' end - # True if the workspace has a root Gemfile. - # - # @todo Handle projects with custom Bundler/Gemfile setups (see DocMap#gemspecs_required_from_bundler) - # - def gemfile? - directory && File.file?(File.join(directory, 'Gemfile')) + # @return [String, nil] + def directory_or_nil + return nil if directory.empty? || directory == '*' + directory + end + + # @return [Solargraph::Workspace::Gemspecs] + def gemspecs + @gemspecs ||= Solargraph::Workspace::Gemspecs.new(directory_or_nil) end private @@ -200,7 +219,7 @@ def load_sources def generate_require_paths return configured_require_paths unless gemspec? result = [] - gemspecs.each do |file| + gemspec_files.each do |file| base = File.dirname(file) # HACK: Evaluating gemspec files violates the goal of not running # workspace code, but this is how Gem::Specification.load does it diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb new file mode 100644 index 000000000..717c723da --- /dev/null +++ b/lib/solargraph/workspace/gemspecs.rb @@ -0,0 +1,184 @@ +# frozen_string_literal: true + +require 'rubygems' +require 'bundler' + +module Solargraph + class Workspace + # Manages determining which gemspecs are available in a workspace + class Gemspecs + include Logging + + attr_reader :directory, :preferences + + # @param directory [String, nil] If nil, assume no bundle is present + # @param preferences [Array] + def initialize directory, preferences: [] + # @todo an issue with both external bundles and the potential + # preferences feature is that bundler gives you a 'clean' + # rubygems environment with only the specified versions + # installed. Possible alternatives: + # + # *) prompt the user to run solargraph outside of bundler + # and treat all bundles as external + # *) reinstall the needed gems dynamically each time + # *) manipulate the rubygems/bundler environment + @directory = directory && File.absolute_path(directory) + # @todo implement preferences as a config-exposed feature + @preferences = preferences + end + + # Take the path given to a 'require' statement in a source file + # and return the Gem::Specifications which will be brought into + # scope with it, so we can load pins for them. + # + # @param require [String] The string sent to 'require' in the code to resolve, e.g. 'rails', 'bundler/require' + # @return [::Array, nil] + def resolve_require require + return nil if require.empty? + return gemspecs_required_from_bundler if require == 'bundler/require' + + # @sg-ignore Variable type could not be inferred for gemspec + # @type [Gem::Specification, nil] + gemspec = Gem::Specification.find_by_path(require) + if gemspec.nil? + gem_name_guess = require.split('/').first + begin + # this can happen when the gem is included via a local path in + # a Gemfile; Gem doesn't try to index the paths in that case. + # + # See if we can make a good guess: + potential_gemspec = Gem::Specification.find_by_name(gem_name_guess) + file = "lib/#{require}.rb" + # @sg-ignore Unresolved call to files + gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file } + rescue Gem::MissingSpecError + logger.debug do + "Require path #{require} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}" + end + [] + end + end + return nil if gemspec.nil? + [gemspec_or_preference(gemspec)] + end + + # @param gemspec [Gem::Specification] + # @param out[IO, nil] output stream for logging + # + # @return [Array] + def fetch_dependencies gemspec, out: $stderr + # @param spec [Gem::Dependency] + only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps| + Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}" + dep = Gem.loaded_specs[spec.name] + # @todo is next line necessary? + dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement) + deps.merge fetch_dependencies(dep) if deps.add?(dep) + rescue Gem::MissingSpecError + Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for " \ + "#{gemspec.name} not found in RubyGems." + end.to_a + end + + private + + # True if the workspace has a root Gemfile. + # + # @todo Handle projects with custom Bundler/Gemfile setups (see DocMap#gemspecs_required_from_bundler) + # + def gemfile? + directory && File.file?(File.join(directory, 'Gemfile')) + end + + # @return [Hash{String => Gem::Specification}] + def preference_map + @preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] } + end + + # @param gemspec [Gem::Specification] + # @return [Gem::Specification] + def gemspec_or_preference gemspec + return gemspec unless preference_map.key?(gemspec.name) + return gemspec if gemspec.version == preference_map[gemspec.name].version + + # @todo this code is unused but broken + # @sg-ignore Unresolved call to by_path + change_gemspec_version gemspec, preference_map[by_path.name].version + end + + # @param gemspec [Gem::Specification] + # @param version [Gem::Version] + # @return [Gem::Specification] + def change_gemspec_version gemspec, version + Gem::Specification.find_by_name(gemspec.name, "= #{version}") + rescue Gem::MissingSpecError + Solargraph.logger.info "Gem #{gemspec.name} version #{version} not found. Using #{gemspec.version} instead" + gemspec + end + + # @param gemspec [Gem::Specification] + # @return [Array] + def only_runtime_dependencies gemspec + gemspec.dependencies - gemspec.development_dependencies + end + + # @return [Array] + def gemspecs_required_from_bundler + # @todo Handle projects with custom Bundler/Gemfile setups + return unless gemfile? + + if gemfile? && Bundler.definition&.lockfile&.to_s&.start_with?(directory) + # Find only the gems bundler is now using + Bundler.definition.locked_gems.specs.flat_map do |lazy_spec| + logger.info "Handling #{lazy_spec.name}:#{lazy_spec.version}" + [Gem::Specification.find_by_name(lazy_spec.name, lazy_spec.version)] + rescue Gem::MissingSpecError => e + logger.info("Could not find #{lazy_spec.name}:#{lazy_spec.version} with " \ + 'find_by_name, falling back to guess') + # can happen in local filesystem references + specs = resolve_require lazy_spec.name + logger.warn "Gem #{lazy_spec.name} #{lazy_spec.version} from bundle not found: #{e}" if specs.nil? + next specs + end.compact + else + logger.info 'Fetching gemspecs required from Bundler (bundler/require)' + gemspecs_required_from_external_bundle + end + end + + # @return [Array] + def gemspecs_required_from_external_bundle + logger.info 'Fetching gemspecs required from external bundle' + return [] unless directory + + Solargraph.with_clean_env do + cmd = [ + 'ruby', '-e', + "require 'bundler'; " \ + "require 'json'; " \ + "Dir.chdir('#{directory}') { " \ + 'puts Bundler.definition.locked_gems.specs.map { |spec| [spec.name, spec.version] }' \ + '.to_h.to_json }' + ] + o, e, s = Open3.capture3(*cmd) + if s.success? + Solargraph.logger.debug "External bundle: #{o}" + hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {} + hash.flat_map do |name, version| + Gem::Specification.find_by_name(name, version) + rescue Gem::MissingSpecError => e + logger.info("Could not find #{name}:#{version} with find_by_name, falling back to guess") + # can happen in local filesystem references + specs = resolve_require name + logger.warn "Gem #{name} #{version} from bundle not found: #{e}" if specs.nil? + next specs + end.compact + else + Solargraph.logger.warn "Failed to load gems from bundle at #{directory}: #{e}" + end + end + end + end + end +end diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index b03e573f0..e8c2c9763 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -8,15 +8,17 @@ Solargraph::PinCache.serialize_yard_gem(gemspec, yard_pins) end + let(:workspace) { Solargraph::Workspace.new(Dir.pwd) } + it 'generates pins from gems' do - doc_map = Solargraph::DocMap.new(['ast'], []) + doc_map = Solargraph::DocMap.new(['ast'], workspace) doc_map.cache_all!($stderr) node_pin = doc_map.pins.find { |pin| pin.path == 'AST::Node' } expect(node_pin).to be_a(Solargraph::Pin::Namespace) end it 'tracks unresolved requires' do - doc_map = Solargraph::DocMap.new(['not_a_gem'], []) + doc_map = Solargraph::DocMap.new(['not_a_gem'], workspace) expect(doc_map.unresolved_requires).to include('not_a_gem') end @@ -26,15 +28,14 @@ spec.version = '1.0.0' end allow(Gem::Specification).to receive(:find_by_path).and_return(gemspec) - doc_map = Solargraph::DocMap.new(['not_a_gem'], [gemspec]) + doc_map = Solargraph::DocMap.new(['not_a_gem'], workspace) expect(doc_map.uncached_yard_gemspecs).to eq([gemspec]) expect(doc_map.uncached_rbs_collection_gemspecs).to eq([gemspec]) end it 'imports all gems when bundler/require used' do - workspace = Solargraph::Workspace.new(Dir.pwd) - plain_doc_map = Solargraph::DocMap.new([], [], workspace) - doc_map_with_bundler_require = Solargraph::DocMap.new(['bundler/require'], [], workspace) + plain_doc_map = Solargraph::DocMap.new([], workspace) + doc_map_with_bundler_require = Solargraph::DocMap.new(['bundler/require'], workspace) expect(doc_map_with_bundler_require.pins.length - plain_doc_map.pins.length).to be_positive end @@ -43,19 +44,19 @@ # 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/) - Solargraph::DocMap.new(['set'], []) + Solargraph::DocMap.new(['set'], workspace) end it 'ignores nil requires' do - expect { Solargraph::DocMap.new([nil], []) }.not_to raise_error + expect { Solargraph::DocMap.new([nil], workspace) }.not_to raise_error end it 'ignores empty requires' do - expect { Solargraph::DocMap.new([''], []) }.not_to raise_error + expect { Solargraph::DocMap.new([''], workspace) }.not_to raise_error end it 'collects dependencies' do - doc_map = Solargraph::DocMap.new(['rspec'], []) + doc_map = Solargraph::DocMap.new(['rspec'], workspace) expect(doc_map.dependencies.map(&:name)).to include('rspec-core') end @@ -70,7 +71,7 @@ def global(doc_map) Solargraph::Convention.register dummy_convention - doc_map = Solargraph::DocMap.new(['original_gem'], []) + doc_map = Solargraph::DocMap.new(['original_gem'], workspace) expect(doc_map.requires).to include('original_gem', 'convention_gem1', 'convention_gem2') diff --git a/spec/workspace_spec.rb b/spec/workspace_spec.rb index 572c3e131..dd8d8844b 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -72,7 +72,7 @@ gemspec_file = File.join(dir_path, 'test.gemspec') File.write(gemspec_file, '') expect(workspace.gemspec?).to be(true) - expect(workspace.gemspecs).to eq([gemspec_file]) + expect(workspace.gemspec_files).to eq([gemspec_file]) end it "generates default require path" do @@ -130,7 +130,7 @@ it 'ignores gemspecs in excluded directories' do # vendor/**/* is excluded by default workspace = Solargraph::Workspace.new('spec/fixtures/vendored') - expect(workspace.gemspecs).to be_empty + expect(workspace.gemspec_files).to be_empty end it 'rescues errors loading files into sources' do From 74e1baf9a7cacad85625eee28875281835ce8fb3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 4 Sep 2025 12:56:26 -0400 Subject: [PATCH 200/460] Add @sg-ignore --- lib/solargraph/workspace/gemspecs.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 717c723da..c42b2d843 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -161,6 +161,7 @@ def gemspecs_required_from_external_bundle 'puts Bundler.definition.locked_gems.specs.map { |spec| [spec.name, spec.version] }' \ '.to_h.to_json }' ] + # @sg-ignore Unresolved call to capture3 on Module o, e, s = Open3.capture3(*cmd) if s.success? Solargraph.logger.debug "External bundle: #{o}" From b9c8600c446ad6f7de1a98c27d776cd6d255a93f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 4 Sep 2025 15:35:00 -0400 Subject: [PATCH 201/460] Drop @sg-ignores --- lib/solargraph/workspace/gemspecs.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 717c723da..38b46da30 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -38,7 +38,6 @@ def resolve_require require return nil if require.empty? return gemspecs_required_from_bundler if require == 'bundler/require' - # @sg-ignore Variable type could not be inferred for gemspec # @type [Gem::Specification, nil] gemspec = Gem::Specification.find_by_path(require) if gemspec.nil? @@ -50,7 +49,6 @@ def resolve_require require # See if we can make a good guess: potential_gemspec = Gem::Specification.find_by_name(gem_name_guess) file = "lib/#{require}.rb" - # @sg-ignore Unresolved call to files gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file } rescue Gem::MissingSpecError logger.debug do From f6faf8a74fdf06b02f466b10a4507ff42c4dbcc3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 4 Sep 2025 15:53:33 -0400 Subject: [PATCH 202/460] Fix merge --- spec/doc_map_spec.rb | 43 +++++++++++++++++++++++++++++++----- spec/gem_pins_spec.rb | 2 +- spec/yard_map/mapper_spec.rb | 2 +- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index b87970d48..cdf12d75a 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -5,7 +5,7 @@ describe Solargraph::DocMap do subject(:doc_map) do - described_class.new(requires, [], workspace, out: out) + described_class.new(requires, workspace, out: out) end let(:out) { StringIO.new } @@ -16,7 +16,7 @@ Solargraph::Workspace.new(Dir.pwd) end - let(:plain_doc_map) { described_class.new([], [], workspace, out: nil) } + let(:plain_doc_map) { described_class.new([], workspace, out: nil) } before do doc_map.cache_doc_map_gems!(nil) if pre_cache @@ -33,6 +33,28 @@ end end + context 'with an invalid require' do + let(:requires) do + ['not_a_gem'] + end + + it 'tracks unresolved requires' do + # These are auto-required by solargraph-rspec in case the bundle + # includes these gems. In our case, it doesn't! + unprovided_solargraph_rspec_requires = [ + 'rspec-rails', + 'actionmailer', + 'activerecord', + 'shoulda-matchers', + 'rspec-sidekiq', + 'airborne', + 'activesupport' + ] + expect(doc_map.unresolved_requires - unprovided_solargraph_rspec_requires) + .to eq(['not_a_gem']) + end + end + context 'when deserialization takes a while' do let(:pre_cache) { false } let(:requires) { ['backport'] } @@ -60,9 +82,10 @@ it 'tracks uncached_gemspecs' do pincache = instance_double(Solargraph::PinCache) uncached_gemspec = Gem::Specification.new('uncached_gem', '1.0.0') - allow(workspace).to receive_messages(fresh_pincache: pincache) - allow(Gem::Specification).to receive(:find_by_path).with('uncached_gem').and_return(uncached_gemspec) + allow(workspace).to receive_messages(resolve_require: [], fresh_pincache: pincache) allow(workspace).to receive(:global_environ).and_return(Solargraph::Environ.new) + allow(workspace).to receive(:resolve_require).with('uncached_gem').and_return([uncached_gemspec]) + allow(workspace).to receive(:fetch_dependencies).with(uncached_gemspec, out: out).and_return([]) allow(pincache).to receive(:deserialize_combined_pin_cache).with(uncached_gemspec).and_return(nil) expect(doc_map.uncached_gemspecs).to eq([uncached_gemspec]) end @@ -70,7 +93,7 @@ context 'with require as bundle/require' do it 'imports all gems when bundler/require used' do - doc_map_with_bundler_require = described_class.new(['bundler/require'], [], workspace, out: nil) + doc_map_with_bundler_require = described_class.new(['bundler/require'], workspace, out: nil) doc_map_with_bundler_require.cache_doc_map_gems!(nil) expect(doc_map_with_bundler_require.pins.length - plain_doc_map.pins.length).to be_positive end @@ -104,6 +127,14 @@ end end + context 'with a require that has dependencies' do + let(:requires) { ['rspec'] } + + it 'collects dependencies' do + expect(doc_map.dependencies.map(&:name)).to include('rspec-core') + end + end + context 'with convention' do let(:pre_cache) { false } @@ -118,7 +149,7 @@ def global(doc_map) Solargraph::Convention.register dummy_convention - doc_map = Solargraph::DocMap.new(['original_gem'], [], workspace) + doc_map = Solargraph::DocMap.new(['original_gem'], workspace) expect(doc_map.requires).to include('original_gem', 'convention_gem1', 'convention_gem2') ensure diff --git a/spec/gem_pins_spec.rb b/spec/gem_pins_spec.rb index 4d2bb4ff5..944afd331 100644 --- a/spec/gem_pins_spec.rb +++ b/spec/gem_pins_spec.rb @@ -2,7 +2,7 @@ describe Solargraph::GemPins do let(:workspace) { Solargraph::Workspace.new(Dir.pwd) } - let(:doc_map) { Solargraph::DocMap.new(requires, [], workspace, out: nil) } + let(:doc_map) { Solargraph::DocMap.new(requires, workspace, out: nil) } let(:pin) { doc_map.pins.find { |pin| pin.path == path } } before do diff --git a/spec/yard_map/mapper_spec.rb b/spec/yard_map/mapper_spec.rb index 770d9e9ef..0fa0f0236 100644 --- a/spec/yard_map/mapper_spec.rb +++ b/spec/yard_map/mapper_spec.rb @@ -4,7 +4,7 @@ end def pins_with require - doc_map = Solargraph::DocMap.new([require], [], @api_map.workspace, out: nil) + doc_map = Solargraph::DocMap.new([require], @api_map.workspace, out: nil) doc_map.cache_doc_map_gems!(nil) doc_map.pins end From 6317cb1d20542467076b0ed19dcbd3614a275e96 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 4 Sep 2025 16:12:39 -0400 Subject: [PATCH 203/460] Remove one future spec --- spec/doc_map_spec.rb | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index cdf12d75a..f03be0e8e 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -33,28 +33,6 @@ end end - context 'with an invalid require' do - let(:requires) do - ['not_a_gem'] - end - - it 'tracks unresolved requires' do - # These are auto-required by solargraph-rspec in case the bundle - # includes these gems. In our case, it doesn't! - unprovided_solargraph_rspec_requires = [ - 'rspec-rails', - 'actionmailer', - 'activerecord', - 'shoulda-matchers', - 'rspec-sidekiq', - 'airborne', - 'activesupport' - ] - expect(doc_map.unresolved_requires - unprovided_solargraph_rspec_requires) - .to eq(['not_a_gem']) - end - end - context 'when deserialization takes a while' do let(:pre_cache) { false } let(:requires) { ['backport'] } From 947d5b9c01b82d74c9f39613e0925ba975f0a98d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 4 Sep 2025 16:42:36 -0400 Subject: [PATCH 204/460] Update rubocop todo file --- .rubocop_todo.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0ed335f34..95d352ebc 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -64,7 +64,6 @@ Layout/BlockAlignment: Layout/ClosingHeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment. @@ -82,7 +81,6 @@ Layout/ElseAlignment: # Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines. Layout/EmptyLineBetweenDefs: Exclude: - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/language_server/message/initialize.rb' - 'lib/solargraph/pin/delegated_method.rb' @@ -167,7 +165,6 @@ Layout/HashAlignment: Layout/HeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - 'spec/yard_map/mapper/to_method_spec.rb' # This cop supports safe autocorrection (--autocorrect). @@ -1181,7 +1178,7 @@ Style/SafeNavigation: # Configuration parameters: Max. Style/SafeNavigationChainLength: Exclude: - - 'lib/solargraph/doc_map.rb' + - 'lib/solargraph/workspace/gemspecs.rb' # This cop supports unsafe autocorrection (--autocorrect-all). Style/SlicingWithRange: From c1e4c8d953cc512b28aa0477efd6005f82082330 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 09:08:28 -0400 Subject: [PATCH 205/460] Add some new specs --- spec/spec_helper.rb | 26 ++ .../gemspecs_fetch_dependencies_spec.rb | 88 ++++++ .../gemspecs_resolve_require_spec.rb | 295 ++++++++++++++++++ 3 files changed, 409 insertions(+) create mode 100644 spec/workspace/gemspecs_fetch_dependencies_spec.rb create mode 100644 spec/workspace/gemspecs_resolve_require_spec.rb diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 00cc6c8c3..59d107aa3 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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 diff --git a/spec/workspace/gemspecs_fetch_dependencies_spec.rb b/spec/workspace/gemspecs_fetch_dependencies_spec.rb new file mode 100644 index 000000000..21fe040e5 --- /dev/null +++ b/spec/workspace/gemspecs_fetch_dependencies_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'fileutils' +require 'tmpdir' +require 'rubygems/commands/install_command' + +xdescribe Solargraph::Workspace::Gemspecs, '#fetch_dependencies' do + subject(:deps) { gemspecs.fetch_dependencies(gemspec) } + + let(:gemspecs) { described_class.new(dir_path) } + let(:dir_path) { Dir.pwd } + + context 'when in our bundle' do + context 'with a Bundler::LazySpecification' do + let(:gemspec) do + Bundler::LazySpecification.new('solargraph', nil, nil) + end + + it 'finds a known dependency' do + pending('https://github.com/castwide/solargraph/pull/1006') + expect(deps.map(&:name)).to include('backport') + end + end + + context 'with gem whose dependency does not exist in our bundle' do + let(:gemspec) do + instance_double(Gem::Specification, + dependencies: [Gem::Dependency.new('activerecord')], + development_dependencies: [], + name: 'my_fake_gem', + version: '123') + end + let(:gem_name) { 'my_fake_gem' } + + it 'gives a useful message' do + pending('https://github.com/castwide/solargraph/pull/1006') + + output = capture_both { deps.map(&:name) } + expect(output).to include('Please install the gem activerecord') + end + end + end + + context 'with external bundle' do + let(:dir_path) { File.realpath(Dir.mktmpdir).to_s } + + let(:gemspec) do + Bundler::LazySpecification.new(gem_name, nil, nil) + end + + before do + # write out Gemfile + File.write(File.join(dir_path, 'Gemfile'), <<~GEMFILE) + source 'https://rubygems.org' + gem '#{gem_name}' + GEMFILE + + # run bundle install + output, status = Solargraph.with_clean_env do + Open3.capture2e('bundle install --verbose', chdir: dir_path) + end + raise "Failure installing bundle: #{output}" unless status.success? + + # ensure Gemfile.lock exists + unless File.exist?(File.join(dir_path, 'Gemfile.lock')) + raise "Gemfile.lock not found after bundle install in #{dir_path}" + end + end + + context 'with gem that exists in our bundle' do + let(:gem_name) { 'undercover' } + + it 'finds dependencies' do + expect(deps.map(&:name)).to include('ast') + end + end + + context 'with gem does not exist in our bundle' do + let(:gem_name) { 'activerecord' } + + it 'gives a useful message' do + dep_names = nil + output = capture_both { dep_names = deps.map(&:name) } + expect(output).to include('Please install the gem activerecord') + end + end + end +end diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb new file mode 100644 index 000000000..f6217271c --- /dev/null +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -0,0 +1,295 @@ +# frozen_string_literal: true + +require 'fileutils' +require 'tmpdir' +require 'rubygems/commands/install_command' + +describe Solargraph::Workspace::Gemspecs, '#resolve_require' do + subject(:specs) { gemspecs.resolve_require(require) } + + let(:gemspecs) { described_class.new(dir_path) } + + def find_or_install gem_name, version + Gem::Specification.find_by_name(gem_name, version) + rescue Gem::LoadError + install_gem(gem_name, version) + end + + def add_bundle + # write out Gemfile + File.write(File.join(dir_path, 'Gemfile'), <<~GEMFILE) + source 'https://rubygems.org' + gem 'backport' + GEMFILE + # run bundle install + output, status = Solargraph.with_clean_env do + Open3.capture2e('bundle install --verbose', chdir: dir_path) + end + raise "Failure installing bundle: #{output}" unless status.success? + # ensure Gemfile.lock exists + return if File.exist?(File.join(dir_path, 'Gemfile.lock')) + raise "Gemfile.lock not found after bundle install in #{dir_path}" + end + + def install_gem gem_name, version + Bundler.with_unbundled_env do + cmd = Gem::Commands::InstallCommand.new + cmd.handle_options [gem_name, '-v', version] + cmd.execute + rescue Gem::SystemExitException => e + raise unless e.exit_code == 0 + end + end + + context 'with local bundle' do + let(:dir_path) { File.realpath(Dir.pwd) } + + context 'with a known gem' do + let(:require) { 'solargraph' } + + it 'returns a single spec' do + expect(specs.size).to eq(1) + end + + it 'resolves to the right known gem' do + expect(specs.map(&:name)).to eq(['solargraph']) + end + end + + context 'with an unknown type from Bundler / RubyGems' do + let(:require) { 'solargraph' } + let(:specish_objects) { [double] } + + before do + lockfile = instance_double(Pathname) + locked_gems = instance_double(Bundler::LockfileParser, specs: specish_objects) + + definition = instance_double(Bundler::Definition, + locked_gems: locked_gems, + lockfile: lockfile) + allow(Bundler).to receive(:definition).and_return(definition) + allow(lockfile).to receive(:to_s).and_return(dir_path) + end + + it 'returns a single spec' do + expect(specs.size).to eq(1) + end + + it 'resolves to the right known gem' do + expect(specs.map(&:name)).to eq(['solargraph']) + end + end + + def configure_bundler_spec stub_value + platform = Gem::Platform::RUBY + bundler_stub_spec = Bundler::StubSpecification.new('solargraph', '123', platform, spec_fetcher) + specish_objects = [bundler_stub_spec] + lockfile = instance_double(Pathname) + locked_gems = instance_double(Bundler::LockfileParser, specs: specish_objects) + definition = instance_double(Bundler::Definition, + locked_gems: locked_gems, + lockfile: lockfile) + # specish_objects = Bundler.definition.locked_gems.specs + allow(Bundler).to receive(:definition).and_return(definition) + allow(lockfile).to receive(:to_s).and_return(dir_path) + allow(bundler_stub_spec).to receive(:respond_to?).with(:name).and_return(true) + allow(bundler_stub_spec).to receive(:respond_to?).with(:version).and_return(true) + allow(bundler_stub_spec).to receive(:respond_to?).with(:gem_dir).and_return(false) + allow(bundler_stub_spec).to receive(:respond_to?).with(:materialize_for_installation).and_return(false) + allow(bundler_stub_spec).to receive_messages(name: 'solargraph', stub: stub_value) + end + + context 'with a Bundler::StubSpecification from Bundler / RubyGems' do + # this can happen from local gems, which is hard to test + # organically + + let(:require) { 'solargraph' } + let(:spec_fetcher) { instance_double(Gem::SpecFetcher) } + + before do + platform = Gem::Platform::RUBY + real_spec = instance_double(Gem::Specification) + allow(real_spec).to receive(:name).and_return('solargraph') + gem_stub_spec = Gem::StubSpecification.new('solargraph', '123', platform, spec_fetcher) + configure_bundler_spec(gem_stub_spec) + allow(gem_stub_spec).to receive_messages(name: 'solargraph', version: '123', spec: real_spec) + end + + it 'returns a single spec' do + expect(specs.size).to eq(1) + end + + it 'resolves to the right known gem' do + expect(specs.map(&:name)).to eq(['solargraph']) + end + end + + context 'with a Bundler::StubSpecification that resolves straight to Gem::Specification' do + # have seen different behavior with different versions of rubygems/bundler + + let(:require) { 'solargraph' } + let(:spec_fetcher) { instance_double(Gem::SpecFetcher) } + let(:real_spec) { Gem::Specification.new('solargraph', '123') } + + before do + configure_bundler_spec(real_spec) + end + + it 'returns a single spec' do + expect(specs.size).to eq(1) + end + + it 'resolves to the right known gem' do + expect(specs.map(&:name)).to eq(['solargraph']) + end + end + + context 'with a less usual require mapping' do + let(:require) { 'diff/lcs' } + + it 'returns a single spec' do + expect(specs.size).to eq(1) + end + + it 'resolves to the right known gem' do + expect(specs.map(&:name)).to eq(['diff-lcs']) + end + end + + context 'with Bundler.require' do + let(:require) { 'bundler/require' } + + it 'returns the gemspec gem' do + expect(specs.map(&:name)).to include('solargraph') + end + end + end + + context 'with nil as directory' do + let(:dir_path) { nil } + + context 'with simple require' do + let(:require) { 'solargraph' } + + it 'finds solargraph' do + expect(specs.map(&:name)).to eq(['solargraph']) + end + end + + context 'with Bundler.require' do + let(:require) { 'bundler/require' } + + it 'finds nothing' do + pending('https://github.com/castwide/solargraph/pull/1006') + + expect(specs).to be_empty + end + end + end + + context 'with external bundle' do + let(:dir_path) { File.realpath(Dir.mktmpdir).to_s } + + context 'with no actual bundle' do + let(:require) { 'bundler/require' } + + it 'raises' do + pending('https://github.com/castwide/solargraph/pull/1006') + + expect { specs }.to raise_error(Solargraph::BundleNotFoundError) + end + end + + context 'with Gemfile and Bundler.require' do + before { add_bundle } + + let(:require) { 'bundler/require' } + + it 'does not raise' do + expect { specs }.not_to raise_error + end + + it 'returns gems' do + expect(specs.map(&:name)).to include('backport') + end + end + + context 'with Gemfile but an unknown gem' do + before { add_bundle } + + let(:require) { 'unknown_gemlaksdflkdf' } + + it 'returns nil' do + expect(specs).to be_nil + end + end + + context 'with a Gemfile and a gem preference' do + # find_or_install helper doesn't seem to work on older versions + if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.1.0') + before do + add_bundle + find_or_install('backport', '1.0.0') + Gem::Specification.find_by_name('backport', '= 1.0.0') + end + + let(:preferences) do + [ + Gem::Specification.new.tap do |spec| + spec.name = 'backport' + spec.version = '1.0.0' + end + ] + end + + it 'returns the preferred gemspec' do + gemspecs = described_class.new(dir_path, preferences: preferences) + specs = gemspecs.resolve_require('backport') + backport = specs.find { |spec| spec.name == 'backport' } + + expect(backport.version.to_s).to eq('1.0.0') + end + + context 'with a gem preference that does not exist' do + let(:preferences) do + [ + Gem::Specification.new.tap do |spec| + spec.name = 'backport' + spec.version = '99.0.0' + end + ] + end + + it 'returns the gemspec we do have' do + gemspecs = described_class.new(dir_path, preferences: preferences) + specs = gemspecs.resolve_require('backport') + backport = specs.find { |spec| spec.name == 'backport' } + + expect(backport.version.to_s).to eq('1.2.0') + end + end + + context 'with a gem preference already set to the version we use' do + let(:version) { Gem::Specification.find_by_name('backport').version.to_s } + + let(:preferences) do + [ + Gem::Specification.new.tap do |spec| + spec.name = 'backport' + spec.version = version + end + ] + end + + it 'returns the gemspec we do have' do + gemspecs = described_class.new(dir_path, preferences: preferences) + specs = gemspecs.resolve_require('backport') + backport = specs.find { |spec| spec.name == 'backport' } + + expect(backport.version.to_s).to eq(version) + end + end + end + end + end +end From f32bb177d45cb62090b3c94f64b83f4ce4f7c4c2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 09:14:07 -0400 Subject: [PATCH 206/460] Convince GHA to run on branch of branch --- .github/workflows/linting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index b4ef26bfe..4520280dc 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -8,7 +8,7 @@ name: Linting on: workflow_dispatch: {} pull_request: - branches: [ master ] + branches: [ * ] push: branches: - 'main' From b1a736d1bb03aa4f51d4eb01553e869fd227b83f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 09:14:48 -0400 Subject: [PATCH 207/460] Convince GHA to run on branch of branch --- .github/workflows/linting.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 4520280dc..1078f5534 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -7,8 +7,7 @@ name: Linting on: workflow_dispatch: {} - pull_request: - branches: [ * ] + pull_request: true push: branches: - 'main' From 87cb546927e73b7e525c5a1aa622c856ed3c084d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 09:17:58 -0400 Subject: [PATCH 208/460] Convince GHA to run on branch of branch --- .github/workflows/linting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 1078f5534..84299cbb5 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -10,7 +10,7 @@ on: pull_request: true push: branches: - - 'main' + - '*' tags: - 'v*' From 99c8b34a7d248f088c4d67a8b48bb38f1c4690ee Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 09:19:18 -0400 Subject: [PATCH 209/460] Convince GHA to run on branch of branch --- .github/workflows/linting.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 84299cbb5..aaefe0c16 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -7,10 +7,12 @@ name: Linting on: workflow_dispatch: {} - pull_request: true - push: + pull_request: branches: - '*' + push: + branches: + - 'main' tags: - 'v*' From b9a16c41b41385c49ed3da101af3a1fce7dd332b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 09:20:34 -0400 Subject: [PATCH 210/460] Convince GHA to run on branch of branch --- .github/workflows/plugins.yml | 3 ++- .github/workflows/rspec.yml | 3 ++- .github/workflows/typecheck.yml | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index b0f22ec3e..69f93e25c 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -4,7 +4,8 @@ on: push: branches: [ master ] pull_request: - branches: [ master ] + branches: + - '*' permissions: contents: read diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index ecc3d9771..1ad29dc63 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -11,7 +11,8 @@ on: push: branches: [ master ] pull_request: - branches: [ master ] + branches: + - '*' permissions: contents: read diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 0ae8a3d8a..26eb75a17 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -11,7 +11,8 @@ on: push: branches: [ master ] pull_request: - branches: [ master ] + branches: + - '*' permissions: contents: read From 76e37a545ec479acbc7b1e005b0365a0fae7f285 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 09:30:26 -0400 Subject: [PATCH 211/460] Mark another new spec as pending --- spec/workspace/gemspecs_resolve_require_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb index f6217271c..cda6c3d5b 100644 --- a/spec/workspace/gemspecs_resolve_require_spec.rb +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -243,6 +243,8 @@ def configure_bundler_spec stub_value end it 'returns the preferred gemspec' do + pending('https://github.com/castwide/solargraph/pull/1006') + gemspecs = described_class.new(dir_path, preferences: preferences) specs = gemspecs.resolve_require('backport') backport = specs.find { |spec| spec.name == 'backport' } From e08b61bf97c86b51e92da687de16a3ad7dfdb1b9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 09:31:12 -0400 Subject: [PATCH 212/460] Mark another new spec as pending --- spec/workspace/gemspecs_resolve_require_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb index cda6c3d5b..2807c3384 100644 --- a/spec/workspace/gemspecs_resolve_require_spec.rb +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -263,6 +263,8 @@ def configure_bundler_spec stub_value end it 'returns the gemspec we do have' do + pending('https://github.com/castwide/solargraph/pull/1006') + gemspecs = described_class.new(dir_path, preferences: preferences) specs = gemspecs.resolve_require('backport') backport = specs.find { |spec| spec.name == 'backport' } From 33b9cb1241ab20fcc552e5f775ff4b16230cb86a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 09:33:38 -0400 Subject: [PATCH 213/460] Show missing coverage --- .github/workflows/rspec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 1ad29dc63..a24b81335 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -70,4 +70,4 @@ jobs: run: bundle exec rake spec - name: Check PR coverage run: bundle exec rake undercover - continue-on-error: true + # continue-on-error: true TODO: Restore before merging From 33a185788e2781b75ee314d19c36c990e4fd8863 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 10:13:24 -0400 Subject: [PATCH 214/460] Handle some missed coverage areas --- lib/solargraph/api_map.rb | 10 +++++----- spec/api_map_method_spec.rb | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index c2e0967f7..ce8f9a0c2 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -124,7 +124,7 @@ def doc_map # @return [::Array] def uncached_gemspecs - @doc_map&.uncached_gemspecs || [] + doc_map.uncached_gemspecs || [] end # @return [::Array] @@ -194,7 +194,7 @@ def self.load directory # @param out [IO, nil] # @return [void] def cache_all!(out) - @doc_map.cache_all!(out) + doc_map.cache_all!(out) end # @param gemspec [Gem::Specification] @@ -202,7 +202,7 @@ def cache_all!(out) # @param out [IO, nil] # @return [void] def cache_gem(gemspec, rebuild: false, out: nil) - @doc_map.cache(gemspec, rebuild: rebuild, out: out) + doc_map.cache(gemspec, rebuild: rebuild, out: out) end class << self @@ -700,9 +700,9 @@ def resolve_method_aliases pins, visibility = [:public, :private, :protected] GemPins.combine_method_pins_by_path(with_resolved_aliases) end - # @return [Workspace, nil] + # @return [Workspace] def workspace - @doc_map&.workspace + doc_map.workspace end # @param fq_reference_tag [String] A fully qualified whose method should be pulled in diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index 9d4e4f553..d3b91321b 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -133,6 +133,37 @@ class B end end + describe '#cache_all!' do + it 'can cache gems without a bench' do + api_map = Solargraph::ApiMap.new + doc_map = instance_double('DocMap', cache_all!: true) + allow(Solargraph::DocMap).to receive(:new).and_return(doc_map) + api_map.cache_all!($stderr) + expect(doc_map).to have_received(:cache_all!).with($stderr) + end + end + + describe '#cache_gem' do + it 'can cache gem without a bench' do + api_map = Solargraph::ApiMap.new + expect { api_map.cache_gem('rake', out: StringIO.new) }.not_to raise_error + end + end + + describe '#cache_gem' do + it 'can get a default workspace without a bench' do + api_map = Solargraph::ApiMap.new + expect(api_map.workspace).not_to be_nil + end + end + + describe '#uncached_gemspecs' do + it 'can get uncached gemspecs workspace without a bench' do + api_map = Solargraph::ApiMap.new + expect(api_map.uncached_gemspecs).not_to be_nil + end + end + describe '#get_methods' do it 'recognizes mixin references from context' do source = Solargraph::Source.load_string(%( From 7c215108324ae9d5d625793ee413937e5e439a43 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 10:21:54 -0400 Subject: [PATCH 215/460] Clean up RSpec/MessageSpies rubocop violations --- .rubocop_todo.yml | 5 ----- spec/doc_map_spec.rb | 3 ++- spec/language_server/host/diagnoser_spec.rb | 3 ++- spec/language_server/host/message_worker_spec.rb | 3 ++- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b8d123824..5739f5fcd 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1106,11 +1106,6 @@ RSpec/LetBeforeExamples: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: . -# SupportedStyles: have_received, receive -RSpec/MessageSpies: - EnforcedStyle: receive - RSpec/MissingExampleGroupArgument: Exclude: - 'spec/diagnostics/rubocop_helpers_spec.rb' diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index b03e573f0..38fd8e5c5 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -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) Solargraph::DocMap.new(['set'], []) + expect(Solargraph.logger).not_to have_received(:warn).with(/path set/) end it 'ignores nil requires' do diff --git a/spec/language_server/host/diagnoser_spec.rb b/spec/language_server/host/diagnoser_spec.rb index d59a843f1..697d352bd 100644 --- a/spec/language_server/host/diagnoser_spec.rb +++ b/spec/language_server/host/diagnoser_spec.rb @@ -1,9 +1,10 @@ describe Solargraph::LanguageServer::Host::Diagnoser do it "diagnoses on ticks" do host = double(Solargraph::LanguageServer::Host, options: { 'diagnostics' => true }, synchronizing?: false) + allow(host).to receive(:diagnose) diagnoser = Solargraph::LanguageServer::Host::Diagnoser.new(host) diagnoser.schedule 'file.rb' - expect(host).to receive(:diagnose).with('file.rb') diagnoser.tick + expect(host).to have_received(:diagnose).with('file.rb') end end diff --git a/spec/language_server/host/message_worker_spec.rb b/spec/language_server/host/message_worker_spec.rb index b9ce2a41f..526d88a07 100644 --- a/spec/language_server/host/message_worker_spec.rb +++ b/spec/language_server/host/message_worker_spec.rb @@ -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) 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).and_return(nil) end end From 5afe71b7d740cff980f4af051f702c08b254ed3b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 10:43:02 -0400 Subject: [PATCH 216/460] Fix up allow syntax --- spec/language_server/host/message_worker_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/language_server/host/message_worker_spec.rb b/spec/language_server/host/message_worker_spec.rb index 526d88a07..5e5bef481 100644 --- a/spec/language_server/host/message_worker_spec.rb +++ b/spec/language_server/host/message_worker_spec.rb @@ -2,12 +2,12 @@ it "handle requests on queue" do host = double(Solargraph::LanguageServer::Host) message = {'method' => '$/example'} - allow(host).to receive(:receive) + 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).and_return(nil) + expect(host).to have_received(:receive).with(message) end end From aaa757347cb7218add6156d4484dde044d9bf8d9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 10:49:00 -0400 Subject: [PATCH 217/460] Allow solargraph version to be overidden for test purposes Useful for: 1. Maintaining a separate pin cache per branch to avoid contamination and get more accurate spec results locally 2. Testing solargraph-rails against branches consolidating multiple open PRs (e.g., the dated ones at https://github.com/apiology/solargraph/pulls) Includes a direnv implementation for #1 --- .envrc | 3 +++ lib/solargraph/version.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .envrc diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..815cb00d0 --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +# current git branch +SOLARGRAPH_FORCE_VERSION=0.0.0.dev-$(git rev-parse --abbrev-ref HEAD | tr -d '\n' | tr '/' '-' | tr '_' '-') +export SOLARGRAPH_FORCE_VERSION diff --git a/lib/solargraph/version.rb b/lib/solargraph/version.rb index 94cc1b851..00ab4af98 100755 --- a/lib/solargraph/version.rb +++ b/lib/solargraph/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Solargraph - VERSION = '0.56.2' + VERSION = ENV.fetch('SOLARGRAPH_FORCE_VERSION', '0.56.2') end From c4f92467787ef76e8464c4e17d9b4fc458e88100 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 14:09:08 -0400 Subject: [PATCH 218/460] Exercise log statements too --- spec/spec_helper.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 00cc6c8c3..a74b8f97e 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -27,6 +27,9 @@ end require 'solargraph' # Suppress logger output in specs (if possible) +# execute any logging blocks to make sure they don't blow up +Solargraph::Logging.logger.sev_threshold = Logger::DEBUG +# ...but still suppress logger output in specs (if possible) if Solargraph::Logging.logger.respond_to?(:reopen) && !ENV.key?('SOLARGRAPH_LOG') Solargraph::Logging.logger.reopen(File::NULL) end From 54334790f6846289d2324714af3900221738c53c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 18:35:16 -0400 Subject: [PATCH 219/460] Better versioning in example --- .envrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.envrc b/.envrc index 815cb00d0..92f925b17 100644 --- a/.envrc +++ b/.envrc @@ -1,3 +1,3 @@ # current git branch -SOLARGRAPH_FORCE_VERSION=0.0.0.dev-$(git rev-parse --abbrev-ref HEAD | tr -d '\n' | tr '/' '-' | tr '_' '-') +SOLARGRAPH_FORCE_VERSION=0.0.1.dev-$(git rev-parse --abbrev-ref HEAD | tr -d '\n' | tr -d '/' | tr -d '-'| tr -d '_') export SOLARGRAPH_FORCE_VERSION From e38a79ad06d4a23fdb324390f2020dac0f2b4d50 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 5 Sep 2025 19:35:16 -0400 Subject: [PATCH 220/460] Backport in some specs --- spec/spec_helper.rb | 26 ++ .../gemspecs_fetch_dependencies_spec.rb | 95 ++++++ .../gemspecs_resolve_require_spec.rb | 299 ++++++++++++++++++ 3 files changed, 420 insertions(+) create mode 100644 spec/workspace/gemspecs_fetch_dependencies_spec.rb create mode 100644 spec/workspace/gemspecs_resolve_require_spec.rb diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a74b8f97e..366c22cc3 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -46,3 +46,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 diff --git a/spec/workspace/gemspecs_fetch_dependencies_spec.rb b/spec/workspace/gemspecs_fetch_dependencies_spec.rb new file mode 100644 index 000000000..d466fc0d7 --- /dev/null +++ b/spec/workspace/gemspecs_fetch_dependencies_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'fileutils' +require 'tmpdir' +require 'rubygems/commands/install_command' + +describe Solargraph::Workspace::Gemspecs, '#fetch_dependencies' do + subject(:deps) { gemspecs.fetch_dependencies(gemspec) } + + let(:gemspecs) { described_class.new(dir_path) } + let(:dir_path) { Dir.pwd } + + context 'when in our bundle' do + xcontext 'with a Bundler::LazySpecification' do + let(:gemspec) do + Bundler::LazySpecification.new('solargraph', nil, nil) + end + + it 'finds a known dependency' do + pending('https://github.com/castwide/solargraph/pull/1006') + expect(deps.map(&:name)).to include('backport') + end + end + + context 'with gem whose dependency does not exist in our bundle' do + let(:gemspec) do + instance_double(Gem::Specification, + dependencies: [Gem::Dependency.new('activerecord')], + development_dependencies: [], + name: 'my_fake_gem', + version: '123') + end + let(:gem_name) { 'my_fake_gem' } + + it 'gives a useful message' do + pending('https://github.com/castwide/solargraph/pull/1006') + + output = capture_both { deps.map(&:name) } + expect(output).to include('Please install the gem activerecord') + end + end + end + + context 'with external bundle' do + let(:dir_path) { File.realpath(Dir.mktmpdir).to_s } + + let(:gemspec) do + Gem::Specification.find_by_name(gem_name) + end + + before do + # write out Gemfile + File.write(File.join(dir_path, 'Gemfile'), <<~GEMFILE) + source 'https://rubygems.org' + gem '#{gem_name}' + GEMFILE + + # run bundle install + output, status = Solargraph.with_clean_env do + Open3.capture2e('bundle install --verbose', chdir: dir_path) + end + raise "Failure installing bundle: #{output}" unless status.success? + + # ensure Gemfile.lock exists + unless File.exist?(File.join(dir_path, 'Gemfile.lock')) + raise "Gemfile.lock not found after bundle install in #{dir_path}" + end + end + + context 'with gem that exists in our bundle' do + let(:gem_name) { 'undercover' } + + it 'finds dependencies' do + expect(deps.map(&:name)).to include('ast') + end + end + + context 'with gem does not exist in our bundle' do + let(:gemspec) do + Gem::Specification.new(fake_gem_name) + end + + let(:gem_name) { 'undercover' } + + let(:fake_gem_name) { 'faaaaaake912' } + + it 'gives a useful message' do + pending('https://github.com/castwide/solargraph/pull/1006') + dep_names = nil + output = capture_both { dep_names = deps.map(&:name) } + expect(output).to include('Please install the gem activerecord') + end + end + end +end diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb new file mode 100644 index 000000000..2807c3384 --- /dev/null +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -0,0 +1,299 @@ +# frozen_string_literal: true + +require 'fileutils' +require 'tmpdir' +require 'rubygems/commands/install_command' + +describe Solargraph::Workspace::Gemspecs, '#resolve_require' do + subject(:specs) { gemspecs.resolve_require(require) } + + let(:gemspecs) { described_class.new(dir_path) } + + def find_or_install gem_name, version + Gem::Specification.find_by_name(gem_name, version) + rescue Gem::LoadError + install_gem(gem_name, version) + end + + def add_bundle + # write out Gemfile + File.write(File.join(dir_path, 'Gemfile'), <<~GEMFILE) + source 'https://rubygems.org' + gem 'backport' + GEMFILE + # run bundle install + output, status = Solargraph.with_clean_env do + Open3.capture2e('bundle install --verbose', chdir: dir_path) + end + raise "Failure installing bundle: #{output}" unless status.success? + # ensure Gemfile.lock exists + return if File.exist?(File.join(dir_path, 'Gemfile.lock')) + raise "Gemfile.lock not found after bundle install in #{dir_path}" + end + + def install_gem gem_name, version + Bundler.with_unbundled_env do + cmd = Gem::Commands::InstallCommand.new + cmd.handle_options [gem_name, '-v', version] + cmd.execute + rescue Gem::SystemExitException => e + raise unless e.exit_code == 0 + end + end + + context 'with local bundle' do + let(:dir_path) { File.realpath(Dir.pwd) } + + context 'with a known gem' do + let(:require) { 'solargraph' } + + it 'returns a single spec' do + expect(specs.size).to eq(1) + end + + it 'resolves to the right known gem' do + expect(specs.map(&:name)).to eq(['solargraph']) + end + end + + context 'with an unknown type from Bundler / RubyGems' do + let(:require) { 'solargraph' } + let(:specish_objects) { [double] } + + before do + lockfile = instance_double(Pathname) + locked_gems = instance_double(Bundler::LockfileParser, specs: specish_objects) + + definition = instance_double(Bundler::Definition, + locked_gems: locked_gems, + lockfile: lockfile) + allow(Bundler).to receive(:definition).and_return(definition) + allow(lockfile).to receive(:to_s).and_return(dir_path) + end + + it 'returns a single spec' do + expect(specs.size).to eq(1) + end + + it 'resolves to the right known gem' do + expect(specs.map(&:name)).to eq(['solargraph']) + end + end + + def configure_bundler_spec stub_value + platform = Gem::Platform::RUBY + bundler_stub_spec = Bundler::StubSpecification.new('solargraph', '123', platform, spec_fetcher) + specish_objects = [bundler_stub_spec] + lockfile = instance_double(Pathname) + locked_gems = instance_double(Bundler::LockfileParser, specs: specish_objects) + definition = instance_double(Bundler::Definition, + locked_gems: locked_gems, + lockfile: lockfile) + # specish_objects = Bundler.definition.locked_gems.specs + allow(Bundler).to receive(:definition).and_return(definition) + allow(lockfile).to receive(:to_s).and_return(dir_path) + allow(bundler_stub_spec).to receive(:respond_to?).with(:name).and_return(true) + allow(bundler_stub_spec).to receive(:respond_to?).with(:version).and_return(true) + allow(bundler_stub_spec).to receive(:respond_to?).with(:gem_dir).and_return(false) + allow(bundler_stub_spec).to receive(:respond_to?).with(:materialize_for_installation).and_return(false) + allow(bundler_stub_spec).to receive_messages(name: 'solargraph', stub: stub_value) + end + + context 'with a Bundler::StubSpecification from Bundler / RubyGems' do + # this can happen from local gems, which is hard to test + # organically + + let(:require) { 'solargraph' } + let(:spec_fetcher) { instance_double(Gem::SpecFetcher) } + + before do + platform = Gem::Platform::RUBY + real_spec = instance_double(Gem::Specification) + allow(real_spec).to receive(:name).and_return('solargraph') + gem_stub_spec = Gem::StubSpecification.new('solargraph', '123', platform, spec_fetcher) + configure_bundler_spec(gem_stub_spec) + allow(gem_stub_spec).to receive_messages(name: 'solargraph', version: '123', spec: real_spec) + end + + it 'returns a single spec' do + expect(specs.size).to eq(1) + end + + it 'resolves to the right known gem' do + expect(specs.map(&:name)).to eq(['solargraph']) + end + end + + context 'with a Bundler::StubSpecification that resolves straight to Gem::Specification' do + # have seen different behavior with different versions of rubygems/bundler + + let(:require) { 'solargraph' } + let(:spec_fetcher) { instance_double(Gem::SpecFetcher) } + let(:real_spec) { Gem::Specification.new('solargraph', '123') } + + before do + configure_bundler_spec(real_spec) + end + + it 'returns a single spec' do + expect(specs.size).to eq(1) + end + + it 'resolves to the right known gem' do + expect(specs.map(&:name)).to eq(['solargraph']) + end + end + + context 'with a less usual require mapping' do + let(:require) { 'diff/lcs' } + + it 'returns a single spec' do + expect(specs.size).to eq(1) + end + + it 'resolves to the right known gem' do + expect(specs.map(&:name)).to eq(['diff-lcs']) + end + end + + context 'with Bundler.require' do + let(:require) { 'bundler/require' } + + it 'returns the gemspec gem' do + expect(specs.map(&:name)).to include('solargraph') + end + end + end + + context 'with nil as directory' do + let(:dir_path) { nil } + + context 'with simple require' do + let(:require) { 'solargraph' } + + it 'finds solargraph' do + expect(specs.map(&:name)).to eq(['solargraph']) + end + end + + context 'with Bundler.require' do + let(:require) { 'bundler/require' } + + it 'finds nothing' do + pending('https://github.com/castwide/solargraph/pull/1006') + + expect(specs).to be_empty + end + end + end + + context 'with external bundle' do + let(:dir_path) { File.realpath(Dir.mktmpdir).to_s } + + context 'with no actual bundle' do + let(:require) { 'bundler/require' } + + it 'raises' do + pending('https://github.com/castwide/solargraph/pull/1006') + + expect { specs }.to raise_error(Solargraph::BundleNotFoundError) + end + end + + context 'with Gemfile and Bundler.require' do + before { add_bundle } + + let(:require) { 'bundler/require' } + + it 'does not raise' do + expect { specs }.not_to raise_error + end + + it 'returns gems' do + expect(specs.map(&:name)).to include('backport') + end + end + + context 'with Gemfile but an unknown gem' do + before { add_bundle } + + let(:require) { 'unknown_gemlaksdflkdf' } + + it 'returns nil' do + expect(specs).to be_nil + end + end + + context 'with a Gemfile and a gem preference' do + # find_or_install helper doesn't seem to work on older versions + if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.1.0') + before do + add_bundle + find_or_install('backport', '1.0.0') + Gem::Specification.find_by_name('backport', '= 1.0.0') + end + + let(:preferences) do + [ + Gem::Specification.new.tap do |spec| + spec.name = 'backport' + spec.version = '1.0.0' + end + ] + end + + it 'returns the preferred gemspec' do + pending('https://github.com/castwide/solargraph/pull/1006') + + gemspecs = described_class.new(dir_path, preferences: preferences) + specs = gemspecs.resolve_require('backport') + backport = specs.find { |spec| spec.name == 'backport' } + + expect(backport.version.to_s).to eq('1.0.0') + end + + context 'with a gem preference that does not exist' do + let(:preferences) do + [ + Gem::Specification.new.tap do |spec| + spec.name = 'backport' + spec.version = '99.0.0' + end + ] + end + + it 'returns the gemspec we do have' do + pending('https://github.com/castwide/solargraph/pull/1006') + + gemspecs = described_class.new(dir_path, preferences: preferences) + specs = gemspecs.resolve_require('backport') + backport = specs.find { |spec| spec.name == 'backport' } + + expect(backport.version.to_s).to eq('1.2.0') + end + end + + context 'with a gem preference already set to the version we use' do + let(:version) { Gem::Specification.find_by_name('backport').version.to_s } + + let(:preferences) do + [ + Gem::Specification.new.tap do |spec| + spec.name = 'backport' + spec.version = version + end + ] + end + + it 'returns the gemspec we do have' do + gemspecs = described_class.new(dir_path, preferences: preferences) + specs = gemspecs.resolve_require('backport') + backport = specs.find { |spec| spec.name == 'backport' } + + expect(backport.version.to_s).to eq(version) + end + end + end + end + end +end From f402d2ed9a5b1dcab2afe57b75c698efca0d0b5e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 6 Sep 2025 13:41:34 -0400 Subject: [PATCH 221/460] Fix merge --- spec/rbs_map/conversions_spec.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 522ee489c..1df43af26 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -20,6 +20,7 @@ before do rbs_file = File.join(temp_dir, 'foo.rbs') File.write(rbs_file, rbs) + api_map.index conversions.pins end attr_reader :temp_dir @@ -58,10 +59,6 @@ class Sub < Hash[Symbol, untyped] let(:sub_alias_stack) { api_map.get_method_stack('Sub', 'meth_alias', scope: :instance) } - before do - api_map.index conversions.pins - end - it 'does not crash looking at superclass method' do expect { sup_method_stack }.not_to raise_error end From 9ef37822f2b63f212873bba75a66dee8f5ee5ce1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 6 Sep 2025 13:54:37 -0400 Subject: [PATCH 222/460] Mark spec as working --- spec/rbs_map/conversions_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 1df43af26..e6322443d 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -103,7 +103,6 @@ class C < ::B::C if Gem::Version.new(RBS::VERSION) >= Gem::Version.new('3.9.1') context 'with method pin for Open3.capture2e' do it 'accepts chdir kwarg' do - pending('https://github.com/castwide/solargraph/pull/1005') api_map = Solargraph::ApiMap.load_with_cache('.', $stdout) method_pin = api_map.pins.find do |pin| From bb0f6079024f1e28ee9f12c26bbc626fe475d2ce Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 6 Sep 2025 14:18:16 -0400 Subject: [PATCH 223/460] Rerun rubocop todo --- .rubocop_todo.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0ed335f34..c55a29039 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -64,7 +64,6 @@ Layout/BlockAlignment: Layout/ClosingHeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment. @@ -167,7 +166,6 @@ Layout/HashAlignment: Layout/HeredocIndentation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/rbs_map/conversions_spec.rb' - 'spec/yard_map/mapper/to_method_spec.rb' # This cop supports safe autocorrection (--autocorrect). @@ -685,10 +683,13 @@ RSpec/LetBeforeExamples: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: . +# Configuration parameters: EnforcedStyle. # SupportedStyles: have_received, receive RSpec/MessageSpies: - EnforcedStyle: receive + Exclude: + - 'spec/doc_map_spec.rb' + - 'spec/language_server/host/diagnoser_spec.rb' + - 'spec/language_server/host/message_worker_spec.rb' RSpec/MissingExampleGroupArgument: Exclude: @@ -750,10 +751,6 @@ RSpec/ScatteredLet: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata. -RSpec/SpecFilePathFormat: - Enabled: false - RSpec/StubbedMock: Exclude: - 'spec/language_server/host/message_worker_spec.rb' From 4d02e767e0a143f5e9abd2e84791cbc99e1cafde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lek=C3=AB=20Mula?= Date: Thu, 4 Sep 2025 23:18:11 +0200 Subject: [PATCH 224/460] Add solargraph profile command --- lib/solargraph/shell.rb | 94 +++++++++++++++++++++++++++++++++++++++++ solargraph.gemspec | 1 + 2 files changed, 95 insertions(+) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index a005f600b..a8a8337f9 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -241,6 +241,100 @@ def list puts "#{workspace.filenames.length} files total." end + desc 'profile [FILE]', 'Profile go-to-definition performance using vernier' + option :directory, type: :string, aliases: :d, desc: 'The workspace directory', default: '.' + option :output_dir, type: :string, aliases: :o, desc: 'The output directory for profiles', default: './tmp/profiles' + option :line, type: :numeric, aliases: :l, desc: 'Line number (0-based)', default: 4 + option :column, type: :numeric, aliases: :c, desc: 'Column number', default: 10 + # @param file [String, nil] + # @return [void] + def profile(file = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + begin + require 'vernier' + rescue LoadError + STDERR.puts "vernier gem not found. Install with: gem install vernier" + return + end + + directory = File.realpath(options[:directory]) + FileUtils.mkdir_p(options[:output_dir]) + + host = Solargraph::LanguageServer::Host.new + host.client_capabilities.merge!({ 'window' => { 'workDoneProgress' => true } }) + def host.send_notification method, params + puts "Notification: #{method} - #{params}" + end + + puts "Parsing and mapping source files..." + prepare_start = Time.now + Vernier.profile(out: "#{options[:output_dir]}/parse_benchmark.json.gz") do + puts "Mapping libraries" + host.prepare(directory) + sleep 0.2 until host.libraries.all?(&:mapped?) + end + prepare_time = Time.now - prepare_start + + puts "Building the catalog..." + catalog_start = Time.now + Vernier.profile(out: "#{options[:output_dir]}/catalog_benchmark.json.gz") do + host.catalog + end + catalog_time = Time.now - catalog_start + + # Determine test file + if file + test_file = File.join(directory, file) + else + test_file = File.join(directory, 'lib', 'other.rb') + unless File.exist?(test_file) + # Fallback to any Ruby file in the workspace + workspace = Solargraph::Workspace.new(directory) + test_file = workspace.filenames.find { |f| f.end_with?('.rb') } + unless test_file + STDERR.puts "No Ruby files found in workspace" + return + end + end + end + + file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path(test_file)) + + puts "Profiling go-to-definition for #{test_file}" + puts "Position: line #{options[:line]}, column #{options[:column]}" + + definition_start = Time.now + Vernier.profile(out: "#{options[:output_dir]}/definition_benchmark.json.gz") do + message = Solargraph::LanguageServer::Message::TextDocument::Definition.new( + host, { + 'params' => { + 'textDocument' => { 'uri' => file_uri }, + 'position' => { 'line' => options[:line], 'character' => options[:column] } + } + } + ) + puts "Processing go-to-definition request..." + result = message.process + + puts "Result: #{result.inspect}" + end + definition_time = Time.now - definition_start + + puts "\n=== Timing Results ===" + puts "Parsing & mapping: #{(prepare_time * 1000).round(2)}ms" + puts "Catalog building: #{(catalog_time * 1000).round(2)}ms" + puts "Go-to-definition: #{(definition_time * 1000).round(2)}ms" + total_time = prepare_time + catalog_time + definition_time + puts "Total time: #{(total_time * 1000).round(2)}ms" + + puts "\nProfiles saved to:" + puts " - #{File.expand_path('parse_benchmark.json.gz', options[:output_dir])}" + puts " - #{File.expand_path('catalog_benchmark.json.gz', options[:output_dir])}" + puts " - #{File.expand_path('definition_benchmark.json.gz', options[:output_dir])}" + + puts "\nUpload the JSON files to https://vernier.prof/ to view the profiles." + puts "Or use https://rubygems.org/gems/profile-viewer to view them locally." + end + private # @param pin [Solargraph::Pin::Base] diff --git a/solargraph.gemspec b/solargraph.gemspec index 42d80dae2..1c694034b 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -59,6 +59,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'undercover', '~> 0.7' s.add_development_dependency 'overcommit', '~> 0.68.0' s.add_development_dependency 'webmock', '~> 3.6' + s.add_development_dependency 'vernier' # work around missing yard dependency needed as of Ruby 3.5 s.add_development_dependency 'irb', '~> 1.15' end From 872431ca5aed2ad0c054ab4bf4a7242a26ae24be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lek=C3=AB=20Mula?= Date: Fri, 5 Sep 2025 11:09:23 +0200 Subject: [PATCH 225/460] Add memory usage counter --- lib/solargraph/shell.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index a8a8337f9..2c07a94e0 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -246,6 +246,7 @@ def list option :output_dir, type: :string, aliases: :o, desc: 'The output directory for profiles', default: './tmp/profiles' option :line, type: :numeric, aliases: :l, desc: 'Line number (0-based)', default: 4 option :column, type: :numeric, aliases: :c, desc: 'Column number', default: 10 + option :memory, type: :boolean, aliases: :m, desc: 'Include memory usage counter', default: true # @param file [String, nil] # @return [void] def profile(file = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength @@ -256,6 +257,9 @@ def profile(file = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength return end + hooks = [] + hooks << :memory_usage if options[:memory] + directory = File.realpath(options[:directory]) FileUtils.mkdir_p(options[:output_dir]) @@ -267,7 +271,7 @@ def host.send_notification method, params puts "Parsing and mapping source files..." prepare_start = Time.now - Vernier.profile(out: "#{options[:output_dir]}/parse_benchmark.json.gz") do + Vernier.profile(out: "#{options[:output_dir]}/parse_benchmark.json.gz", hooks: hooks) do puts "Mapping libraries" host.prepare(directory) sleep 0.2 until host.libraries.all?(&:mapped?) @@ -276,7 +280,7 @@ def host.send_notification method, params puts "Building the catalog..." catalog_start = Time.now - Vernier.profile(out: "#{options[:output_dir]}/catalog_benchmark.json.gz") do + Vernier.profile(out: "#{options[:output_dir]}/catalog_benchmark.json.gz", hooks: hooks) do host.catalog end catalog_time = Time.now - catalog_start @@ -303,7 +307,7 @@ def host.send_notification method, params puts "Position: line #{options[:line]}, column #{options[:column]}" definition_start = Time.now - Vernier.profile(out: "#{options[:output_dir]}/definition_benchmark.json.gz") do + Vernier.profile(out: "#{options[:output_dir]}/definition_benchmark.json.gz", hooks: hooks) do message = Solargraph::LanguageServer::Message::TextDocument::Definition.new( host, { 'params' => { From 7edc8691344bec6ca09fe1b8a4cce69df3cb0113 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 6 Sep 2025 22:04:57 -0400 Subject: [PATCH 226/460] Reodo rubocop todo --- .rubocop_todo.yml | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c55a29039..c2fe01517 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -223,11 +223,6 @@ Layout/SpaceAfterComma: Layout/SpaceAroundEqualsInParameterDefault: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -Layout/SpaceAroundKeyword: - Exclude: - - 'spec/rbs_map/conversions_spec.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator, EnforcedStyleForRationalLiterals. # SupportedStylesForExponentOperator: space, no_space @@ -643,7 +638,6 @@ RSpec/ExampleWording: # This cop supports safe autocorrection (--autocorrect). RSpec/ExcessiveDocstringSpacing: Exclude: - - 'spec/rbs_map/conversions_spec.rb' - 'spec/source/chain/call_spec.rb' # This cop supports safe autocorrection (--autocorrect). @@ -659,21 +653,10 @@ RSpec/ExpectActual: RSpec/HookArgument: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: . -# SupportedStyles: is_expected, should -RSpec/ImplicitExpect: - EnforcedStyle: should - # Configuration parameters: AssignmentOnly. RSpec/InstanceVariable: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -RSpec/LeadingSubject: - Exclude: - - 'spec/rbs_map/conversions_spec.rb' - RSpec/LeakyConstantDeclaration: Exclude: - 'spec/complex_type_spec.rb' @@ -974,7 +957,6 @@ Style/MapIntoArray: Exclude: - 'lib/solargraph/diagnostics/update_errors.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/type_checker/param_def.rb' # This cop supports unsafe autocorrection (--autocorrect-all). Style/MapToHash: @@ -1048,7 +1030,6 @@ Style/Next: - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - 'lib/solargraph/pin/signature.rb' - 'lib/solargraph/source_map/clip.rb' - - 'lib/solargraph/type_checker/checks.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: Strict, AllowedNumbers, AllowedPatterns. @@ -1295,10 +1276,7 @@ YARD/MismatchName: Enabled: false YARD/TagTypeSyntax: - Exclude: - - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/parser/comment_ripper.rb' - - 'lib/solargraph/type_checker.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. From 86965f26b813efdd98f87a22cc90faef4871a968 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 6 Sep 2025 22:15:50 -0400 Subject: [PATCH 227/460] Fix merge --- lib/solargraph/api_map/store.rb | 2 +- spec/rbs_map/conversions_spec.rb | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index e12431c94..e3972415c 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -297,7 +297,7 @@ def include_reference_pins index.include_reference_pins end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def prepend_references index.prepend_references end diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index ea7d3df02..30f784673 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -135,7 +135,6 @@ class C < ::B::C param.name == 'chdir' end expect(chdir_param).not_to be_nil, -> { "Found pin #{method_pin.to_rbs} from #{method_pin.type_location}" } ->>>>>>> origin/master end end end From 557edd0574bc2a1dbc0f87937f64e7a019c4a4bc Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 6 Sep 2025 23:04:04 -0400 Subject: [PATCH 228/460] Fix merge --- spec/rbs_map/conversions_spec.rb | 51 ++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 30f784673..7eac07209 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -25,6 +25,33 @@ attr_reader :temp_dir + + context 'with overlapping module hierarchies and inheritance' do + let(:rbs) do + <<~RBS + module B + class C + def foo: () -> String + end + end + module A + module B + class C < ::B::C + end + end + end + RBS + end + + subject(:method_pin) { api_map.get_method_stack('A::B::C', 'foo').first } + + before do + api_map.index conversions.pins + end + + it { should be_a(Solargraph::Pin::Method) } + end + context 'with untyped response' do let(:rbs) do <<~RBS @@ -51,7 +78,7 @@ def bar: () -> untyped @api_map = Solargraph::ApiMap.load_with_cache('.') end - let(:api_map) { @api_map } # rubocop:disable RSpec/InstanceVariable + let(:api_map) { @api_map } context 'with superclass pin for Parser::AST::Node' do let(:superclass_pin) do @@ -97,28 +124,6 @@ class Sub < Hash[Symbol, untyped] .uniq).to eq(['Symbol']) end end - - context 'with overlapping module hierarchies and inheritance' do - let(:rbs) do - <<~RBS - module B - class C - def foo: () -> String - end - end - module A - module B - class C < ::B::C - end - end - end - RBS - end - - subject(:method_pin) { api_map.get_method_stack('A::B::C', 'foo').first } - - it { should be_a(Solargraph::Pin::Method) } - end end if Gem::Version.new(RBS::VERSION) >= Gem::Version.new('3.9.1') From 9628ef4be67f0ff5b3769760156bacd4673c83c7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 6 Sep 2025 23:09:23 -0400 Subject: [PATCH 229/460] Clean up spec --- spec/rbs_map/conversions_spec.rb | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 7eac07209..e964e69f2 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -25,8 +25,9 @@ attr_reader :temp_dir - context 'with overlapping module hierarchies and inheritance' do + subject(:method_pin) { api_map.get_method_stack('A::B::C', 'foo').first } + let(:rbs) do <<~RBS module B @@ -43,16 +44,16 @@ class C < ::B::C RBS end - subject(:method_pin) { api_map.get_method_stack('A::B::C', 'foo').first } - before do api_map.index conversions.pins end - it { should be_a(Solargraph::Pin::Method) } + it { is_expected.to be_a(Solargraph::Pin::Method) } end context 'with untyped response' do + subject(:method_pin) { conversions.pins.find { |pin| pin.path == 'Foo#bar' } } + let(:rbs) do <<~RBS class Foo @@ -61,13 +62,11 @@ def bar: () -> untyped RBS end - subject(:method_pin) { conversions.pins.find { |pin| pin.path == 'Foo#bar' } } - - it { should_not be_nil } + it { is_expected.not_to be_nil } - it { should be_a(Solargraph::Pin::Method) } + it { is_expected.to be_a(Solargraph::Pin::Method) } - it 'maps untyped in RBS to undefined in Solargraph 'do + it 'maps untyped in RBS to undefined in Solargraph' do expect(method_pin.return_type.tag).to eq('undefined') end end From 4bfee71fce385524654c955d1a22988ffea25189 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 6 Sep 2025 23:15:18 -0400 Subject: [PATCH 230/460] Add @type annotation --- lib/solargraph/type_checker.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 0b43c44fe..c315a407f 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -481,6 +481,7 @@ def kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, else ptype = data[:qualified] unless ptype.undefined? + # @type [ComplexType] argtype = argchain.infer(api_map, block_pin, locals) if argtype.defined? && ptype && !arg_conforms_to?(argtype, ptype) result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") From ddc3d0088038204d4a052e6d892337591733c061 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 05:32:54 -0400 Subject: [PATCH 231/460] Fix merge --- lib/solargraph/doc_map.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 7c1d4ad3b..081a92d82 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -31,12 +31,6 @@ def uncached_gemspecs @uncached_gemspecs end - # @return [Workspace, nil] - attr_reader :workspace - - # @return [Environ] - attr_reader :environ - # @param requires [Array] # @param workspace [Workspace, nil] # @param out [IO, nil] output stream for logging From 2548d6d8bcf9dd3a4de3933eaebeeef355e7a275 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 05:36:42 -0400 Subject: [PATCH 232/460] Drop xcontext --- spec/workspace/gemspecs_fetch_dependencies_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/workspace/gemspecs_fetch_dependencies_spec.rb b/spec/workspace/gemspecs_fetch_dependencies_spec.rb index d466fc0d7..285f8e1a0 100644 --- a/spec/workspace/gemspecs_fetch_dependencies_spec.rb +++ b/spec/workspace/gemspecs_fetch_dependencies_spec.rb @@ -11,7 +11,7 @@ let(:dir_path) { Dir.pwd } context 'when in our bundle' do - xcontext 'with a Bundler::LazySpecification' do + context 'with a Bundler::LazySpecification' do let(:gemspec) do Bundler::LazySpecification.new('solargraph', nil, nil) end From e51777bc2b97e031e0911e3edc5461d1acd3758d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 05:43:30 -0400 Subject: [PATCH 233/460] Rename method and add comments --- lib/solargraph/workspace/gemspecs.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index c42b2d843..1f4fef27b 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -36,7 +36,12 @@ def initialize directory, preferences: [] # @return [::Array, nil] def resolve_require require return nil if require.empty? - return gemspecs_required_from_bundler if require == 'bundler/require' + + # This is added in the parser when it sees 'Bundler.require' - + # see https://bundler.io/guides/bundler_setup.html ' + # + # @todo handle different arguments to Bundler.require + return auto_required_gemspecs_from_bundler if require == 'bundler/require' # @sg-ignore Variable type could not be inferred for gemspec # @type [Gem::Specification, nil] @@ -85,7 +90,7 @@ def fetch_dependencies gemspec, out: $stderr # True if the workspace has a root Gemfile. # - # @todo Handle projects with custom Bundler/Gemfile setups (see DocMap#gemspecs_required_from_bundler) + # @todo Handle projects with custom Bundler/Gemfile setups (see #auto_required_gemspecs_from_bundler) # def gemfile? directory && File.file?(File.join(directory, 'Gemfile')) @@ -124,7 +129,7 @@ def only_runtime_dependencies gemspec end # @return [Array] - def gemspecs_required_from_bundler + def auto_required_gemspecs_from_bundler # @todo Handle projects with custom Bundler/Gemfile setups return unless gemfile? From 837d7f67872f47619200320bafb7d80da9abad95 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 16:57:05 -0400 Subject: [PATCH 234/460] Force build --- .github/workflows/rspec.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index ecc3d9771..bfa6dce07 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -48,8 +48,8 @@ jobs: run: | bundle install bundle update rbs # use latest available for this Ruby version - - name: Run tests - run: bundle exec rake spec +# - name: Run tests +# run: bundle exec rake spec undercover: runs-on: ubuntu-latest steps: From a4208e71241a3b2e5e2f9198d8d135a065bd7ea4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:10:41 -0400 Subject: [PATCH 235/460] Restore --- .github/workflows/rspec.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index bfa6dce07..ecc3d9771 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -48,8 +48,8 @@ jobs: run: | bundle install bundle update rbs # use latest available for this Ruby version -# - name: Run tests -# run: bundle exec rake spec + - name: Run tests + run: bundle exec rake spec undercover: runs-on: ubuntu-latest steps: From b66f2ac85a8d5599e36decc6a24fce36eed2f382 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:14:22 -0400 Subject: [PATCH 236/460] install -> update with rbs collection --- .github/workflows/plugins.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 730882e30..b013abd3b 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -164,7 +164,7 @@ jobs: cd ${RAILS_DIR} bundle install bundle exec --gemfile ../../Gemfile rbs --version - bundle exec --gemfile ../../Gemfile rbs collection install + bundle exec --gemfile ../../Gemfile rbs collection update cd ../../ # bundle exec rbs collection init # bundle exec rbs collection install From a09a9af2bc797134fac776c370ce4fcc2c6db121 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:25:05 -0400 Subject: [PATCH 237/460] Try Ruby 3.2 --- .github/workflows/plugins.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index b013abd3b..af9997846 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -144,7 +144,8 @@ jobs: uses: ruby/setup-ruby@v1 with: # solargraph-rails supports Ruby 3.0+ - ruby-version: '3.0' + # RBS 3.9 supports Ruby 3.2+ + ruby-version: '3.2' bundler-cache: false bundler: latest env: @@ -164,7 +165,7 @@ jobs: cd ${RAILS_DIR} bundle install bundle exec --gemfile ../../Gemfile rbs --version - bundle exec --gemfile ../../Gemfile rbs collection update + bundle exec --gemfile ../../Gemfile rbs collection install cd ../../ # bundle exec rbs collection init # bundle exec rbs collection install From 6fc8febcdd4cb9515785e0f85788497ad8923ae7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:44:39 -0400 Subject: [PATCH 238/460] Update solargraph --- .github/workflows/plugins.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index af9997846..ef9fe0155 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -158,8 +158,7 @@ jobs: export BUNDLE_PATH cd ../solargraph-rails echo "gem 'solargraph', path: '${GITHUB_WORKSPACE:?}'" >> Gemfile - bundle install - bundle update rbs + bundle update solargraph rbs RAILS_DIR="$(pwd)/spec/rails7" export RAILS_DIR cd ${RAILS_DIR} From 388c170d76531530012eb1dff32579a059e3bda6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:45:56 -0400 Subject: [PATCH 239/460] Re-add bundle install --- .github/workflows/plugins.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index ef9fe0155..a97b27c7c 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -158,6 +158,7 @@ jobs: export BUNDLE_PATH cd ../solargraph-rails echo "gem 'solargraph', path: '${GITHUB_WORKSPACE:?}'" >> Gemfile + bundle install bundle update solargraph rbs RAILS_DIR="$(pwd)/spec/rails7" export RAILS_DIR From f80b73a020dd405addf8f56d2317e31614c2f8da Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:58:16 -0400 Subject: [PATCH 240/460] Drop MATRIX_SOLARGRAPH_VERSION --- .github/workflows/plugins.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index a97b27c7c..4dedbd93f 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -149,7 +149,6 @@ jobs: bundler-cache: false bundler: latest env: - MATRIX_SOLARGRAPH_VERSION: '>=0.56.0.pre1' MATRIX_RAILS_VERSION: "7.0" - name: Install gems run: | @@ -170,7 +169,6 @@ jobs: # bundle exec rbs collection init # bundle exec rbs collection install env: - MATRIX_SOLARGRAPH_VERSION: '>=0.56.0.pre1' MATRIX_RAILS_VERSION: "7.0" MATRIX_RAILS_MAJOR_VERSION: '7' - name: Run specs @@ -184,5 +182,4 @@ jobs: bundle info yard ALLOW_IMPROVEMENTS=true bundle exec rake spec env: - MATRIX_SOLARGRAPH_VERSION: '>=0.56.0.pre1' MATRIX_RAILS_VERSION: "7.0" From ce2bee62f20628f33f1e9abb98f7faf96f69166f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 18:09:04 -0400 Subject: [PATCH 241/460] Drop debugging changes --- .github/workflows/plugins.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 4dedbd93f..b5984f3cb 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -144,8 +144,7 @@ jobs: uses: ruby/setup-ruby@v1 with: # solargraph-rails supports Ruby 3.0+ - # RBS 3.9 supports Ruby 3.2+ - ruby-version: '3.2' + ruby-version: '3.0' bundler-cache: false bundler: latest env: @@ -158,7 +157,7 @@ jobs: cd ../solargraph-rails echo "gem 'solargraph', path: '${GITHUB_WORKSPACE:?}'" >> Gemfile bundle install - bundle update solargraph rbs + bundle update rbs RAILS_DIR="$(pwd)/spec/rails7" export RAILS_DIR cd ${RAILS_DIR} From 6433c25e15002a3ebc10f4aa7bda21c0fc87645d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 18:32:29 -0400 Subject: [PATCH 242/460] Update rubocop todo --- .rubocop_todo.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c55a29039..89f703d23 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -280,7 +280,6 @@ Layout/TrailingWhitespace: Exclude: - 'lib/solargraph/language_server/message/client/register_capability.rb' - 'spec/api_map/config_spec.rb' - - 'spec/convention_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedMethods, AllowedPatterns. @@ -338,11 +337,6 @@ Lint/DuplicateBranch: Lint/DuplicateMethods: Enabled: false -# Configuration parameters: AllowComments, AllowEmptyLambdas. -Lint/EmptyBlock: - Exclude: - - 'spec/convention_spec.rb' - # Configuration parameters: AllowComments. Lint/EmptyClass: Enabled: false @@ -618,11 +612,6 @@ RSpec/DescribeClass: RSpec/DescribedClass: Enabled: false -# This cop supports unsafe autocorrection (--autocorrect-all). -RSpec/EmptyExampleGroup: - Exclude: - - 'spec/convention_spec.rb' - # This cop supports safe autocorrection (--autocorrect). RSpec/EmptyLineAfterFinalLet: Exclude: From 32fc77e303b7cb4097d8a331957d1a79440a200a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 18:33:02 -0400 Subject: [PATCH 243/460] linting fix --- spec/convention_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/convention_spec.rb b/spec/convention_spec.rb index 98a8f41bf..b6f4fc52e 100644 --- a/spec/convention_spec.rb +++ b/spec/convention_spec.rb @@ -1,5 +1,4 @@ describe Solargraph::Convention do - # rubocop:disable RSpec/ExampleLength, RSpec/MultipleExpectations it 'newly defined pins are resolved by ApiMap after file changes' do filename = 'test.rb' @@ -106,5 +105,4 @@ def local _source_map described_class.unregister updated_dummy_convention end - # rubocop:enable RSpec/ExampleLength, RSpec/MultipleExpectations end From dbe9a3edc5291e6cf50632560893306ee074a79f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 18:40:51 -0400 Subject: [PATCH 244/460] Update expectations from master branch --- .rubocop_todo.yml | 11 ----------- spec/convention_spec.rb | 2 -- 2 files changed, 13 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c55a29039..89f703d23 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -280,7 +280,6 @@ Layout/TrailingWhitespace: Exclude: - 'lib/solargraph/language_server/message/client/register_capability.rb' - 'spec/api_map/config_spec.rb' - - 'spec/convention_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedMethods, AllowedPatterns. @@ -338,11 +337,6 @@ Lint/DuplicateBranch: Lint/DuplicateMethods: Enabled: false -# Configuration parameters: AllowComments, AllowEmptyLambdas. -Lint/EmptyBlock: - Exclude: - - 'spec/convention_spec.rb' - # Configuration parameters: AllowComments. Lint/EmptyClass: Enabled: false @@ -618,11 +612,6 @@ RSpec/DescribeClass: RSpec/DescribedClass: Enabled: false -# This cop supports unsafe autocorrection (--autocorrect-all). -RSpec/EmptyExampleGroup: - Exclude: - - 'spec/convention_spec.rb' - # This cop supports safe autocorrection (--autocorrect). RSpec/EmptyLineAfterFinalLet: Exclude: diff --git a/spec/convention_spec.rb b/spec/convention_spec.rb index 98a8f41bf..b6f4fc52e 100644 --- a/spec/convention_spec.rb +++ b/spec/convention_spec.rb @@ -1,5 +1,4 @@ describe Solargraph::Convention do - # rubocop:disable RSpec/ExampleLength, RSpec/MultipleExpectations it 'newly defined pins are resolved by ApiMap after file changes' do filename = 'test.rb' @@ -106,5 +105,4 @@ def local _source_map described_class.unregister updated_dummy_convention end - # rubocop:enable RSpec/ExampleLength, RSpec/MultipleExpectations end From 217fecdcf8bf20402c78348a86aa9f4c083c1a6d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 18:40:51 -0400 Subject: [PATCH 245/460] Update expectations from master branch --- .rubocop_todo.yml | 11 ----------- spec/convention_spec.rb | 2 -- 2 files changed, 13 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3965777d9..1b3783d86 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -279,7 +279,6 @@ Layout/TrailingWhitespace: Exclude: - 'lib/solargraph/language_server/message/client/register_capability.rb' - 'spec/api_map/config_spec.rb' - - 'spec/convention_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedMethods, AllowedPatterns. @@ -337,11 +336,6 @@ Lint/DuplicateBranch: Lint/DuplicateMethods: Enabled: false -# Configuration parameters: AllowComments, AllowEmptyLambdas. -Lint/EmptyBlock: - Exclude: - - 'spec/convention_spec.rb' - # Configuration parameters: AllowComments. Lint/EmptyClass: Enabled: false @@ -609,11 +603,6 @@ RSpec/DescribeClass: RSpec/DescribedClass: Enabled: false -# This cop supports unsafe autocorrection (--autocorrect-all). -RSpec/EmptyExampleGroup: - Exclude: - - 'spec/convention_spec.rb' - # This cop supports safe autocorrection (--autocorrect). RSpec/EmptyLineAfterFinalLet: Exclude: diff --git a/spec/convention_spec.rb b/spec/convention_spec.rb index 98a8f41bf..b6f4fc52e 100644 --- a/spec/convention_spec.rb +++ b/spec/convention_spec.rb @@ -1,5 +1,4 @@ describe Solargraph::Convention do - # rubocop:disable RSpec/ExampleLength, RSpec/MultipleExpectations it 'newly defined pins are resolved by ApiMap after file changes' do filename = 'test.rb' @@ -106,5 +105,4 @@ def local _source_map described_class.unregister updated_dummy_convention end - # rubocop:enable RSpec/ExampleLength, RSpec/MultipleExpectations end From 2d2f0a77d8d17b9dabeee2a65f3ed636e528437b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:14:22 -0400 Subject: [PATCH 246/460] install -> update with rbs collection --- .github/workflows/plugins.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 730882e30..b013abd3b 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -164,7 +164,7 @@ jobs: cd ${RAILS_DIR} bundle install bundle exec --gemfile ../../Gemfile rbs --version - bundle exec --gemfile ../../Gemfile rbs collection install + bundle exec --gemfile ../../Gemfile rbs collection update cd ../../ # bundle exec rbs collection init # bundle exec rbs collection install From 3db4b1f3b049338fea2f871724c2b6a7d5de7277 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:25:05 -0400 Subject: [PATCH 247/460] Try Ruby 3.2 --- .github/workflows/plugins.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index b013abd3b..af9997846 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -144,7 +144,8 @@ jobs: uses: ruby/setup-ruby@v1 with: # solargraph-rails supports Ruby 3.0+ - ruby-version: '3.0' + # RBS 3.9 supports Ruby 3.2+ + ruby-version: '3.2' bundler-cache: false bundler: latest env: @@ -164,7 +165,7 @@ jobs: cd ${RAILS_DIR} bundle install bundle exec --gemfile ../../Gemfile rbs --version - bundle exec --gemfile ../../Gemfile rbs collection update + bundle exec --gemfile ../../Gemfile rbs collection install cd ../../ # bundle exec rbs collection init # bundle exec rbs collection install From faa64283a3e63ec203d94a8d76ff1de3b14bcd1f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:44:39 -0400 Subject: [PATCH 248/460] Update solargraph --- .github/workflows/plugins.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index af9997846..ef9fe0155 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -158,8 +158,7 @@ jobs: export BUNDLE_PATH cd ../solargraph-rails echo "gem 'solargraph', path: '${GITHUB_WORKSPACE:?}'" >> Gemfile - bundle install - bundle update rbs + bundle update solargraph rbs RAILS_DIR="$(pwd)/spec/rails7" export RAILS_DIR cd ${RAILS_DIR} From 3f68a02944a1bd249a3ea6d2c5964dd0f467c798 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:45:56 -0400 Subject: [PATCH 249/460] Re-add bundle install --- .github/workflows/plugins.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index ef9fe0155..a97b27c7c 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -158,6 +158,7 @@ jobs: export BUNDLE_PATH cd ../solargraph-rails echo "gem 'solargraph', path: '${GITHUB_WORKSPACE:?}'" >> Gemfile + bundle install bundle update solargraph rbs RAILS_DIR="$(pwd)/spec/rails7" export RAILS_DIR From b8c16054ec9be28fb3143113bd58424af82acdd6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:58:16 -0400 Subject: [PATCH 250/460] Drop MATRIX_SOLARGRAPH_VERSION --- .github/workflows/plugins.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index a97b27c7c..4dedbd93f 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -149,7 +149,6 @@ jobs: bundler-cache: false bundler: latest env: - MATRIX_SOLARGRAPH_VERSION: '>=0.56.0.pre1' MATRIX_RAILS_VERSION: "7.0" - name: Install gems run: | @@ -170,7 +169,6 @@ jobs: # bundle exec rbs collection init # bundle exec rbs collection install env: - MATRIX_SOLARGRAPH_VERSION: '>=0.56.0.pre1' MATRIX_RAILS_VERSION: "7.0" MATRIX_RAILS_MAJOR_VERSION: '7' - name: Run specs @@ -184,5 +182,4 @@ jobs: bundle info yard ALLOW_IMPROVEMENTS=true bundle exec rake spec env: - MATRIX_SOLARGRAPH_VERSION: '>=0.56.0.pre1' MATRIX_RAILS_VERSION: "7.0" From d3a0c36a5ca735759a050e1ccc5114832e01ba36 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 18:09:04 -0400 Subject: [PATCH 251/460] Drop debugging changes --- .github/workflows/plugins.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 4dedbd93f..b5984f3cb 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -144,8 +144,7 @@ jobs: uses: ruby/setup-ruby@v1 with: # solargraph-rails supports Ruby 3.0+ - # RBS 3.9 supports Ruby 3.2+ - ruby-version: '3.2' + ruby-version: '3.0' bundler-cache: false bundler: latest env: @@ -158,7 +157,7 @@ jobs: cd ../solargraph-rails echo "gem 'solargraph', path: '${GITHUB_WORKSPACE:?}'" >> Gemfile bundle install - bundle update solargraph rbs + bundle update rbs RAILS_DIR="$(pwd)/spec/rails7" export RAILS_DIR cd ${RAILS_DIR} From 410372e83ac2c576b98e7ab24fe76401daeef402 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 4 Sep 2025 08:36:01 -0400 Subject: [PATCH 252/460] Drop broken 'namespaces' method These unused methods call into ApiMap::Index#namespaces, which does not exist. --- lib/solargraph/api_map.rb | 7 ------- lib/solargraph/api_map/store.rb | 5 ----- 2 files changed, 12 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index b85fa8a0b..1f3459d96 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -237,13 +237,6 @@ def keyword_pins store.pins_by_class(Pin::Keyword) end - # An array of namespace names defined in the ApiMap. - # - # @return [Set] - def namespaces - store.namespaces - end - # True if the namespace exists. # # @param name [String] The namespace to match diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index eec3a50ac..d240752de 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -147,11 +147,6 @@ def namespace_exists?(fqns) fqns_pins(fqns).any? end - # @return [Set] - def namespaces - index.namespaces - end - # @return [Enumerable] def namespace_pins pins_by_class(Solargraph::Pin::Namespace) From 29e019320bba5655eca7bb48703534e7fca00800 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 21:30:02 -0400 Subject: [PATCH 253/460] Merge branch 'rubocop_stability' into cache_uncache_gem --- lib/solargraph/api_map.rb | 7 +++++++ lib/solargraph/api_map/store.rb | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 1f3459d96..b85fa8a0b 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -237,6 +237,13 @@ def keyword_pins store.pins_by_class(Pin::Keyword) end + # An array of namespace names defined in the ApiMap. + # + # @return [Set] + def namespaces + store.namespaces + end + # True if the namespace exists. # # @param name [String] The namespace to match diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index d240752de..eec3a50ac 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -147,6 +147,11 @@ def namespace_exists?(fqns) fqns_pins(fqns).any? end + # @return [Set] + def namespaces + index.namespaces + end + # @return [Enumerable] def namespace_pins pins_by_class(Solargraph::Pin::Namespace) From 97f0ad8d0b621077039ceb221ef115e0423fb779 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 22:51:37 -0400 Subject: [PATCH 254/460] Fix merge --- spec/yardoc_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/yardoc_spec.rb b/spec/yardoc_spec.rb index 039c5d174..60897bef4 100644 --- a/spec/yardoc_spec.rb +++ b/spec/yardoc_spec.rb @@ -13,7 +13,7 @@ end let(:gem_yardoc_path) do - Solargraph::PinCache.yardoc_path gemspec + File.join(@tmpdir, 'solargraph', 'yardoc', 'test_gem') end before do @@ -21,6 +21,8 @@ end describe '#processing?' do + let(:gemspec) { Gem::Specification.find_by_path('rubocop') } + it 'returns true if the yardoc is being processed' do FileUtils.touch(File.join(gem_yardoc_path, 'processing')) expect(described_class.processing?(gem_yardoc_path)).to be(true) @@ -103,7 +105,7 @@ ['output', instance_double(Process::Status, success?: true)] end - described_class.cache([], gemspec) + described_class.build_docs(gem_yardoc_path, [], gemspec) expect(called_with[0]['BUNDLE_GEMFILE']).to start_with('/') end From 9b2568534aba6a191d7d91cf43cc9f4c2808f431 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 22:57:59 -0400 Subject: [PATCH 255/460] Update rubocop todo --- .rubocop_todo.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b0979592f..9b4312c7f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -271,7 +271,6 @@ Layout/TrailingWhitespace: Exclude: - 'lib/solargraph/language_server/message/client/register_capability.rb' - 'spec/api_map/config_spec.rb' - - 'spec/convention_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedMethods, AllowedPatterns. @@ -329,11 +328,6 @@ Lint/DuplicateBranch: Lint/DuplicateMethods: Enabled: false -# Configuration parameters: AllowComments, AllowEmptyLambdas. -Lint/EmptyBlock: - Exclude: - - 'spec/convention_spec.rb' - # Configuration parameters: AllowComments. Lint/EmptyClass: Enabled: false @@ -601,11 +595,6 @@ RSpec/DescribeClass: RSpec/DescribedClass: Enabled: false -# This cop supports unsafe autocorrection (--autocorrect-all). -RSpec/EmptyExampleGroup: - Exclude: - - 'spec/convention_spec.rb' - # This cop supports safe autocorrection (--autocorrect). RSpec/EmptyLineAfterFinalLet: Exclude: From 481dcb56e2a74b08d8074462b4f5806540d19f56 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 23:06:06 -0400 Subject: [PATCH 256/460] Fix RuboCop issues --- spec/convention_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/convention_spec.rb b/spec/convention_spec.rb index 98a8f41bf..b6f4fc52e 100644 --- a/spec/convention_spec.rb +++ b/spec/convention_spec.rb @@ -1,5 +1,4 @@ describe Solargraph::Convention do - # rubocop:disable RSpec/ExampleLength, RSpec/MultipleExpectations it 'newly defined pins are resolved by ApiMap after file changes' do filename = 'test.rb' @@ -106,5 +105,4 @@ def local _source_map described_class.unregister updated_dummy_convention end - # rubocop:enable RSpec/ExampleLength, RSpec/MultipleExpectations end From 3c3754d391f4006589f289b1c9da2d89d37d5d18 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 23:13:14 -0400 Subject: [PATCH 257/460] Fix spec --- lib/solargraph/api_map.rb | 4 ++-- spec/api_map_method_spec.rb | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index d47356635..46e877a1e 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -193,8 +193,8 @@ def self.load directory # @param out [IO, nil] # @return [void] - def cache_all_for_doc_map! out - @doc_map.cache_doc_map_gems!(out) + def cache_all out + doc_map.cache_all(out) end # @param gemspec [Gem::Specification] diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index dadc6c93c..261d947fb 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -133,20 +133,21 @@ class B end end - describe '#cache_all!' do + describe '#cache_all' do it 'can cache gems without a bench' do api_map = Solargraph::ApiMap.new - doc_map = instance_double(Solargraph::DocMap, cache_all!: true) + doc_map = instance_double(Solargraph::DocMap, cache_all: true) allow(Solargraph::DocMap).to receive(:new).and_return(doc_map) - api_map.cache_all!($stderr) - expect(doc_map).to have_received(:cache_all!).with($stderr) + api_map.cache_all($stderr) + expect(doc_map).to have_received(:cache_all).with($stderr) end end describe '#cache_gem' do it 'can cache gem without a bench' do api_map = Solargraph::ApiMap.new - expect { api_map.cache_gem('rake', out: StringIO.new) }.not_to raise_error + gemspec = Gem::Specification.find_by_name('backport') + expect { api_map.cache_gem(gemspec, out: StringIO.new) }.not_to raise_error end end From d8b2326833039a150f6f0e00810de4db9c125b83 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 23:15:34 -0400 Subject: [PATCH 258/460] Fix strong typechecking issues --- lib/solargraph/source_map.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 3a366d9a6..5173be180 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -44,6 +44,7 @@ def initialize source @pin_select_cache = {} end + # @sg-ignore Solargraph::SourceMap#pins_by_class return type could not be inferreda # @generic T # @param klass [Class>] # @return [Array>] @@ -172,6 +173,7 @@ def map source # @return [Array] attr_writer :convention_pins + # @return [Hash{Class => Array}] def pin_class_hash @pin_class_hash ||= pins.to_set.classify(&:class).transform_values(&:to_a) end From 42cd3337209fec27bed38e20ac07b14573971286 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 23:19:27 -0400 Subject: [PATCH 259/460] Fix strong typechecking issues --- lib/solargraph/api_map.rb | 2 +- lib/solargraph/shell.rb | 2 +- spec/shell_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 46e877a1e..d69ee9f74 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -224,7 +224,7 @@ def self.load_with_cache directory, out = $stderr return api_map end - api_map.cache_all_for_doc_map!(out) + api_map.cache_all(out) load(directory) end diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 347488111..d54b294ce 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -138,7 +138,7 @@ def gems *names api_map = Solargraph::ApiMap.load(Dir.pwd) if names.empty? - api_map.cache_all_for_doc_map!($stdout) + api_map.cache_all($stdout) else names.each do |name| if name == 'core' diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index 4dad2d0c0..77dec7dc2 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -143,11 +143,11 @@ def bundle_exec(*cmd) end it 'caches all without erroring out' do - allow(api_map).to receive(:cache_all_for_doc_map!) + allow(api_map).to receive(:cache_all) _output = capture_both { shell.gems } - expect(api_map).to have_received(:cache_all_for_doc_map!) + expect(api_map).to have_received(:cache_all) end it 'caches single gem without erroring out' do From aeb9035cf825009376ae227a85c7d4278d24c37e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 8 Sep 2025 10:23:45 -0400 Subject: [PATCH 260/460] Fix merge issue --- lib/solargraph/api_map.rb | 6 +++--- lib/solargraph/doc_map.rb | 2 +- lib/solargraph/shell.rb | 2 +- spec/api_map_method_spec.rb | 6 +++--- spec/shell_spec.rb | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index d69ee9f74..5561db4b8 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -193,8 +193,8 @@ def self.load directory # @param out [IO, nil] # @return [void] - def cache_all out - doc_map.cache_all(out) + def cache_all! out + doc_map.cache_all!(out) end # @param gemspec [Gem::Specification] @@ -224,7 +224,7 @@ def self.load_with_cache directory, out = $stderr return api_map end - api_map.cache_all(out) + api_map.cache_all!(out) load(directory) end diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 081a92d82..1f4bc67a7 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -44,7 +44,7 @@ def initialize requires, workspace, out: $stderr # @param rebuild [Boolean] whether to rebuild the pins even if they are cached # # @return [void] - def cache_all out, rebuild: false + def cache_all! out, rebuild: false load_serialized_gem_pins PinCache.cache_core(out: out) unless PinCache.core? gem_specs = uncached_gemspecs diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index d54b294ce..3e6098e6b 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -138,7 +138,7 @@ def gems *names api_map = Solargraph::ApiMap.load(Dir.pwd) if names.empty? - api_map.cache_all($stdout) + api_map.cache_all!($stdout) else names.each do |name| if name == 'core' diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index 261d947fb..26c531747 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -133,13 +133,13 @@ class B end end - describe '#cache_all' do + describe '#cache_all!' do it 'can cache gems without a bench' do api_map = Solargraph::ApiMap.new - doc_map = instance_double(Solargraph::DocMap, cache_all: true) + doc_map = instance_double(Solargraph::DocMap, cache_all!: true) allow(Solargraph::DocMap).to receive(:new).and_return(doc_map) api_map.cache_all($stderr) - expect(doc_map).to have_received(:cache_all).with($stderr) + expect(doc_map).to have_received(:cache_all!).with($stderr) end end diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index 77dec7dc2..f21029b5e 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -143,11 +143,11 @@ def bundle_exec(*cmd) end it 'caches all without erroring out' do - allow(api_map).to receive(:cache_all) + allow(api_map).to receive(:cache_all!) _output = capture_both { shell.gems } - expect(api_map).to have_received(:cache_all) + expect(api_map).to have_received(:cache_all!) end it 'caches single gem without erroring out' do From 72f709c5e6b854f7b21daeebaf020d7dbc91c59c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 8 Sep 2025 10:29:27 -0400 Subject: [PATCH 261/460] Fix merge issue --- lib/solargraph/pin_cache.rb | 45 +++++++++---------------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 1aa77f63d..8c47cfef4 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -593,46 +593,11 @@ def has_rbs_collection?(gemspec, hash) exist?(rbs_collection_path(gemspec, hash)) end - # @param out [IO, nil] # @return [void] def clear FileUtils.rm_rf base_dir, secure: true end - def core? - File.file?(core_path) - end - - # @param out [IO, nil] - # @return [Enumerable] - def cache_core out: nil - RbsMap::CoreMap.new.cache_core(out: out) - end - - # @param out [IO, nil] output stream for logging - # - # @return [void] - def cache_all_stdlibs out: $stderr - possible_stdlibs.each do |stdlib| - RbsMap::StdlibMap.new(stdlib, out: out) - end - end - - # @return [Array] a list of possible standard library names - def possible_stdlibs - # all dirs and .rb files in Gem::RUBYGEMS_DIR - Dir.glob(File.join(Gem::RUBYGEMS_DIR, '*')).map do |file_or_dir| - basename = File.basename(file_or_dir) - # remove .rb - basename = basename[0..-4] if basename.end_with?('.rb') - basename - end.sort.uniq - rescue StandardError => e - logger.info { "Failed to get possible stdlibs: #{e.message}" } - logger.debug { e.backtrace.join("\n") } - [] - end - # @param file [String] # @return [Array, nil] def load file @@ -655,6 +620,16 @@ def save file, pins logger.debug { "Cache#save: Saved #{pins.length} pins to #{file}" } end + def core? + File.file?(core_path) + end + + # @param out [IO, nil] + # @return [Array] + def cache_core out: $stderr + RbsMap::CoreMap.new.cache_core(out: out) + end + # @param path [String] def exist? *path File.file? File.join(*path) From 877236be3f2262dc6dda75ad2ce1827b2c96fe38 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 8 Sep 2025 13:42:03 -0400 Subject: [PATCH 262/460] Set spec time limit with rspec-time-guard --- solargraph.gemspec | 1 + spec/api_map_method_spec.rb | 4 ++-- spec/api_map_spec.rb | 2 +- spec/language_server/host_spec.rb | 2 +- .../message/text_document/definition_spec.rb | 2 +- spec/language_server/protocol_spec.rb | 2 +- spec/spec_helper.rb | 7 +++++++ 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/solargraph.gemspec b/solargraph.gemspec index 22843e481..a00052d34 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -53,6 +53,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'public_suffix', '~> 3.1' s.add_development_dependency 'rake', '~> 13.2' s.add_development_dependency 'rspec', '~> 3.5' + s.add_development_dependency 'rspec-time-guard', '~> 0.2.0' # # very specific development-time RuboCop version patterns for CI # stability - feel free to update in an isolated PR diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index 26c531747..03bddbe1c 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -110,7 +110,7 @@ class B end end - describe '#get_method_stack' do + describe '#get_method_stack', time_limit_seconds: 240 do let(:out) { StringIO.new } let(:api_map) { Solargraph::ApiMap.load_with_cache(Dir.pwd, out) } @@ -118,7 +118,7 @@ class B let(:external_requires) { ['yaml'] } let(:method_stack) { api_map.get_method_stack('YAML', 'safe_load', scope: :class) } - it 'handles the YAML gem aliased to Psych' do + it 'handles the YAML gem aliased to Psych', time_limit_seconds: 240 do expect(method_stack).not_to be_empty end end diff --git a/spec/api_map_spec.rb b/spec/api_map_spec.rb index 01f78846c..3fd96a8f0 100755 --- a/spec/api_map_spec.rb +++ b/spec/api_map_spec.rb @@ -5,7 +5,7 @@ @api_map = Solargraph::ApiMap.new end - it 'returns core methods' do + it 'returns core methods', time_limit_seconds: 120 do pins = @api_map.get_methods('String') expect(pins.map(&:path)).to include('String#upcase') end diff --git a/spec/language_server/host_spec.rb b/spec/language_server/host_spec.rb index 40c8b7292..bccf00a87 100644 --- a/spec/language_server/host_spec.rb +++ b/spec/language_server/host_spec.rb @@ -247,7 +247,7 @@ def initialize(foo); end expect(symbols).not_to be_empty end - it "opens a file outside of prepared libraries" do + it "opens a file outside of prepared libraries", time_limit_seconds: 120 do @host.prepare(File.absolute_path(File.join('spec', 'fixtures', 'workspace'))) @host.open('file:///file.rb', 'class Foo; end', 1) symbols = @host.document_symbols('file:///file.rb') diff --git a/spec/language_server/message/text_document/definition_spec.rb b/spec/language_server/message/text_document/definition_spec.rb index 72ff77f1e..beebb48de 100644 --- a/spec/language_server/message/text_document/definition_spec.rb +++ b/spec/language_server/message/text_document/definition_spec.rb @@ -21,7 +21,7 @@ expect(message.result.first[:uri]).to eq(other_uri) end - it 'finds definitions of require paths' do + it 'finds definitions of require paths', time_limit_seconds: 120 do path = File.absolute_path('spec/fixtures/workspace') host = Solargraph::LanguageServer::Host.new host.prepare(path) diff --git a/spec/language_server/protocol_spec.rb b/spec/language_server/protocol_spec.rb index e88fb9c05..6f4e70a80 100644 --- a/spec/language_server/protocol_spec.rb +++ b/spec/language_server/protocol_spec.rb @@ -431,7 +431,7 @@ def bar baz expect(response['result']['available']).to be_a(String) end - it "handles $/solargraph/documentGems" do + it "handles $/solargraph/documentGems", time_limit_seconds: 120 do @protocol.request '$/solargraph/documentGems', {} response = @protocol.response expect(response['error']).to be_nil diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 366c22cc3..5042ba9f9 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,6 @@ require 'bundler/setup' require 'webmock/rspec' +require 'rspec_time_guard' WebMock.disable_net_connect!(allow_localhost: true) unless ENV['SIMPLECOV_DISABLED'] # set up lcov reporting for undercover @@ -25,6 +26,12 @@ # Allow use of --only-failures with rspec, handy for local development c.example_status_persistence_file_path = 'rspec-examples.txt' end +RspecTimeGuard.setup +RspecTimeGuard.configure do |config| + config.global_time_limit_seconds = 60 + config.continue_on_timeout = false +end + require 'solargraph' # Suppress logger output in specs (if possible) # execute any logging blocks to make sure they don't blow up From 3a733209b9cb5721bc3b731ca7bea76790599740 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 8 Sep 2025 14:16:56 -0400 Subject: [PATCH 263/460] Fix merge --- lib/solargraph/api_map.rb | 6 +++--- spec/api_map_method_spec.rb | 2 +- spec/shell_spec.rb | 4 ++-- spec/yardoc_spec.rb | 5 +---- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 5561db4b8..d47356635 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -193,8 +193,8 @@ def self.load directory # @param out [IO, nil] # @return [void] - def cache_all! out - doc_map.cache_all!(out) + def cache_all_for_doc_map! out + @doc_map.cache_doc_map_gems!(out) end # @param gemspec [Gem::Specification] @@ -224,7 +224,7 @@ def self.load_with_cache directory, out = $stderr return api_map end - api_map.cache_all!(out) + api_map.cache_all_for_doc_map!(out) load(directory) end diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index 03bddbe1c..1874fb4ab 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -133,7 +133,7 @@ class B end end - describe '#cache_all!' do + describe '#cache_all_for_doc_map!' do it 'can cache gems without a bench' do api_map = Solargraph::ApiMap.new doc_map = instance_double(Solargraph::DocMap, cache_all!: true) diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index f21029b5e..4dad2d0c0 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -143,11 +143,11 @@ def bundle_exec(*cmd) end it 'caches all without erroring out' do - allow(api_map).to receive(:cache_all!) + allow(api_map).to receive(:cache_all_for_doc_map!) _output = capture_both { shell.gems } - expect(api_map).to have_received(:cache_all!) + expect(api_map).to have_received(:cache_all_for_doc_map!) end it 'caches single gem without erroring out' do diff --git a/spec/yardoc_spec.rb b/spec/yardoc_spec.rb index 60897bef4..6e7171afe 100644 --- a/spec/yardoc_spec.rb +++ b/spec/yardoc_spec.rb @@ -21,8 +21,6 @@ end describe '#processing?' do - let(:gemspec) { Gem::Specification.find_by_path('rubocop') } - it 'returns true if the yardoc is being processed' do FileUtils.touch(File.join(gem_yardoc_path, 'processing')) expect(described_class.processing?(gem_yardoc_path)).to be(true) @@ -39,10 +37,9 @@ end end - describe '#cache' do + describe '#build_docs' do let(:workspace) { Solargraph::Workspace.new(Dir.pwd) } let(:gemspec) { workspace.find_gem('rubocop') } - let(:output) { '' } before do From c1108e4153c86e8ff1998fe75ff3b6eca8512c93 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 8 Sep 2025 14:24:03 -0400 Subject: [PATCH 264/460] One more --- spec/api_map_method_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index 03bddbe1c..83a3d1bb3 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -114,7 +114,7 @@ class B let(:out) { StringIO.new } let(:api_map) { Solargraph::ApiMap.load_with_cache(Dir.pwd, out) } - context 'with stdlib that has vital dependencies' do + context 'with stdlib that has vital dependencies', time_limit_seconds: 240 do let(:external_requires) { ['yaml'] } let(:method_stack) { api_map.get_method_stack('YAML', 'safe_load', scope: :class) } From ef3fc9d700c117e0e466a16bbcc469e6ca066481 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 8 Sep 2025 15:21:31 -0400 Subject: [PATCH 265/460] Add empty-directory-related regression spec --- .../message/text_document/definition_spec.rb | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/spec/language_server/message/text_document/definition_spec.rb b/spec/language_server/message/text_document/definition_spec.rb index 72ff77f1e..b6c98b99b 100644 --- a/spec/language_server/message/text_document/definition_spec.rb +++ b/spec/language_server/message/text_document/definition_spec.rb @@ -1,4 +1,33 @@ describe Solargraph::LanguageServer::Message::TextDocument::Definition do + it 'prepares empty directory' do + Dir.mktmpdir do |dir| + host = Solargraph::LanguageServer::Host.new + test_rb_path = File.join(dir, 'test.rb') + thing_rb_path = File.join(dir, 'thing.rb') + FileUtils.cp('spec/fixtures/workspace/lib/other.rb', test_rb_path) + FileUtils.cp('spec/fixtures/workspace/lib/thing.rb', thing_rb_path) + host.prepare(dir) + sleep 0.1 until host.libraries.all?(&:mapped?) + host.catalog + file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(test_rb_path) + other_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(thing_rb_path) + message = Solargraph::LanguageServer::Message::TextDocument::Definition + .new(host, { + 'params' => { + 'textDocument' => { + 'uri' => file_uri + }, + 'position' => { + 'line' => 4, + 'character' => 10 + } + } + }) + message.process + expect(message.result.first[:uri]).to eq(other_uri) + end + end + it 'finds definitions of methods' do host = Solargraph::LanguageServer::Host.new host.prepare('spec/fixtures/workspace') @@ -21,7 +50,7 @@ expect(message.result.first[:uri]).to eq(other_uri) end - it 'finds definitions of require paths' do + it 'finds definitions of require paths', time_limit_seconds: 120 do path = File.absolute_path('spec/fixtures/workspace') host = Solargraph::LanguageServer::Host.new host.prepare(path) From bcd5fab2e88c7baf5b4bc0802835ebfd01d41ca5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 8 Sep 2025 15:28:18 -0400 Subject: [PATCH 266/460] Factor out a find_gem() method --- lib/solargraph/shell.rb | 2 +- lib/solargraph/workspace.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index a005f600b..5c3fc0581 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -145,7 +145,7 @@ def gems *names STDERR.puts "Documentation cached for all #{Gem::Specification.count} gems." else names.each do |name| - spec = Gem::Specification.find_by_name(*name.split('=')) + spec = api_map.workspace.find_gem(*name.split('=')) do_cache spec, api_map rescue Gem::MissingSpecError warn "Gem '#{name}' not found" diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index e2d3d7495..1531509af 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -162,6 +162,16 @@ def rbs_collection_config_path end end + # @param name [String] + # @param version [String, nil] + # + # @return [Gem::Specification, nil] + def find_gem name, version = nil + Gem::Specification.find_by_name(name, version) + rescue Gem::MissingSpecError + nil + end + # Synchronize the workspace from the provided updater. # # @param updater [Source::Updater] From 8d5e77519ecb403fec9d9831628b887515b32189 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 8 Sep 2025 20:50:23 -0400 Subject: [PATCH 267/460] Fix merge issues --- lib/solargraph/pin_cache.rb | 48 ++++++++++++++++++------------------- lib/solargraph/shell.rb | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 8c47cfef4..45c6bdafb 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -59,15 +59,6 @@ def suppress_yard_cache? gemspec, rbs_version_cache_key false end - # @param out [IO, nil] output stream for logging - # - # @return [void] - def cache_all_stdlibs out: $stderr - possible_stdlibs.each do |stdlib| - RbsMap::StdlibMap.new(stdlib, out: out) - end - end - # @param path [String] require path that might be in the RBS stdlib collection # @return [void] def cache_stdlib_rbs_map path @@ -127,21 +118,6 @@ def yardoc_processing? gemspec Yardoc.processing?(yardoc_path(gemspec)) end - # @return [Array] a list of possible standard library names - def possible_stdlibs - # all dirs and .rb files in Gem::RUBYGEMS_DIR - Dir.glob(File.join(Gem::RUBYGEMS_DIR, '*')).map do |file_or_dir| - basename = File.basename(file_or_dir) - # remove .rb - basename = basename[0..-4] if basename.end_with?('.rb') - basename - end.sort.uniq - rescue StandardError => e - logger.info { "Failed to get possible stdlibs: #{e.message}" } - logger.debug { e.backtrace.join("\n") } - [] - end - private # @param gemspec [Gem::Specification, Bundler::LazySpecification] @@ -412,6 +388,30 @@ def uncache_by_prefix *path_segments, out: nil class << self include Logging + # @param out [IO, nil] output stream for logging + # + # @return [void] + def cache_all_stdlibs out: $stderr + possible_stdlibs.each do |stdlib| + RbsMap::StdlibMap.new(stdlib, out: out) + end + end + + # @return [Array] a list of possible standard library names + def possible_stdlibs + # all dirs and .rb files in Gem::RUBYGEMS_DIR + Dir.glob(File.join(Gem::RUBYGEMS_DIR, '*')).map do |file_or_dir| + basename = File.basename(file_or_dir) + # remove .rb + basename = basename[0..-4] if basename.end_with?('.rb') + basename + end.sort.uniq + rescue StandardError => e + logger.info { "Failed to get possible stdlibs: #{e.message}" } + logger.debug { e.backtrace.join("\n") } + [] + end + # @return [Hash{Array => Hash{Array(String, String) => # Array}}] yard plugins, then gemspec name and # version diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 3e6098e6b..347488111 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -138,7 +138,7 @@ def gems *names api_map = Solargraph::ApiMap.load(Dir.pwd) if names.empty? - api_map.cache_all!($stdout) + api_map.cache_all_for_doc_map!($stdout) else names.each do |name| if name == 'core' From d9e7de73107f0562106ae1eeff56f7ab2fd6a509 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 06:54:51 -0400 Subject: [PATCH 268/460] Fix merge --- lib/solargraph/api_map.rb | 2 +- spec/api_map_method_spec.rb | 2 +- spec/pin_cache_spec.rb | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index d47356635..49b323718 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -194,7 +194,7 @@ def self.load directory # @param out [IO, nil] # @return [void] def cache_all_for_doc_map! out - @doc_map.cache_doc_map_gems!(out) + doc_map.cache_all!(out) end # @param gemspec [Gem::Specification] diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index ff54dd53d..a0546d6a4 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -138,7 +138,7 @@ class B api_map = Solargraph::ApiMap.new doc_map = instance_double(Solargraph::DocMap, cache_all!: true) allow(Solargraph::DocMap).to receive(:new).and_return(doc_map) - api_map.cache_all($stderr) + api_map.cache_all_for_doc_map!($stderr) expect(doc_map).to have_received(:cache_all!).with($stderr) end end diff --git a/spec/pin_cache_spec.rb b/spec/pin_cache_spec.rb index 0a11686f5..4786feeec 100644 --- a/spec/pin_cache_spec.rb +++ b/spec/pin_cache_spec.rb @@ -43,19 +43,19 @@ end end - describe '#possible_stdlibs' do + describe '.possible_stdlibs' do it 'is tolerant of less usual Ruby installations' do stub_const('Gem::RUBYGEMS_DIR', nil) - expect(pin_cache.possible_stdlibs).to eq([]) + expect(Solargraph::PinCache.possible_stdlibs).to eq([]) end end - describe '#cache_all_stdlibs' do + describe '.cache_all_stdlibs' do it 'creates stdlibmaps' do allow(Solargraph::RbsMap::StdlibMap).to receive(:new).and_return(instance_double(Solargraph::RbsMap::StdlibMap)) - pin_cache.cache_all_stdlibs + Solargraph::PinCache.cache_all_stdlibs expect(Solargraph::RbsMap::StdlibMap).to have_received(:new).at_least(:once) end From 636c55885f97df5f869e9421dc701987d82a34ab Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 07:54:56 -0400 Subject: [PATCH 269/460] Kill needless GitHub workflow tweaks --- .github/workflows/linting.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 419190900..b4ef26bfe 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -33,7 +33,7 @@ jobs: ruby-version: 3.4 bundler: latest bundler-cache: true - cache-version: 2025-08 + cache-version: 2025-06-06 - name: Update to best available RBS run: | @@ -46,8 +46,8 @@ jobs: key: | 2025-06-26-09-${{ runner.os }}-dot-cache-${{ hashFiles('Gemfile.lock') }} restore-keys: | - 2025-08-${{ runner.os }}-dot-cache - 2025-08-${{ runner.os }}-dot-cache- + 2025-06-26-09-${{ runner.os }}-dot-cache + 2025-06-26-09-${{ runner.os }}-dot-cache- path: | /home/runner/.cache/solargraph From 4b6c338153ca46b59ced0ed0ccf61140a41ae83b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 10:51:09 -0400 Subject: [PATCH 270/460] Add branch name to output dir --- lib/solargraph/shell.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 2c07a94e0..81eaf0627 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -261,7 +261,10 @@ def profile(file = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength hooks << :memory_usage if options[:memory] directory = File.realpath(options[:directory]) - FileUtils.mkdir_p(options[:output_dir]) + + branch_name = `git rev-parse --abbrev-ref HEAD`.strip + output_dir = File.join(options[:output_dir], branch_name) + FileUtils.mkdir_p(output_dir) host = Solargraph::LanguageServer::Host.new host.client_capabilities.merge!({ 'window' => { 'workDoneProgress' => true } }) @@ -271,7 +274,7 @@ def host.send_notification method, params puts "Parsing and mapping source files..." prepare_start = Time.now - Vernier.profile(out: "#{options[:output_dir]}/parse_benchmark.json.gz", hooks: hooks) do + Vernier.profile(out: "#{output_dir}/parse_benchmark.json.gz", hooks: hooks) do puts "Mapping libraries" host.prepare(directory) sleep 0.2 until host.libraries.all?(&:mapped?) @@ -280,7 +283,7 @@ def host.send_notification method, params puts "Building the catalog..." catalog_start = Time.now - Vernier.profile(out: "#{options[:output_dir]}/catalog_benchmark.json.gz", hooks: hooks) do + Vernier.profile(out: "#{output_dir}/catalog_benchmark.json.gz", hooks: hooks) do host.catalog end catalog_time = Time.now - catalog_start @@ -307,7 +310,7 @@ def host.send_notification method, params puts "Position: line #{options[:line]}, column #{options[:column]}" definition_start = Time.now - Vernier.profile(out: "#{options[:output_dir]}/definition_benchmark.json.gz", hooks: hooks) do + Vernier.profile(out: "#{output_dir}/definition_benchmark.json.gz", hooks: hooks) do message = Solargraph::LanguageServer::Message::TextDocument::Definition.new( host, { 'params' => { @@ -331,9 +334,9 @@ def host.send_notification method, params puts "Total time: #{(total_time * 1000).round(2)}ms" puts "\nProfiles saved to:" - puts " - #{File.expand_path('parse_benchmark.json.gz', options[:output_dir])}" - puts " - #{File.expand_path('catalog_benchmark.json.gz', options[:output_dir])}" - puts " - #{File.expand_path('definition_benchmark.json.gz', options[:output_dir])}" + puts " - #{File.expand_path('parse_benchmark.json.gz', output_dir)}" + puts " - #{File.expand_path('catalog_benchmark.json.gz', output_dir)}" + puts " - #{File.expand_path('definition_benchmark.json.gz', output_dir)}" puts "\nUpload the JSON files to https://vernier.prof/ to view the profiles." puts "Or use https://rubygems.org/gems/profile-viewer to view them locally." From 9b713b27c4f3ced6377b4b352fd249e2bda0ab59 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 11:20:02 -0400 Subject: [PATCH 271/460] Fix combined gem cache implementation --- lib/solargraph/pin_cache.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 8c47cfef4..d48873c53 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -119,7 +119,8 @@ def uncache_gem gemspec, out: nil PinCache.uncache(yard_gem_path(gemspec), out: out) uncache_by_prefix(rbs_collection_pins_path_prefix(gemspec), out: out) uncache_by_prefix(combined_path_prefix(gemspec), out: out) - combined_pins_in_memory.delete([gemspec.name, gemspec.version]) + rbs_version_cache_key = lookup_rbs_version_cache_key(gemspec) + combined_pins_in_memory.delete([gemspec.name, gemspec.version, rbs_version_cache_key]) end # @param gemspec [Gem::Specification, Bundler::LazySpecification] @@ -380,7 +381,11 @@ def combined_gem? gemspec, hash # @param hash [String, nil] # @return [Array, nil] def load_combined_gem gemspec, hash - PinCache.load(combined_path(gemspec, hash)) + cached = combined_pins_in_memory[[gemspec.name, gemspec.version, hash]] + return cached if cached + loaded = PinCache.load(combined_path(gemspec, hash)) + combined_pins_in_memory[[gemspec.name, gemspec.version, hash]] = loaded if loaded + loaded end # @param gemspec [Gem::Specification] From 7eba8ac8699a543c73ac0c9dc824a6fc52b5d5d6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 14:46:10 -0400 Subject: [PATCH 272/460] Drop @sg-ignores --- lib/solargraph/source_map/mapper.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 18fdf1f88..5fdcb9fe6 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -70,7 +70,6 @@ def closure_at(position) # @param comment [String] # @return [void] def process_comment source_position, comment_position, comment - # @sg-ignore Wrong argument type for String#=~: object expected String::_MatchAgainst, received Regexp return unless comment.encode('UTF-8', invalid: :replace, replace: '?') =~ DIRECTIVE_REGEXP cmnt = remove_inline_comment_hashes(comment) parse = Solargraph::Source.parse_docstring(cmnt) @@ -245,7 +244,6 @@ def remove_inline_comment_hashes comment # @return [void] def process_comment_directives - # @sg-ignore Wrong argument type for String#=~: object expected String::_MatchAgainst, received Regexp return unless @code.encode('UTF-8', invalid: :replace, replace: '?') =~ DIRECTIVE_REGEXP code_lines = @code.lines @source.associated_comments.each do |line, comments| From 346f19251744d70dfcc5c2126d8a52724ad4922b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 14:51:34 -0400 Subject: [PATCH 273/460] Fix merge issue --- spec/pin/combine_with_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/pin/combine_with_spec.rb b/spec/pin/combine_with_spec.rb index 38d45a3e1..cc80d76d5 100644 --- a/spec/pin/combine_with_spec.rb +++ b/spec/pin/combine_with_spec.rb @@ -9,7 +9,6 @@ end it 'combines return types with another method without type parameters' do - pending('logic being added to handle this case') pin1 = Solargraph::Pin::Method.new(name: 'foo', parameters: [], comments: '@return [Array]') pin2 = Solargraph::Pin::Method.new(name: 'foo', parameters: [], comments: '@return [Array]') combined = pin1.combine_with(pin2) From 2638cadb987334f1a279fdf74862af4a631852b1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 15:22:44 -0400 Subject: [PATCH 274/460] Allow longer timeout --- spec/workspace/gemspecs_fetch_dependencies_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/workspace/gemspecs_fetch_dependencies_spec.rb b/spec/workspace/gemspecs_fetch_dependencies_spec.rb index 285f8e1a0..7c8a8fe8f 100644 --- a/spec/workspace/gemspecs_fetch_dependencies_spec.rb +++ b/spec/workspace/gemspecs_fetch_dependencies_spec.rb @@ -84,7 +84,7 @@ let(:fake_gem_name) { 'faaaaaake912' } - it 'gives a useful message' do + it 'gives a useful message', time_limit_seconds: 120 do pending('https://github.com/castwide/solargraph/pull/1006') dep_names = nil output = capture_both { dep_names = deps.map(&:name) } From 29a94db3c31db45ada1b15189ad31b87a328831a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 16:00:16 -0400 Subject: [PATCH 275/460] Extend time limit --- spec/workspace/gemspecs_fetch_dependencies_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/workspace/gemspecs_fetch_dependencies_spec.rb b/spec/workspace/gemspecs_fetch_dependencies_spec.rb index 7c8a8fe8f..77d00008a 100644 --- a/spec/workspace/gemspecs_fetch_dependencies_spec.rb +++ b/spec/workspace/gemspecs_fetch_dependencies_spec.rb @@ -70,7 +70,7 @@ context 'with gem that exists in our bundle' do let(:gem_name) { 'undercover' } - it 'finds dependencies' do + it 'finds dependencies', time_limit_seconds: 120 do expect(deps.map(&:name)).to include('ast') end end From 4e02576942b48d8320123c9921324db1c203ab26 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 16:36:23 -0400 Subject: [PATCH 276/460] Improve doc_map_spec.rb --- spec/doc_map_spec.rb | 177 +++++++++++++++++++++++++++++++++---------- 1 file changed, 135 insertions(+), 42 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 1315f6c90..2624e472d 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -1,80 +1,173 @@ # frozen_string_literal: true +require 'bundler' +require 'benchmark' + describe Solargraph::DocMap do - before :all do - # We use ast here because it's a known dependency. - gemspec = Gem::Specification.find_by_name('ast') - yard_pins = Solargraph::GemPins.build_yard_pins([], gemspec) - Solargraph::PinCache.serialize_yard_gem(gemspec, yard_pins) + subject(:doc_map) do + described_class.new(requires, [], workspace) end - it 'generates pins from gems' do - doc_map = Solargraph::DocMap.new(['ast'], []) - doc_map.cache_all!($stderr) - node_pin = doc_map.pins.find { |pin| pin.path == 'AST::Node' } - expect(node_pin).to be_a(Solargraph::Pin::Namespace) + let(:out) { StringIO.new } + let(:pre_cache) { true } + let(:requires) { [] } + + let(:workspace) do + Solargraph::Workspace.new(Dir.pwd) end - it 'tracks unresolved requires' do - doc_map = Solargraph::DocMap.new(['not_a_gem'], []) - expect(doc_map.unresolved_requires).to include('not_a_gem') + let(:plain_doc_map) { described_class.new([], [], workspace) } + + before do + doc_map.cache_all!(nil) if pre_cache end - it 'tracks uncached_gemspecs' do - gemspec = Gem::Specification.new do |spec| - spec.name = 'not_a_gem' - spec.version = '1.0.0' + context 'with a require in solargraph test bundle' do + let(:requires) do + ['ast'] + end + + it 'generates pins from gems' do + node_pin = doc_map.pins.find { |pin| pin.path == 'AST::Node' } + expect(node_pin).to be_a(Solargraph::Pin::Namespace) end - allow(Gem::Specification).to receive(:find_by_path).and_return(gemspec) - doc_map = Solargraph::DocMap.new(['not_a_gem'], [gemspec]) - expect(doc_map.uncached_yard_gemspecs).to eq([gemspec]) - expect(doc_map.uncached_rbs_collection_gemspecs).to eq([gemspec]) end - it 'imports all gems when bundler/require used' do - workspace = Solargraph::Workspace.new(Dir.pwd) - plain_doc_map = Solargraph::DocMap.new([], [], workspace) - doc_map_with_bundler_require = Solargraph::DocMap.new(['bundler/require'], [], workspace) + context 'with an invalid require' do + let(:requires) do + ['not_a_gem'] + end - expect(doc_map_with_bundler_require.pins.length - plain_doc_map.pins.length).to be_positive + it 'tracks unresolved requires' do + # These are auto-required by solargraph-rspec in case the bundle + # includes these gems. In our case, it doesn't! + unprovided_solargraph_rspec_requires = [ + 'rspec-rails', + 'actionmailer', + 'activerecord', + 'shoulda-matchers', + 'rspec-sidekiq', + 'airborne', + 'activesupport' + ] + expect(doc_map.unresolved_requires - unprovided_solargraph_rspec_requires) + .to eq(['not_a_gem']) + end end 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 + + context 'when deserialization takes a while' do + let(:pre_cache) { false } + let(:requires) { ['backport'] } + + before do + # proxy this method to simulate a long-running deserialization + allow(Benchmark).to receive(:measure) do |&block| + block.call + 5.0 + end + end + + it 'logs timing' do + pending('logging being implemented') + # force lazy evaluation + _pins = doc_map.pins + expect(out.string).to include('Deserialized ').and include(' gem pins ').and include(' ms') + end + end + + 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. + allow(Solargraph.logger).to receive(:warn) + Solargraph::DocMap.new(['set'], [], workspace) + expect(Solargraph.logger).not_to have_received(:warn).with(/path set/) end it 'ignores nil requires' do - expect { Solargraph::DocMap.new([nil], []) }.not_to raise_error + expect { Solargraph::DocMap.new([nil], [], workspace) }.not_to raise_error end it 'ignores empty requires' do - expect { Solargraph::DocMap.new([''], []) }.not_to raise_error + expect { Solargraph::DocMap.new([''], [], workspace) }.not_to raise_error end it 'collects dependencies' do - doc_map = Solargraph::DocMap.new(['rspec'], []) + doc_map = Solargraph::DocMap.new(['rspec'], [], workspace) expect(doc_map.dependencies.map(&:name)).to include('rspec-core') end - it 'includes convention requires from environ' do - dummy_convention = Class.new(Solargraph::Convention::Base) do - def global(doc_map) - Solargraph::Environ.new( - requires: ['convention_gem1', 'convention_gem2'] - ) - end + context 'with require as bundle/require' do + it 'imports all gems when bundler/require used' do + doc_map_with_bundler_require = described_class.new(['bundler/require'], [], workspace) + doc_map_with_bundler_require.cache_all!(nil) + expect(doc_map_with_bundler_require.pins.length - plain_doc_map.pins.length).to be_positive end + end + + context 'with a require not needed by Ruby core' do + let(:requires) { ['set'] } + + it 'does not warn' 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. + allow(Solargraph.logger).to receive(:warn) + doc_map + expect(Solargraph.logger).not_to have_received(:warn).with(/path set/) + end + end + + context 'with a nil require' do + let(:requires) { [nil] } + + it 'does not raise error' do + expect { doc_map }.not_to raise_error + end + end + + context 'with an empty require' do + let(:requires) { [''] } - Solargraph::Convention.register dummy_convention + it 'does not raise error' do + expect { doc_map }.not_to raise_error + end + end + + context 'with a require that has dependencies' do + let(:requires) { ['rspec'] } + + it 'collects dependencies' do + expect(doc_map.dependencies.map(&:name)).to include('rspec-core') + end + end + + context 'with convention' do + let(:pre_cache) { false } - doc_map = Solargraph::DocMap.new(['original_gem'], []) + it 'includes convention requires from environ' do + dummy_convention = Class.new(Solargraph::Convention::Base) do + def global(doc_map) + Solargraph::Environ.new( + requires: ['convention_gem1', 'convention_gem2'] + ) + end + end - expect(doc_map.requires).to include('original_gem', 'convention_gem1', 'convention_gem2') + Solargraph::Convention.register dummy_convention - # Clean up the registered convention - Solargraph::Convention.unregister dummy_convention + doc_map = Solargraph::DocMap.new(['original_gem'], [], workspace) + + expect(doc_map.requires).to include('original_gem', 'convention_gem1', 'convention_gem2') + ensure + # Clean up the registered convention + Solargraph::Convention.unregister dummy_convention + end end end From 2d0479977a08c6d358c588c559351dc684fbc7a5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 16:46:41 -0400 Subject: [PATCH 277/460] Fix merge errors --- spec/doc_map_spec.rb | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 2624e472d..54b423f99 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -55,14 +55,6 @@ end end - 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. - 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 - context 'when deserialization takes a while' do let(:pre_cache) { false } let(:requires) { ['backport'] } @@ -83,27 +75,6 @@ end end - 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. - allow(Solargraph.logger).to receive(:warn) - Solargraph::DocMap.new(['set'], [], workspace) - expect(Solargraph.logger).not_to have_received(:warn).with(/path set/) - end - - it 'ignores nil requires' do - expect { Solargraph::DocMap.new([nil], [], workspace) }.not_to raise_error - end - - it 'ignores empty requires' do - expect { Solargraph::DocMap.new([''], [], workspace) }.not_to raise_error - end - - it 'collects dependencies' do - doc_map = Solargraph::DocMap.new(['rspec'], [], workspace) - expect(doc_map.dependencies.map(&:name)).to include('rspec-core') - end - context 'with require as bundle/require' do it 'imports all gems when bundler/require used' do doc_map_with_bundler_require = described_class.new(['bundler/require'], [], workspace) From be3f5e54853eebbf835964ac139e3ace4ae81318 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 16:55:10 -0400 Subject: [PATCH 278/460] Add fix-ups --- spec/api_map_spec.rb | 2 +- spec/shell_spec.rb | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/api_map_spec.rb b/spec/api_map_spec.rb index 01f78846c..805ce49cb 100755 --- a/spec/api_map_spec.rb +++ b/spec/api_map_spec.rb @@ -766,7 +766,7 @@ def bar; end it 'knows that false is a "subtype" of Boolean' do api_map = Solargraph::ApiMap.new - expect(api_map.super_and_sub?('Boolean', 'true')).to be(true) + expect(api_map.super_and_sub?('Boolean', 'false')).to be(true) end it 'resolves aliases for YARD methods' do diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index 91f84b4c7..e24220ea9 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -7,12 +7,14 @@ before do File.open(File.join(temp_dir, 'Gemfile'), 'w') do |file| file.puts "source 'https://rubygems.org'" - file.puts "gem 'solargraph', path: #{File.expand_path('..', __dir__)}" + file.puts "gem 'solargraph', path: '#{File.expand_path('..', __dir__)}'" end output, status = Open3.capture2e("bundle install", chdir: temp_dir) raise "Failure installing bundle: #{output}" unless status.success? end + # @type cmd [Array] + # @return [String] def bundle_exec(*cmd) # run the command in the temporary directory with bundle exec output, status = Open3.capture2e("bundle exec #{cmd.join(' ')}", chdir: temp_dir) From 63b73aae91bf94e02ea4cc0dfe66c6f6e3086986 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 17:04:37 -0400 Subject: [PATCH 279/460] Drop debugging --- .github/workflows/linting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index cbf6db2c9..a67fff5df 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -63,7 +63,7 @@ jobs: - name: Overcommit run: | bundle exec overcommit --sign - OVERCOMMIT_DEBUG=1 SOLARGRAPH_ASSERTS=on bundle exec overcommit --run --diff origin/master + SOLARGRAPH_ASSERTS=on bundle exec overcommit --run --diff origin/master rubocop: name: rubocop runs-on: ubuntu-latest From e6ad7ec70165184bbffaf22cd0422a97d786bea6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 17:05:51 -0400 Subject: [PATCH 280/460] Remove debugging change --- .github/workflows/rspec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 93e13a0eb..d8f440124 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -84,4 +84,4 @@ jobs: run: bundle exec rake spec - name: Check PR coverage run: bundle exec rake undercover - # continue-on-error: true TODO: Restore before merging + continue-on-error: true From 755058edbb603c6e68d61137b3658fd51bf3f88c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 17:08:36 -0400 Subject: [PATCH 281/460] Dedupe, drop unneeded Bundler.with_unbundled_env --- spec/shell_spec.rb | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index 53754fa01..6d51b495a 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -20,11 +20,9 @@ # @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 + output, status = Open3.capture2e("bundle exec #{cmd.join(' ')}") + expect(status.success?).to be(true), "Command failed: #{output}" + output end after do @@ -178,17 +176,6 @@ def bundle_exec(*cmd) end end - # @type cmd [Array] - # @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')) } From fd1b994bfc9874f7e30b40f66d0719c7e3c3364f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 17:09:56 -0400 Subject: [PATCH 282/460] Add missing chdir flag --- spec/shell_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index 6d51b495a..6b88b33c9 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -20,7 +20,7 @@ # @return [String] def bundle_exec(*cmd) # run the command in the temporary directory with bundle exec - output, status = Open3.capture2e("bundle exec #{cmd.join(' ')}") + output, status = Open3.capture2e("bundle exec #{cmd.join(' ')}", chdir: temp_dir) expect(status.success?).to be(true), "Command failed: #{output}" output end From 82c94a675f989db005e0e29dba45dafe507c6157 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 12:16:23 -0400 Subject: [PATCH 283/460] Move Pin::Base#infer deprecation to assert system --- lib/solargraph/api_map.rb | 3 ++- lib/solargraph/pin/base.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 02f8f68ea..f41ae0038 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -793,7 +793,8 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if const.is_a?(Pin::Namespace) result.concat inner_get_methods(const.path, scope, visibility, deep, skip, true) elsif const.is_a?(Pin::Constant) - type = const.infer(self) + type = const.typify(self) + type = const.probe(self) unless type.defined? result.concat inner_get_methods(type.namespace, scope, visibility, deep, skip, true) if type.defined? else referenced_tag = ref.parametrized_tag diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index e6a630562..42b90b77c 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -528,7 +528,7 @@ def probe api_map # @param api_map [ApiMap] # @return [ComplexType] def infer api_map - Solargraph::Logging.logger.warn "WARNING: Pin #infer methods are deprecated. Use #typify or #probe instead." + Solargraph.assert_or_log(:pin_infer, 'WARNING: Pin #infer methods are deprecated. Use #typify or #probe instead.') type = typify(api_map) return type unless type.undefined? probe api_map From 6372fdf19b10d4ffa7408c250517d43e7327b17a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 12:25:04 -0400 Subject: [PATCH 284/460] Let extra line slide pending new constants PR --- .rubocop_todo.yml | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 89fd47c5d..65e9d3feb 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.80.2. +# using RuboCop version 1.80.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -433,7 +433,6 @@ Layout/TrailingWhitespace: Exclude: - 'lib/solargraph/language_server/message/client/register_capability.rb' - 'spec/api_map/config_spec.rb' - - 'spec/convention_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedMethods, AllowedPatterns. @@ -512,11 +511,6 @@ Lint/DuplicateMethods: - 'lib/solargraph/rbs_map/core_map.rb' - 'lib/solargraph/source/chain/link.rb' -# Configuration parameters: AllowComments, AllowEmptyLambdas. -Lint/EmptyBlock: - Exclude: - - 'spec/convention_spec.rb' - # Configuration parameters: AllowComments. Lint/EmptyClass: Exclude: @@ -530,6 +524,7 @@ Lint/EmptyClass: # Configuration parameters: AllowComments. Lint/EmptyFile: Exclude: + - 'lib/solargraph/foo.rb' - 'spec/fixtures/vendored/vendor/do_not_use.gemspec' # This cop supports unsafe autocorrection (--autocorrect-all). @@ -782,6 +777,7 @@ Metrics/ParameterLists: # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/PerceivedComplexity: Exclude: + - 'lib/solargraph/api_map.rb' - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/source/chain/call.rb' @@ -998,11 +994,6 @@ RSpec/DescribedClass: - 'spec/yard_map/mapper/to_method_spec.rb' - 'spec/yard_map/mapper_spec.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -RSpec/EmptyExampleGroup: - Exclude: - - 'spec/convention_spec.rb' - # This cop supports safe autocorrection (--autocorrect). RSpec/EmptyLineAfterFinalLet: Exclude: @@ -2143,7 +2134,6 @@ Style/RedundantParentheses: - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/pin/search.rb' - 'lib/solargraph/source.rb' - 'lib/solargraph/type_checker.rb' @@ -2299,7 +2289,6 @@ Style/StringLiterals: - 'lib/solargraph/library.rb' - 'lib/solargraph/parser/parser_gem/class_methods.rb' - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/pin/base.rb' - 'lib/solargraph/pin/conversions.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/parameter.rb' @@ -2614,7 +2603,6 @@ Layout/LineLength: - 'lib/solargraph/workspace.rb' - 'lib/solargraph/workspace/config.rb' - 'lib/solargraph/yard_map/mapper/to_method.rb' - - 'spec/api_map_spec.rb' - 'spec/complex_type_spec.rb' - 'spec/language_server/message/completion_item/resolve_spec.rb' - 'spec/language_server/message/extended/check_gem_version_spec.rb' From 3aa3ac090bd5f8a644ec3d7cb0a824148b57611b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 16:43:59 -0400 Subject: [PATCH 285/460] Fix merge error --- lib/solargraph/doc_map.rb | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index c68781502..255be68b1 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -40,32 +40,6 @@ def initialize requires, workspace, out: $stderr @out = out end - # @param out [IO] - # @param rebuild [Boolean] whether to rebuild the pins even if they are cached - # - # @return [void] - def cache_all! out, rebuild: false - load_serialized_gem_pins - PinCache.cache_core(out: out) unless PinCache.core? - gem_specs = uncached_gemspecs - # try any possible standard libraries, but be quiet about it - stdlib_specs = PinCache.possible_stdlibs.map { |stdlib| workspace.find_gem(stdlib, out: nil) }.compact - specs = (gem_specs + stdlib_specs) - specs.each do |gemspec| - cache(gemspec, out: out) - end - out&.puts "Documentation cached for all #{specs.length} gems." - - # do this after so that we prefer stdlib requires from gems, - # which are likely to be newer and have more pins - PinCache.cache_all_stdlibs(out: out) - - out&.puts 'Documentation cached for core, standard library and gems.' - - load_serialized_gem_pins - @uncached_gemspecs = [] - end - # @return [Array] def pins @pins ||= load_serialized_gem_pins + (workspace.global_environ&.pins || []) From 837ee4165251f296e34a5f0c1e973ee771941f7b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 16:46:34 -0400 Subject: [PATCH 286/460] Fix merge error --- lib/solargraph/api_map.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 49b323718..6c62ea819 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -194,7 +194,7 @@ def self.load directory # @param out [IO, nil] # @return [void] def cache_all_for_doc_map! out - doc_map.cache_all!(out) + doc_map.cache_doc_map_gems!(out) end # @param gemspec [Gem::Specification] From c32c86cc9fbdb45f97aed4c7ead1c20fcfbcaee2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 16:55:27 -0400 Subject: [PATCH 287/460] Point to branch immediately upstream --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index d731fc786..793831b14 100755 --- a/Rakefile +++ b/Rakefile @@ -54,7 +54,7 @@ def undercover cmd = 'bundle exec undercover ' \ '--simplecov coverage/combined/coverage.json ' \ '--exclude-files "Rakefile,spec/*,spec/**/*,lib/solargraph/version.rb" ' \ - '--compare origin/master' + '--compare origin/extract_gemspecs_logic_from_doc_map' output, status = Bundler.with_unbundled_env do Open3.capture2e(cmd) end From 1000778414d69ea41da4b5824aef6c19eee155f4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 16:59:09 -0400 Subject: [PATCH 288/460] Update spec expectations --- spec/api_map_method_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index a0546d6a4..412143982 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -136,10 +136,10 @@ class B describe '#cache_all_for_doc_map!' do it 'can cache gems without a bench' do api_map = Solargraph::ApiMap.new - doc_map = instance_double(Solargraph::DocMap, cache_all!: true) + doc_map = instance_double(Solargraph::DocMap, cache_doc_map_gems!: true) allow(Solargraph::DocMap).to receive(:new).and_return(doc_map) api_map.cache_all_for_doc_map!($stderr) - expect(doc_map).to have_received(:cache_all!).with($stderr) + expect(doc_map).to have_received(:cache_doc_map_gems!).with($stderr) end end From 8756a41751188108c7d6c5613f477ee565359768 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 17:27:38 -0400 Subject: [PATCH 289/460] Add spec --- spec/doc_map_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 71beb2df4..7a7e1b1c6 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -74,6 +74,23 @@ end end + context 'with an uncached but valid gemspec' do + let(:requires) { ['uncached_gem'] } + let(:pre_cache) { false } + let(:workspace) { instance_double(Solargraph::Workspace) } + + it 'tracks uncached_gemspecs' do + pincache = instance_double(Solargraph::PinCache, cache_stdlib_rbs_map: false) + uncached_gemspec = Gem::Specification.new('uncached_gem', '1.0.0') + allow(workspace).to receive_messages(fresh_pincache: pincache, resolve_require: [uncached_gemspec], stdlib_dependencies: [], + fetch_dependencies: []) + allow(Gem::Specification).to receive(:find_by_path).with('uncached_gem').and_return(uncached_gemspec) + allow(workspace).to receive(:global_environ).and_return(Solargraph::Environ.new) + allow(pincache).to receive(:deserialize_combined_pin_cache).with(uncached_gemspec).and_return(nil) + expect(doc_map.uncached_gemspecs).to eq([uncached_gemspec]) + end + end + context 'with require as bundle/require' do it 'imports all gems when bundler/require used' do doc_map_with_bundler_require = described_class.new(['bundler/require'], workspace, out: nil) From 6eef00f0e71b5759c05851f217af6275fa0ad1c9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 17:30:05 -0400 Subject: [PATCH 290/460] Add pending mark --- spec/doc_map_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 7a7e1b1c6..093f5c665 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -39,6 +39,8 @@ end it 'tracks unresolved requires' do + pending("rspec-mocks being able to be resolved when solargraph-rspec is in the bundle") + # These are auto-required by solargraph-rspec in case the bundle # includes these gems. In our case, it doesn't! unprovided_solargraph_rspec_requires = [ From e538f57e0d55c9eb20498c4ff9c779af48c91ae5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 17:44:48 -0400 Subject: [PATCH 291/460] Drop spec we are not ready for yet --- spec/doc_map_spec.rb | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 093f5c665..7fee8e760 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -33,30 +33,6 @@ end end - context 'with an invalid require' do - let(:requires) do - ['not_a_gem'] - end - - it 'tracks unresolved requires' do - pending("rspec-mocks being able to be resolved when solargraph-rspec is in the bundle") - - # These are auto-required by solargraph-rspec in case the bundle - # includes these gems. In our case, it doesn't! - unprovided_solargraph_rspec_requires = [ - 'rspec-rails', - 'actionmailer', - 'activerecord', - 'shoulda-matchers', - 'rspec-sidekiq', - 'airborne', - 'activesupport' - ] - expect(doc_map.unresolved_requires - unprovided_solargraph_rspec_requires) - .to eq(['not_a_gem']) - end - end - context 'when deserialization takes a while' do let(:pre_cache) { false } let(:requires) { ['backport'] } From bd3ce0b0ed43efb0b1f3401e595a4e2a1dd4d6c7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 18:00:20 -0400 Subject: [PATCH 292/460] Move find_gem implementation, add more specs --- lib/solargraph/workspace.rb | 4 +- lib/solargraph/workspace/gemspecs.rb | 11 +++ spec/workspace/gemspecs_find_gem_spec.rb | 102 +++++++++++++++++++++++ 3 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 spec/workspace/gemspecs_find_gem_spec.rb diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 1531509af..69ad12bae 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -167,9 +167,7 @@ def rbs_collection_config_path # # @return [Gem::Specification, nil] def find_gem name, version = nil - Gem::Specification.find_by_name(name, version) - rescue Gem::MissingSpecError - nil + gemspecs.find_gem(name, version) end # Synchronize the workspace from the provided updater. diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 1f4fef27b..4b4eb0265 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -68,6 +68,17 @@ def resolve_require require [gemspec_or_preference(gemspec)] end + # @param name [String] + # @param version [String, nil] + # @param out [IO, nil] output stream for logging + # + # @return [Gem::Specification, nil] + def find_gem name, version = nil, out = nil + Gem::Specification.find_by_name(name, version) + rescue Gem::MissingSpecError + nil + end + # @param gemspec [Gem::Specification] # @param out[IO, nil] output stream for logging # diff --git a/spec/workspace/gemspecs_find_gem_spec.rb b/spec/workspace/gemspecs_find_gem_spec.rb new file mode 100644 index 000000000..76caddab3 --- /dev/null +++ b/spec/workspace/gemspecs_find_gem_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'fileutils' +require 'tmpdir' +require 'rubygems/commands/install_command' + +describe Solargraph::Workspace::Gemspecs, '#find_gem' do + subject(:gemspec) { gemspecs.find_gem(name, version, out: out) } + + let(:gemspecs) { described_class.new(dir_path) } + let(:out) { StringIO.new } + + context 'with local bundle' do + let(:dir_path) { File.realpath(Dir.pwd) } + + context 'with solargraph from bundle' do + let(:name) { 'solargraph' } + let(:version) { nil } + + it 'returns the gem' do + expect(gemspec.name).to eq(name) + end + end + + context 'with random from core' do + let(:name) { 'random' } + let(:version) { nil } + + it 'returns no gemspec' do + expect(gemspec).to be_nil + end + + it 'does not complain' do + expect(out.string).to be_empty + end + end + + context 'with ripper from core' do + let(:name) { 'ripper' } + let(:version) { nil } + + it 'returns no gemspec' do + expect(gemspec).to be_nil + end + end + + context 'with base64 from stdlib' do + let(:name) { 'base64' } + let(:version) { nil } + + it 'returns a gemspec' do + expect(gemspec).not_to be_nil + end + end + + context 'with gem not in bundle' do + let(:name) { 'checkoff' } + let(:version) { nil } + + it 'returns no gemspec' do + expect(gemspec).to be_nil + end + + it 'complains' do + pending("implementation") + gemspec + + expect(out.string).to include('install the gem checkoff ') + end + end + + context 'with gem not in bundle but no logger' do + let(:name) { 'checkoff' } + let(:version) { nil } + let(:out) { nil } + + it 'returns no gemspec' do + expect(gemspec).to be_nil + end + + it 'does not fail' do + expect { gemspec }.not_to raise_error + end + end + + context 'with gem not in bundle with version' do + let(:name) { 'checkoff' } + let(:version) { '1.0.0' } + + it 'returns no gemspec' do + expect(gemspec).to be_nil + end + + it 'complains' do + pending("implementation") + gemspec + + expect(out.string).to include('install the gem checkoff:1.0.0') + end + end + end +end From 930d3c6c6cbd7987f6ba3754b504eb242088b45a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 20:13:23 -0400 Subject: [PATCH 293/460] Keep workspace directories as absolute paths --- lib/solargraph/workspace.rb | 10 ++++++++-- spec/workspace_spec.rb | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index a8325ea89..de4b11084 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -19,11 +19,17 @@ class Workspace attr_reader :gemnames alias source_gems gemnames - # @param directory [String] + # @param directory [String] TODO: Remove '' and '*' special cases # @param config [Config, nil] # @param server [Hash] def initialize directory = '', config = nil, server = {} - @directory = directory + raise ArgumentError, 'directory must be a String' unless directory.is_a?(String) + + @directory = if ['*', ''].include?(directory) + directory + else + File.absolute_path(directory) + end @config = config @server = server load_sources diff --git a/spec/workspace_spec.rb b/spec/workspace_spec.rb index 572c3e131..d9c7ca415 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -124,7 +124,8 @@ it "uses configured require paths" do workspace = Solargraph::Workspace.new('spec/fixtures/workspace') - expect(workspace.require_paths).to eq(['spec/fixtures/workspace/lib', 'spec/fixtures/workspace/ext']) + expect(workspace.require_paths).to eq([File.absolute_path('spec/fixtures/workspace/lib'), + File.absolute_path('spec/fixtures/workspace/ext')]) end it 'ignores gemspecs in excluded directories' do From edaa8194ad3e6a1e2f19d22a2772fe906a1ec6d2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 20:49:07 -0400 Subject: [PATCH 294/460] Remove dead code These were moved to workspace/require_paths.rb in https://github.com/castwide/solargraph/pull/1062 --- lib/solargraph/workspace.rb | 17 ----------------- spec/workspace_spec.rb | 9 +-------- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index a8325ea89..07cf26f09 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -114,23 +114,6 @@ def would_require? path false end - # True if the workspace contains at least one gemspec file. - # - # @return [Boolean] - def gemspec? - !gemspecs.empty? - end - - # Get an array of all gemspec files in the workspace. - # - # @return [Array] - def gemspecs - return [] if directory.empty? || directory == '*' - @gemspecs ||= Dir[File.join(directory, '**/*.gemspec')].select do |gs| - config.allow? gs - end - end - # @return [String, nil] def rbs_collection_path @gem_rbs_collection ||= read_rbs_collection_path diff --git a/spec/workspace_spec.rb b/spec/workspace_spec.rb index 572c3e131..db8141249 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -68,13 +68,6 @@ }.not_to raise_error end - it "detects gemspecs in workspaces" do - gemspec_file = File.join(dir_path, 'test.gemspec') - File.write(gemspec_file, '') - expect(workspace.gemspec?).to be(true) - expect(workspace.gemspecs).to eq([gemspec_file]) - end - it "generates default require path" do expect(workspace.require_paths).to eq([File.join(dir_path, 'lib')]) end @@ -130,7 +123,7 @@ it 'ignores gemspecs in excluded directories' do # vendor/**/* is excluded by default workspace = Solargraph::Workspace.new('spec/fixtures/vendored') - expect(workspace.gemspecs).to be_empty + expect(workspace.require_paths).to eq(['spec/fixtures/vendored/lib']) end it 'rescues errors loading files into sources' do From c3457f43615c55a723908a1d8bf8eb3be8d9d960 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 20:57:03 -0400 Subject: [PATCH 295/460] Fix merge issue --- lib/solargraph/workspace.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index bbac5a79e..3cbc75fc3 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -198,16 +198,6 @@ def rbs_collection_config_path end end - # @param name [String] - # @param version [String, nil] - # - # @return [Gem::Specification, nil] - def find_gem name, version = nil - Gem::Specification.find_by_name(name, version) - rescue Gem::MissingSpecError - nil - end - # @param name [String] # @param version [String, nil] # From 588ce796866d828e69dce0d6e52e35be4ae8bc86 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 21:01:48 -0400 Subject: [PATCH 296/460] Use find_gem in another location --- lib/solargraph/shell.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 793537ab6..d3aa81c46 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -180,7 +180,7 @@ def uncache *gems next end - spec = Gem::Specification.find_by_name(gem) + spec = workspace.find_gem(gem) workspace.uncache_gem(spec, out: $stdout) end end From adb34e55d3ca11e84191acbf4ccea18643da7f12 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 21:02:51 -0400 Subject: [PATCH 297/460] Use find_gem in another location --- lib/solargraph/shell.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 5c3fc0581..4284838d3 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -129,7 +129,7 @@ def uncache *gems next end - spec = Gem::Specification.find_by_name(gem) + spec = api_map.workspace.find_gem(gem) PinCache.uncache_gem(spec, out: $stdout) end end From 82bfce70146f35a112e46386b574334beea01bfc Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 22:05:12 -0400 Subject: [PATCH 298/460] Add rebuild option --- lib/solargraph/shell.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 4f114c5fe..c66dd9c32 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -125,23 +125,27 @@ def cache gem, version = nil The 'core' argument can be used to cache the type documentation for the core Ruby libraries. + If the library is already cached, it will be rebuilt if the + --rebuild option is set. + Cached documentation is stored in #{PinCache.base_dir}, which can be stored between CI runs. ) + option :rebuild, type: :boolean, desc: 'Rebuild existing documentation', default: false # @param names [Array] # @return [void] def gems *names - $stderr.puts("Caching these gems: #{names}") # print time with ms workspace = Solargraph::Workspace.new('.') api_map = Solargraph::ApiMap.load(Dir.pwd) if names.empty? - api_map.cache_all!($stdout) + api_map.cache_all!($stdout, rebuild: options[:rebuild]]) else + $stderr.puts("Caching these gems: #{names}") names.each do |name| if name == 'core' - PinCache.cache_core(out: $stdout) + PinCache.cache_core(out: $stdout) if !PinCache.core? || options[:rebuild] next end From 0fa1ad149dcb4799f37850bbd2670d1b5644eddb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 22:09:27 -0400 Subject: [PATCH 299/460] Fix syntax --- lib/solargraph/shell.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index c66dd9c32..68e7bf661 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -140,7 +140,7 @@ def gems *names api_map = Solargraph::ApiMap.load(Dir.pwd) if names.empty? - api_map.cache_all!($stdout, rebuild: options[:rebuild]]) + api_map.cache_all!($stdout, rebuild: options[:rebuild]) else $stderr.puts("Caching these gems: #{names}") names.each do |name| From 97a3ceb841eafb6e2d69c26ec39c822c711abc6d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 22:15:39 -0400 Subject: [PATCH 300/460] Fix workspace calls in shell.rb --- lib/solargraph/shell.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 4284838d3..907daccc6 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -118,6 +118,7 @@ def cache gem, version = nil # @return [void] def uncache *gems raise ArgumentError, 'No gems specified.' if gems.empty? + workspace = Solargraph::Workspace.new(Dir.pwd) gems.each do |gem| if gem == 'core' PinCache.uncache_core @@ -129,7 +130,7 @@ def uncache *gems next end - spec = api_map.workspace.find_gem(gem) + spec = workspace.find_gem(gem) PinCache.uncache_gem(spec, out: $stdout) end end @@ -140,12 +141,13 @@ def uncache *gems # @return [void] def gems *names api_map = ApiMap.load('.') + workspace = api_map.workspace if names.empty? Gem::Specification.to_a.each { |spec| do_cache spec, api_map } STDERR.puts "Documentation cached for all #{Gem::Specification.count} gems." else names.each do |name| - spec = api_map.workspace.find_gem(*name.split('=')) + spec = workspace.find_gem(*name.split('=')) do_cache spec, api_map rescue Gem::MissingSpecError warn "Gem '#{name}' not found" From 5a5b865963c7a35539ff0df315707232d2f913ae Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 22:18:50 -0400 Subject: [PATCH 301/460] Pass through rebuild flag --- lib/solargraph/api_map.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index b85fa8a0b..532ea62a2 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -190,9 +190,10 @@ def self.load directory end # @param out [IO, nil] + # @param rebuild [Boolean] whether to rebuild the pins even if they are cached # @return [void] - def cache_all!(out) - @doc_map.cache_all!(out) + def cache_all!(out, rebuild: false) + @doc_map.cache_all!(out, rebuild: rebuild) end # @param gemspec [Gem::Specification] From c2948337728df198fb410212b1cdeb63cb33e995 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 22:48:19 -0400 Subject: [PATCH 302/460] Fix merge issue --- spec/api_map_method_spec.rb | 2 +- spec/workspace_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index 412143982..857ef6897 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -139,7 +139,7 @@ class B doc_map = instance_double(Solargraph::DocMap, cache_doc_map_gems!: true) allow(Solargraph::DocMap).to receive(:new).and_return(doc_map) api_map.cache_all_for_doc_map!($stderr) - expect(doc_map).to have_received(:cache_doc_map_gems!).with($stderr) + expect(doc_map).to have_received(:cache_doc_map_gems!).with($stderr, rebuild: false) end end diff --git a/spec/workspace_spec.rb b/spec/workspace_spec.rb index 1db09770d..37275bb86 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -124,7 +124,7 @@ it 'ignores gemspecs in excluded directories' do # vendor/**/* is excluded by default workspace = Solargraph::Workspace.new('spec/fixtures/vendored') - expect(workspace.require_paths).to eq(['spec/fixtures/vendored/lib']) + expect(workspace.require_paths).to eq([File.absolute_path('spec/fixtures/vendored/lib')]) end it 'rescues errors loading files into sources' do From 3a7b7e7bc980cfc22371348362d98b0d99e78408 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 22:51:56 -0400 Subject: [PATCH 303/460] Document 'solargraph init' command in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7f344c712..febc972ee 100755 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ Plug-ins and extensions are available for the following editors: Solargraph's behavior can be controlled via optional [configuration](https://solargraph.org/guides/configuration) files. The highest priority file is a `.solargraph.yml` file at the root of the project. If not present, any global configuration at `~/.config/solargraph/config.yml` will apply. The path to the global configuration can be overridden with the `SOLARGRAPH_GLOBAL_CONFIG` environment variable. +Use `bundle exec solargraph init` to create a configuration file. + ### Plugins Solargraph supports [plugins](https://solargraph.org/guides/plugins) that implement their own Solargraph features, such as diagnostics reporters and conventions to provide LSP features and type-checking, e.g. for frameworks which use metaprogramming and/or DSLs. From 2ea9620b7e1b96c41d1381d674b0dd4c9d5dc4a5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 08:03:08 -0400 Subject: [PATCH 304/460] Add future spec for issue that blocks existing one --- spec/doc_map_spec.rb | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 54b423f99..51acdbb93 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -33,12 +33,30 @@ end end + context 'understands rspec + rspec-mocks require pattern' do + let(:requires) do + ['rspec-mocks'] + end + + it 'generates pins from gems' do + pending('handling dependencies from conventions as gem names, not requires') + + ns_pin = doc_map.pins.find { |pin| pin.path == 'RSpec::Mocks' } + expect(ns_pin).to be_a(Solargraph::Pin::Namespace) + end + end + context 'with an invalid require' do let(:requires) do ['not_a_gem'] end - it 'tracks unresolved requires' do + # expected: ["not_a_gem"] + # got: ["not_a_gem", "rspec-mocks"] + # + # This is a gem name vs require name issue coming from conventions + # - will pass once the above context passes + xit 'tracks unresolved requires' do # These are auto-required by solargraph-rspec in case the bundle # includes these gems. In our case, it doesn't! unprovided_solargraph_rspec_requires = [ From 0dbf35846dec2b3a80d31699258598cf253379a9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 08:17:41 -0400 Subject: [PATCH 305/460] Disable new spec --- spec/doc_map_spec.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 51acdbb93..b0efb7d44 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -94,7 +94,19 @@ end context 'with require as bundle/require' do - it 'imports all gems when bundler/require used' do + # @todo need to debug this failure in CI: + # + # Errno::ENOENT: + # No such file or directory - /opt/hostedtoolcache/Ruby/3.3.9/x64/lib/ruby/3.3.0/gems/bundler-2.5.22 + # # ./lib/solargraph/yardoc.rb:29:in `cache' + # # ./lib/solargraph/gem_pins.rb:48:in `build_yard_pins' + # # ./lib/solargraph/doc_map.rb:86:in `cache_yard_pins' + # # ./lib/solargraph/doc_map.rb:117:in `cache' + # # ./lib/solargraph/doc_map.rb:75:in `block in cache_all!' + # # ./lib/solargraph/doc_map.rb:74:in `each' + # # ./lib/solargraph/doc_map.rb:74:in `cache_all!' + # # ./spec/doc_map_spec.rb:99:in `block (3 levels) in ' + xit 'imports all gems when bundler/require used' do doc_map_with_bundler_require = described_class.new(['bundler/require'], [], workspace) doc_map_with_bundler_require.cache_all!(nil) expect(doc_map_with_bundler_require.pins.length - plain_doc_map.pins.length).to be_positive From 4c181eccd77b202a16798a486ec379b517809ef2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 08:26:29 -0400 Subject: [PATCH 306/460] Refactor doc_map_spec --- spec/doc_map_spec.rb | 193 +++++++++++++++++++++++++++++++------------ 1 file changed, 142 insertions(+), 51 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 3f77cd7cc..f557366e0 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -1,82 +1,173 @@ # frozen_string_literal: true +require 'bundler' +require 'benchmark' + describe Solargraph::DocMap do - before :all do - # We use ast here because it's a known dependency. - gemspec = Gem::Specification.find_by_name('ast') - yard_pins = Solargraph::GemPins.build_yard_pins([], gemspec) - Solargraph::PinCache.serialize_yard_gem(gemspec, yard_pins) + subject(:doc_map) do + described_class.new(requires, workspace, out: out) + end + + let(:out) { StringIO.new } + let(:pre_cache) { true } + let(:requires) { [] } + + let(:workspace) do + Solargraph::Workspace.new(Dir.pwd) end - let(:workspace) { Solargraph::Workspace.new(Dir.pwd) } + let(:plain_doc_map) { described_class.new([], workspace, out: nil) } - it 'generates pins from gems' do - doc_map = Solargraph::DocMap.new(['ast'], workspace) - doc_map.cache_all!($stderr) - node_pin = doc_map.pins.find { |pin| pin.path == 'AST::Node' } - expect(node_pin).to be_a(Solargraph::Pin::Namespace) + before do + doc_map.cache_all!(nil) if pre_cache end - it 'tracks unresolved requires' do - doc_map = Solargraph::DocMap.new(['not_a_gem'], workspace) - expect(doc_map.unresolved_requires).to include('not_a_gem') + context 'with a require in solargraph test bundle' do + let(:requires) do + ['ast'] + end + + it 'generates pins from gems' do + node_pin = doc_map.pins.find { |pin| pin.path == 'AST::Node' } + expect(node_pin).to be_a(Solargraph::Pin::Namespace) + end end - it 'tracks uncached_gemspecs' do - gemspec = Gem::Specification.new do |spec| - spec.name = 'not_a_gem' - spec.version = '1.0.0' + context 'understands rspec + rspec-mocks require pattern' do + let(:requires) do + ['rspec-mocks'] + end + + it 'generates pins from gems' do + pending('handling dependencies from conventions as gem names, not requires') + + ns_pin = doc_map.pins.find { |pin| pin.path == 'RSpec::Mocks' } + expect(ns_pin).to be_a(Solargraph::Pin::Namespace) end - allow(Gem::Specification).to receive(:find_by_path).and_return(gemspec) - doc_map = Solargraph::DocMap.new(['not_a_gem'], workspace) - expect(doc_map.uncached_yard_gemspecs).to eq([gemspec]) - expect(doc_map.uncached_rbs_collection_gemspecs).to eq([gemspec]) end - it 'imports all gems when bundler/require used' do - plain_doc_map = Solargraph::DocMap.new([], workspace) - doc_map_with_bundler_require = Solargraph::DocMap.new(['bundler/require'], workspace) + context 'with an invalid require' do + let(:requires) do + ['not_a_gem'] + end + + # expected: ["not_a_gem"] + # got: ["not_a_gem", "rspec-mocks"] + # + # This is a gem name vs require name issue coming from conventions + # - will pass once the above context passes + xit 'tracks unresolved requires' do + # These are auto-required by solargraph-rspec in case the bundle + # includes these gems. In our case, it doesn't! + unprovided_solargraph_rspec_requires = [ + 'rspec-rails', + 'actionmailer', + 'activerecord', + 'shoulda-matchers', + 'rspec-sidekiq', + 'airborne', + 'activesupport' + ] + expect(doc_map.unresolved_requires - unprovided_solargraph_rspec_requires) + .to eq(['not_a_gem']) + end + end - expect(doc_map_with_bundler_require.pins.length - plain_doc_map.pins.length).to be_positive + context 'with an uncached but valid gemspec' do + let(:requires) { ['uncached_gem'] } + let(:pre_cache) { false } + let(:workspace) { instance_double(Solargraph::Workspace) } + + it 'tracks uncached_gemspecs' do + pending('moving cache_stdlib_rbs_map into PinCache') + + pincache = instance_double(Solargraph::PinCache, cache_stdlib_rbs_map: false) + uncached_gemspec = Gem::Specification.new('uncached_gem', '1.0.0') + allow(workspace).to receive_messages(fresh_pincache: pincache, resolve_require: [uncached_gemspec], stdlib_dependencies: [], + fetch_dependencies: []) + allow(Gem::Specification).to receive(:find_by_path).with('uncached_gem').and_return(uncached_gemspec) + allow(workspace).to receive(:global_environ).and_return(Solargraph::Environ.new) + allow(pincache).to receive(:deserialize_combined_pin_cache).with(uncached_gemspec).and_return(nil) + expect(doc_map.uncached_gemspecs).to eq([uncached_gemspec]) + end end - 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. - allow(Solargraph.logger).to receive(:warn) - Solargraph::DocMap.new(['set'], workspace) - expect(Solargraph.logger).not_to have_received(:warn).with(/path set/) + context 'with require as bundle/require' do + # @todo need to debug this failure in CI: + # + # Errno::ENOENT: + # No such file or directory - /opt/hostedtoolcache/Ruby/3.3.9/x64/lib/ruby/3.3.0/gems/bundler-2.5.22 + # # ./lib/solargraph/yardoc.rb:29:in `cache' + # # ./lib/solargraph/gem_pins.rb:48:in `build_yard_pins' + # # ./lib/solargraph/doc_map.rb:86:in `cache_yard_pins' + # # ./lib/solargraph/doc_map.rb:117:in `cache' + # # ./lib/solargraph/doc_map.rb:75:in `block in cache_all!' + # # ./lib/solargraph/doc_map.rb:74:in `each' + # # ./lib/solargraph/doc_map.rb:74:in `cache_all!' + # # ./spec/doc_map_spec.rb:99:in `block (3 levels) in ' + xit 'imports all gems when bundler/require used' do + doc_map_with_bundler_require = described_class.new(['bundler/require'], [], workspace, out: nil) + doc_map_with_bundler_require.cache_doc_map_gems!(nil) + expect(doc_map_with_bundler_require.pins.length - plain_doc_map.pins.length).to be_positive + end end - it 'ignores nil requires' do - expect { Solargraph::DocMap.new([nil], workspace) }.not_to raise_error + context 'with a require not needed by Ruby core' do + let(:requires) { ['set'] } + + it 'does not warn' 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. + allow(Solargraph.logger).to receive(:warn) + doc_map + expect(Solargraph.logger).not_to have_received(:warn).with(/path set/) + end end - it 'ignores empty requires' do - expect { Solargraph::DocMap.new([''], workspace) }.not_to raise_error + context 'with a nil require' do + let(:requires) { [nil] } + + it 'does not raise error' do + expect { doc_map }.not_to raise_error + end end - it 'collects dependencies' do - doc_map = Solargraph::DocMap.new(['rspec'], workspace) - expect(doc_map.dependencies.map(&:name)).to include('rspec-core') + context 'with an empty require' do + let(:requires) { [''] } + + it 'does not raise error' do + expect { doc_map }.not_to raise_error + end end - it 'includes convention requires from environ' do - dummy_convention = Class.new(Solargraph::Convention::Base) do - def global(doc_map) - Solargraph::Environ.new( - requires: ['convention_gem1', 'convention_gem2'] - ) - end + context 'with a require that has dependencies' do + let(:requires) { ['rspec'] } + + it 'collects dependencies' do + expect(doc_map.dependencies.map(&:name)).to include('rspec-core') end + end - Solargraph::Convention.register dummy_convention + context 'with convention' do + let(:pre_cache) { false } - doc_map = Solargraph::DocMap.new(['original_gem'], workspace) + it 'includes convention requires from environ' do + dummy_convention = Class.new(Solargraph::Convention::Base) do + def global(doc_map) + Solargraph::Environ.new( + requires: ['convention_gem1', 'convention_gem2'] + ) + end + end + + Solargraph::Convention.register dummy_convention - expect(doc_map.requires).to include('original_gem', 'convention_gem1', 'convention_gem2') + doc_map = Solargraph::DocMap.new(['original_gem'], workspace) - # Clean up the registered convention - Solargraph::Convention.unregister dummy_convention + expect(doc_map.requires).to include('original_gem', 'convention_gem1', 'convention_gem2') + ensure + # Clean up the registered convention + Solargraph::Convention.unregister dummy_convention + end end end From daf863331cc3959036f607cc774de4ad06a7a002 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 08:41:43 -0400 Subject: [PATCH 307/460] Mark spec with xit --- spec/doc_map_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index b0efb7d44..0d8817116 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -38,7 +38,9 @@ ['rspec-mocks'] end - it 'generates pins from gems' do + # This is a gem name vs require name issue - works under + # solargraph-rspec, but not without + xit 'generates pins from gems' do pending('handling dependencies from conventions as gem names, not requires') ns_pin = doc_map.pins.find { |pin| pin.path == 'RSpec::Mocks' } From 7762471ebddfdfc1a1a11f9f2f6c91a3e155ebfa Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 08:44:35 -0400 Subject: [PATCH 308/460] Enable xit'ed spec --- spec/doc_map_spec.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index f557366e0..18fef6ff6 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -51,12 +51,7 @@ ['not_a_gem'] end - # expected: ["not_a_gem"] - # got: ["not_a_gem", "rspec-mocks"] - # - # This is a gem name vs require name issue coming from conventions - # - will pass once the above context passes - xit 'tracks unresolved requires' do + it 'tracks unresolved requires' do # These are auto-required by solargraph-rspec in case the bundle # includes these gems. In our case, it doesn't! unprovided_solargraph_rspec_requires = [ From 7a88bd05cc8697442136f10c08816e756f086b39 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 09:23:52 -0400 Subject: [PATCH 309/460] Add assert --- lib/solargraph/workspace/gemspecs.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 4b4eb0265..a796cbabf 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -57,6 +57,7 @@ def resolve_require require file = "lib/#{require}.rb" # @sg-ignore Unresolved call to files gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file } + Solargraph.assert_or_log(:gemspecs_resolve_require_guess, "Could not find require based on #{require.inspect}") if gemspec.nil? rescue Gem::MissingSpecError logger.debug do "Require path #{require} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}" From 436f9708a1e84af78da63c5872944a74ded2b77a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 09:26:15 -0400 Subject: [PATCH 310/460] Add failing spec --- spec/workspace/gemspecs_resolve_require_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb index 2807c3384..b3fefa840 100644 --- a/spec/workspace/gemspecs_resolve_require_spec.rb +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -214,6 +214,18 @@ def configure_bundler_spec stub_value end end + context 'with Gemfile and deep require into a gem' do + before { add_bundle } + + let(:require) { 'bundler/gem_tasks' } + + it 'returns gems' do + pending('improved logic for require lookups') + + expect(specs&.map(&:name)).to include('bundler') + end + end + context 'with Gemfile but an unknown gem' do before { add_bundle } From 70c60a4e36b8f459341d2e999a5294bbc2303acf Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 10:11:47 -0400 Subject: [PATCH 311/460] Use xit to exclude spec that fails in some environment combinations --- spec/workspace/gemspecs_resolve_require_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb index b3fefa840..eb2ab1f58 100644 --- a/spec/workspace/gemspecs_resolve_require_spec.rb +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -219,7 +219,7 @@ def configure_bundler_spec stub_value let(:require) { 'bundler/gem_tasks' } - it 'returns gems' do + xit 'returns gems' do pending('improved logic for require lookups') expect(specs&.map(&:name)).to include('bundler') From 602d803000c3d598fa005cda362c78d6efc562e0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 10:20:20 -0400 Subject: [PATCH 312/460] Add another regression spec --- spec/workspace/gemspecs_resolve_require_spec.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb index eb2ab1f58..4ffcd0bd1 100644 --- a/spec/workspace/gemspecs_resolve_require_spec.rb +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -214,7 +214,7 @@ def configure_bundler_spec stub_value end end - context 'with Gemfile and deep require into a gem' do + context 'with Gemfile and deep require into a possibly-core gem' do before { add_bundle } let(:require) { 'bundler/gem_tasks' } @@ -226,6 +226,16 @@ def configure_bundler_spec stub_value end end + context 'with Gemfile and deep require into a gem' do + before { add_bundle } + + let(:require) { 'rspec/mocks' } + + it 'returns gems' do + expect(specs&.map(&:name)).to include('rspec-mocks') + end + end + context 'with Gemfile but an unknown gem' do before { add_bundle } From 569bf4f3d97120c8264657a9322b5ad2f83140d1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 10:41:48 -0400 Subject: [PATCH 313/460] Improve assertion --- lib/solargraph/pin_cache.rb | 17 +++++++++++++++++ lib/solargraph/workspace/gemspecs.rb | 4 +++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 2a0ec4639..c72a6a770 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -7,6 +7,23 @@ module PinCache class << self include Logging + # @return [Array] a list of possible standard library names + def possible_stdlibs + @possible_stdlibs ||= begin + # all dirs and .rb files in Gem::RUBYGEMS_DIR + Dir.glob(File.join(Gem::RUBYGEMS_DIR, '*')).map do |file_or_dir| + basename = File.basename(file_or_dir) + # remove .rb + basename = basename[0..-4] if basename.end_with?('.rb') + basename + end.sort.uniq + rescue StandardError => e + logger.info { "Failed to get possible stdlibs: #{e.message}" } + logger.debug { e.backtrace.join("\n") } + [] + end + end + # The base directory where cached YARD documentation and serialized pins are serialized # # @return [String] diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index a796cbabf..40826879a 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -57,7 +57,9 @@ def resolve_require require file = "lib/#{require}.rb" # @sg-ignore Unresolved call to files gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file } - Solargraph.assert_or_log(:gemspecs_resolve_require_guess, "Could not find require based on #{require.inspect}") if gemspec.nil? + if gemspec.nil? && !PinCache.possible_stdlibs.include?(gem_name_guess) + Solargraph.assert_or_log(:gemspecs_resolve_require_guess, "Could not find require based on #{require.inspect}") + end rescue Gem::MissingSpecError logger.debug do "Require path #{require} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}" From 396c11406b24dd5ce5fc163a333bddae8c6b20d7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 11:54:05 -0400 Subject: [PATCH 314/460] Drop assert, add gem lookup workaround for conventions, enable spec --- lib/solargraph/workspace/gemspecs.rb | 18 +++++++++++---- spec/doc_map_spec.rb | 34 +++++----------------------- 2 files changed, 19 insertions(+), 33 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 40826879a..31c5908c3 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -57,17 +57,23 @@ def resolve_require require file = "lib/#{require}.rb" # @sg-ignore Unresolved call to files gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file } - if gemspec.nil? && !PinCache.possible_stdlibs.include?(gem_name_guess) - Solargraph.assert_or_log(:gemspecs_resolve_require_guess, "Could not find require based on #{require.inspect}") - end rescue Gem::MissingSpecError logger.debug do "Require path #{require} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}" end - [] end end - return nil if gemspec.nil? + # @todo the 'requires' provided in Environ is being used + # by plugins to pass gem names instead of require paths + # - need to expand Environ to provide a place to put gem + # names and get new plugins out before retiring this. + gemspec ||= find_gem(gem_name_guess) + if gemspec.nil? + if !PinCache.possible_stdlibs.include?(gem_name_guess) && !['rspec-rails', 'actionmailer', 'activesupport', 'activerecord', 'shoulda-matchers', 'rspec-sidekiq', 'airborne'].include?(gem_name_guess) + Solargraph.assert_or_log(:gemspecs_resolve_require_guess, "Could not find require based on #{require.inspect}") + end + return nil + end [gemspec_or_preference(gemspec)] end @@ -156,6 +162,7 @@ def auto_required_gemspecs_from_bundler logger.info("Could not find #{lazy_spec.name}:#{lazy_spec.version} with " \ 'find_by_name, falling back to guess') # can happen in local filesystem references + # TODO: should this be resolve_require or find_gem? specs = resolve_require lazy_spec.name logger.warn "Gem #{lazy_spec.name} #{lazy_spec.version} from bundle not found: #{e}" if specs.nil? next specs @@ -190,6 +197,7 @@ def gemspecs_required_from_external_bundle rescue Gem::MissingSpecError => e logger.info("Could not find #{name}:#{version} with find_by_name, falling back to guess") # can happen in local filesystem references + # TODO: should this be resolve_require or find_gem? specs = resolve_require name logger.warn "Gem #{name} #{version} from bundle not found: #{e}" if specs.nil? next specs diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 18fef6ff6..fad6d3363 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -33,19 +33,6 @@ end end - context 'understands rspec + rspec-mocks require pattern' do - let(:requires) do - ['rspec-mocks'] - end - - it 'generates pins from gems' do - pending('handling dependencies from conventions as gem names, not requires') - - ns_pin = doc_map.pins.find { |pin| pin.path == 'RSpec::Mocks' } - expect(ns_pin).to be_a(Solargraph::Pin::Namespace) - end - end - context 'with an invalid require' do let(:requires) do ['not_a_gem'] @@ -88,21 +75,9 @@ end context 'with require as bundle/require' do - # @todo need to debug this failure in CI: - # - # Errno::ENOENT: - # No such file or directory - /opt/hostedtoolcache/Ruby/3.3.9/x64/lib/ruby/3.3.0/gems/bundler-2.5.22 - # # ./lib/solargraph/yardoc.rb:29:in `cache' - # # ./lib/solargraph/gem_pins.rb:48:in `build_yard_pins' - # # ./lib/solargraph/doc_map.rb:86:in `cache_yard_pins' - # # ./lib/solargraph/doc_map.rb:117:in `cache' - # # ./lib/solargraph/doc_map.rb:75:in `block in cache_all!' - # # ./lib/solargraph/doc_map.rb:74:in `each' - # # ./lib/solargraph/doc_map.rb:74:in `cache_all!' - # # ./spec/doc_map_spec.rb:99:in `block (3 levels) in ' - xit 'imports all gems when bundler/require used' do - doc_map_with_bundler_require = described_class.new(['bundler/require'], [], workspace, out: nil) - doc_map_with_bundler_require.cache_doc_map_gems!(nil) + it 'imports all gems when bundler/require used' do + doc_map_with_bundler_require = described_class.new(['bundler/require'], workspace, out: nil) + doc_map_with_bundler_require.cache_all!(nil) expect(doc_map_with_bundler_require.pins.length - plain_doc_map.pins.length).to be_positive end end @@ -159,6 +134,9 @@ def global(doc_map) doc_map = Solargraph::DocMap.new(['original_gem'], workspace) + # @todo this should probably not be in requires, which is a + # path, and instead be in a new gem_names property on the + # Environ expect(doc_map.requires).to include('original_gem', 'convention_gem1', 'convention_gem2') ensure # Clean up the registered convention From 6e03bc8ae2e4d973397db0e77c9764e3bcbe9f25 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 14:19:00 -0400 Subject: [PATCH 315/460] Handle bad gemdir from gemspec object --- lib/solargraph/gem_pins.rb | 1 + lib/solargraph/yardoc.rb | 9 +++++++++ spec/gem_pins_spec.rb | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index a193a8a39..ba362c351 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -46,6 +46,7 @@ def self.combine_method_pins(*pins) # @return [Array] def self.build_yard_pins(yard_plugins, gemspec) Yardoc.cache(yard_plugins, gemspec) unless Yardoc.cached?(gemspec) + return [] unless Yardoc.cached?(gemspec) yardoc = Yardoc.load!(gemspec) YardMap::Mapper.new(yardoc, gemspec).map end diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 625e41ce4..43424d99a 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -18,6 +18,15 @@ def cache(yard_plugins, gemspec) path = PinCache.yardoc_path gemspec return path if cached?(gemspec) + unless Dir.exist? gemspec.gem_dir + # Can happen in at least some (old?) RubyGems versions when we + # have a gemspec describing a standard library like bundler. + # + # https://github.com/apiology/solargraph/actions/runs/17650140201/job/50158676842?pr=10 + Solargraph.logger.info { "Bad info from gemspec - #{gemspec.gem_dir} does not exist" } + return path + end + Solargraph.logger.info "Caching yardoc for #{gemspec.name} #{gemspec.version}" cmd = "yardoc --db #{path} --no-output --plugin solargraph" yard_plugins.each { |plugin| cmd << " --plugin #{plugin}" } diff --git a/spec/gem_pins_spec.rb b/spec/gem_pins_spec.rb index d630784cf..c3c18109d 100644 --- a/spec/gem_pins_spec.rb +++ b/spec/gem_pins_spec.rb @@ -11,4 +11,9 @@ expect(core_root.return_type.to_s).to eq('Pathname, nil') expect(core_root.location.filename).to end_with('environment_loader.rb') end + + it "does not error out when handed incorrect gemspec" do + gemspec = instance_double(Gem::Specification, name: 'foo', version: '1.0', gem_dir: "/not-there") + expect { Solargraph::GemPins.build_yard_pins([], gemspec) }.not_to raise_error + end end From 1ca071d5d12f20b5fac1148e74d1640946716f87 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 14:28:04 -0400 Subject: [PATCH 316/460] Fix specs --- spec/gem_pins_spec.rb | 5 ----- spec/pin_cache_spec.rb | 9 +++++++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/spec/gem_pins_spec.rb b/spec/gem_pins_spec.rb index 1d958eb8c..944afd331 100644 --- a/spec/gem_pins_spec.rb +++ b/spec/gem_pins_spec.rb @@ -46,9 +46,4 @@ expect(pin.location.filename).to end_with('task.rb') end end - - it "does not error out when handed incorrect gemspec" do - gemspec = instance_double(Gem::Specification, name: 'foo', version: '1.0', gem_dir: "/not-there") - expect { Solargraph::GemPins.build_yard_pins([], gemspec) }.not_to raise_error - end end diff --git a/spec/pin_cache_spec.rb b/spec/pin_cache_spec.rb index 4786feeec..bbb5f42fc 100644 --- a/spec/pin_cache_spec.rb +++ b/spec/pin_cache_spec.rb @@ -78,6 +78,15 @@ end end + context 'with an incorrect gemspec' do + let(:requires) { [] } + + it "does not error out when handed incorrect gemspec" do + gemspec = instance_double(Gem::Specification, name: 'foo', version: '1.0', gem_dir: "/not-there") + expect { pin_cache.cache_gem(gemspec: gemspec) }.not_to raise_error + end + end + context 'with the parser gem' do before do Solargraph::Shell.new.uncache('parser') From 62d7d0833bd366a7b98690e157a1dd79be2db658 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 14:29:03 -0400 Subject: [PATCH 317/460] Linting fix --- spec/gem_pins_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/gem_pins_spec.rb b/spec/gem_pins_spec.rb index c3c18109d..8e3962341 100644 --- a/spec/gem_pins_spec.rb +++ b/spec/gem_pins_spec.rb @@ -12,8 +12,8 @@ expect(core_root.location.filename).to end_with('environment_loader.rb') end - it "does not error out when handed incorrect gemspec" do - gemspec = instance_double(Gem::Specification, name: 'foo', version: '1.0', gem_dir: "/not-there") + it 'does not error out when handed incorrect gemspec' do + gemspec = instance_double(Gem::Specification, name: 'foo', version: '1.0', gem_dir: '/not-there') expect { Solargraph::GemPins.build_yard_pins([], gemspec) }.not_to raise_error end end From 9b58947b4beada2bc5cf4faff9915a59bdcbedf8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 15:24:35 -0400 Subject: [PATCH 318/460] xit -> pending so we reenable specs once fixed --- spec/api_map_spec.rb | 5 ++-- spec/complex_type_spec.rb | 15 ++++++++---- spec/parser/node_chainer_spec.rb | 4 +++- spec/parser/node_methods_spec.rb | 4 +++- spec/rbs_map/core_map_spec.rb | 4 +++- spec/source/chain/call_spec.rb | 12 +++++++--- spec/source/chain_spec.rb | 4 +++- spec/source_map/clip_spec.rb | 32 ++++++++++++++++++------- spec/type_checker/levels/strict_spec.rb | 16 +++++++++---- spec/yard_map/mapper/to_method_spec.rb | 4 +++- 10 files changed, 74 insertions(+), 26 deletions(-) diff --git a/spec/api_map_spec.rb b/spec/api_map_spec.rb index 805ce49cb..bb76679f1 100755 --- a/spec/api_map_spec.rb +++ b/spec/api_map_spec.rb @@ -430,8 +430,9 @@ class Sup expect(pins.map(&:path)).to include('Mixin#bar') end - # pending https://github.com/apiology/solargraph/pull/4 - xit 'understands tuples inherit from regular arrays' do + it 'understands tuples inherit from regular arrays' do + pending('Fix to remove trailing generic<> after resolution') + method_pins = @api_map.get_method_stack("Array(1, 2, 'a')", 'include?') method_pin = method_pins.first expect(method_pin).to_not be_nil diff --git a/spec/complex_type_spec.rb b/spec/complex_type_spec.rb index f876d642f..b97338a6c 100644 --- a/spec/complex_type_spec.rb +++ b/spec/complex_type_spec.rb @@ -13,7 +13,8 @@ expect(types.length).to eq(0) end - xit 'parses zero types as a string' do + it 'parses zero types as a string' do + pending('special case being added') types = Solargraph::ComplexType.parse '' expect(types.length).to eq(0) end @@ -265,14 +266,18 @@ # See literal details at # https://github.com/ruby/rbs/blob/master/docs/syntax.md and # https://yardoc.org/types.html - xit 'understands literal strings with double quotes' do + it 'understands literal strings with double quotes' do + pending('string escaping support being added') + type = Solargraph::ComplexType.parse('"foo"') expect(type.tag).to eq('"foo"') expect(type.to_rbs).to eq('"foo"') expect(type.to_s).to eq('String') end - xit 'understands literal strings with single quotes' do + it 'understands literal strings with single quotes' do + pending('string escaping support being added') + type = Solargraph::ComplexType.parse("'foo'") expect(type.tag).to eq("'foo'") expect(type.to_rbs).to eq("'foo'") @@ -725,7 +730,9 @@ def make_bar expect(result.to_rbs).to eq('::Array[::String]') end - xit 'stops parsing when the first character indicates a string literal' do + it 'stops parsing when the first character indicates a string literal' do + pending('string escaping support being added') + api_map = Solargraph::ApiMap.new type = Solargraph::ComplexType.parse('"Array(Symbol, String, Array(Integer, Integer)"') type = type.qualify(api_map) diff --git a/spec/parser/node_chainer_spec.rb b/spec/parser/node_chainer_spec.rb index e92431aae..85fa140d8 100644 --- a/spec/parser/node_chainer_spec.rb +++ b/spec/parser/node_chainer_spec.rb @@ -141,7 +141,9 @@ class Foo expect(chain.links.first).to be_with_block end - xit 'tracks complex multiple assignment' do + it 'tracks complex multiple assignment' do + pending('complex multiple assignment support') + source = Solargraph::Source.load_string(%( foo.baz, bar = [1, 2] )) diff --git a/spec/parser/node_methods_spec.rb b/spec/parser/node_methods_spec.rb index f9504b584..536bc61d6 100644 --- a/spec/parser/node_methods_spec.rb +++ b/spec/parser/node_methods_spec.rb @@ -289,7 +289,9 @@ expect(rets.length).to eq(1) end - xit "short-circuits return node finding after a raise statement in a begin expressiona" do + it "short-circuits return node finding after a raise statement in a begin expression" do + pending('case being handled') + node = Solargraph::Parser.parse(%( raise "Error" y diff --git a/spec/rbs_map/core_map_spec.rb b/spec/rbs_map/core_map_spec.rb index 88590925b..3f9cc6e03 100644 --- a/spec/rbs_map/core_map_spec.rb +++ b/spec/rbs_map/core_map_spec.rb @@ -58,7 +58,9 @@ expect(signature.block.parameters.map(&:return_type).map(&:to_s)).to eq(['String']) end - xit 'understands defaulted type parameters' do + it 'understands defaulted type parameters' do + pending('defaulted type parameter support') + # @todo Enumerable#each's' return type not yet supported as _Each<> # takes two type parameters, the second has a default value, # Enumerable specifies it, but Solargraph doesn't support type diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index 8b67a3c66..e27203fd4 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -250,7 +250,9 @@ def baz expect(type.simple_tags).to eq('Integer') end - xit 'infers method return types based on method generic' do + it 'infers method return types based on method generic' do + pending('deeper inference support') + source = Solargraph::Source.load_string(%( class Foo # @Generic A @@ -315,7 +317,9 @@ def baz expect(type.tag).to eq('String') end - xit 'infers generic return types from block from yield being a return node' do + it 'infers generic return types from block from yield being a return node' do + pending('deeper inference support') + source = Solargraph::Source.load_string(%( def yielder(&blk) yield @@ -595,7 +599,9 @@ def k expect(clip.infer.rooted_tags).to eq('::Array<::A::D::E>') end - xit 'correctly looks up civars' do + it 'correctly looks up civars' do + pending('better civar support') + source = Solargraph::Source.load_string(%( class Foo BAZ = /aaa/ diff --git a/spec/source/chain_spec.rb b/spec/source/chain_spec.rb index abc8c2b05..ec96e800f 100644 --- a/spec/source/chain_spec.rb +++ b/spec/source/chain_spec.rb @@ -362,7 +362,9 @@ class Bar; end expect(chain.links[1]).to be_with_block end - xit 'infers instance variables from multiple assignments' do + it 'infers instance variables from sequential assignments' do + pending('sequential assignment support') + source = Solargraph::Source.load_string(%( def foo @foo = nil diff --git a/spec/source_map/clip_spec.rb b/spec/source_map/clip_spec.rb index 0f83331ec..fe6a14723 100644 --- a/spec/source_map/clip_spec.rb +++ b/spec/source_map/clip_spec.rb @@ -320,7 +320,9 @@ def foo expect(type.simple_tags).to eq('String, Integer') end - xit 'uses flow-sensitive typing to infer non-nil method return type' do + it 'uses flow-sensitive typing to infer non-nil method return type' do + pending('if x.nil? support in flow sensitive typing') + source = Solargraph::Source.load_string(%( # @return [Gem::Specification,nil] def find_by_name; end @@ -2626,7 +2628,9 @@ def bar; end expect(clip.infer.to_s).to eq('Foo') end - xit 'replaces nil with reassignments' do + it 'replaces nil with reassignments' do + pending 'sequential assignment support' + source = Solargraph::Source.load_string(%( bar = nil bar @@ -2641,7 +2645,9 @@ def bar; end expect(clip.infer.to_s).to eq('Integer') end - xit 'replaces type with reassignments' do + it 'replaces type with reassignments' do + pending 'sequential assignment support' + source = Solargraph::Source.load_string(%( bar = 'a' bar @@ -2669,7 +2675,9 @@ def bar; end expect(clip.infer.to_s).to eq('String, nil') end - xit 'replaces nil with alternate reassignments' do + it 'replaces nil with alternate reassignments' do + pending 'conditional assignment support' + source = Solargraph::Source.load_string(%( bar = nil if baz @@ -2684,7 +2692,9 @@ def bar; end expect(clip.infer.to_s).to eq('Symbol, Integer') end - xit 'replaces type with alternate reassignments' do + it 'replaces type with alternate reassignments' do + pending 'conditional assignment support' + source = Solargraph::Source.load_string(%( bar = 'a' if baz @@ -2949,7 +2959,9 @@ def foo expect(clip.infer.to_s).to eq('Array, Hash, Integer, nil') end - xit 'infers that type of argument has been overridden' do + it 'infers that type of argument has been overridden' do + pending 'sequential assignment support' + source = Solargraph::Source.load_string(%( def foo a a = 'foo' @@ -2962,7 +2974,9 @@ def foo a expect(clip.infer.to_s).to eq('String') end - xit 'preserves hash value when it is a union with brackets' do + it 'preserves hash value when it is a union with brackets' do + pending 'union in bracket support' + source = Solargraph::Source.load_string(%( # @type [Hash{String => [Array, Hash, Integer, nil]}] raw_data = {} @@ -2988,7 +3002,9 @@ def foo a expect(clip.infer.to_s).to eq('Array') end - xit 'preserves hash value when it is a union with brackets' do + it 'preserves hash value when it is a union with brackets' do + pending 'union in bracket support' + source = Solargraph::Source.load_string(%( # @type [Hash{String => [Array, Hash, Integer, nil]}] raw_data = {} diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 7e57cb7cf..a408544f5 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -539,7 +539,9 @@ def bar(baz:, bing:) expect(checker.problems).to be_empty end - xit 'requires strict return tags' do + it 'requires strict return tags' do + pending 'nil? support in flow sensitive typing' + checker = type_checker(%( class Foo # The tag is [String] but the inference is [String, nil] @@ -554,7 +556,9 @@ def bar expect(checker.problems.first.message).to include('does not match inferred type') end - xit 'requires strict return tags' do + it 'requires strict return tags' do + pending 'nil? support in flow sensitive typing' + checker = type_checker(%( class Foo # The tag is [String] but the inference is [String, nil] @@ -755,7 +759,9 @@ def meth(param1) expect(checker.problems).to be_one end - xit 'uses nil? to refine type' do + it 'uses nil? to refine type' do + pending 'nil? support in flow sensitive typing' + checker = type_checker(%( # @sg-ignore # @type [String, nil] @@ -859,7 +865,9 @@ def foo *path, baz; end expect(checker.problems.map(&:message)).to be_empty end - xit "Uses flow scope to specialize understanding of cvar types" do + it "Uses flow scope to specialize understanding of cvar types" do + pending 'better cvar support' + checker = type_checker(%( class Bar # @return [String] diff --git a/spec/yard_map/mapper/to_method_spec.rb b/spec/yard_map/mapper/to_method_spec.rb index 9c5caa705..c90fe75ed 100644 --- a/spec/yard_map/mapper/to_method_spec.rb +++ b/spec/yard_map/mapper/to_method_spec.rb @@ -67,7 +67,9 @@ expect(param.full).to eq("&bar") end - xit 'parses undefined but typed blockargs' do + it 'parses undeclared but typed blockargs' do + pending('block args coming from YARD alone') + code_object.parameters = [] code_object.docstring = < Date: Thu, 11 Sep 2025 15:35:57 -0400 Subject: [PATCH 319/460] Mark spec as working --- spec/source/chain/call_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index e27203fd4..4ed843901 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -600,8 +600,6 @@ def k end it 'correctly looks up civars' do - pending('better civar support') - source = Solargraph::Source.load_string(%( class Foo BAZ = /aaa/ From 182fcccd35475b30682010d0b81aa7b97634320e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 14:36:53 -0400 Subject: [PATCH 320/460] Handle a solargraph-rails case --- spec/source_map/clip_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/source_map/clip_spec.rb b/spec/source_map/clip_spec.rb index 0f83331ec..3fea653f7 100644 --- a/spec/source_map/clip_spec.rb +++ b/spec/source_map/clip_spec.rb @@ -1647,7 +1647,9 @@ def foo; end expect(array_names).to eq(["byteindex", "byterindex", "bytes", "bytesize", "byteslice", "bytesplice"]) string_names = api_map.clip_at('test.rb', [6, 22]).complete.pins.map(&:name) - expect(string_names).to eq(['upcase', 'upcase!', 'upto']) + # can be brought in by solargraph-rails + activesupport_completions = ['upcase_first'] + expect(string_names - activesupport_completions).to eq(['upcase', 'upcase!', 'upto']) end it 'completes global methods defined in top level scope inside class when referenced inside a namespace' do From d85fc6526448a8f6580b72a9b47d260c5a4bb3e7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 15:54:04 -0400 Subject: [PATCH 321/460] Reenable specs --- spec/workspace/gemspecs_resolve_require_spec.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb index 4ffcd0bd1..6ee0cecf0 100644 --- a/spec/workspace/gemspecs_resolve_require_spec.rb +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -180,9 +180,7 @@ def configure_bundler_spec stub_value let(:require) { 'bundler/require' } it 'finds nothing' do - pending('https://github.com/castwide/solargraph/pull/1006') - - expect(specs).to be_empty + expect(specs).to be_nil end end end @@ -219,9 +217,7 @@ def configure_bundler_spec stub_value let(:require) { 'bundler/gem_tasks' } - xit 'returns gems' do - pending('improved logic for require lookups') - + it 'returns gems' do expect(specs&.map(&:name)).to include('bundler') end end From 73f98b9ee3ab7914c899837d54ca418b2b6fffce Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 16:08:14 -0400 Subject: [PATCH 322/460] Add a regression spec --- spec/workspace/gemspecs_fetch_dependencies_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/workspace/gemspecs_fetch_dependencies_spec.rb b/spec/workspace/gemspecs_fetch_dependencies_spec.rb index 285f8e1a0..6ead1060a 100644 --- a/spec/workspace/gemspecs_fetch_dependencies_spec.rb +++ b/spec/workspace/gemspecs_fetch_dependencies_spec.rb @@ -22,6 +22,16 @@ end end + context 'with a Gem::Specification' do + let(:gemspec) do + Gem::Specification.find_by_name('solargraph') + end + + it 'finds a known dependency' do + expect(deps.map(&:name)).to include('backport') + end + end + context 'with gem whose dependency does not exist in our bundle' do let(:gemspec) do instance_double(Gem::Specification, From cb59ba6bebbc35461a47bc97642a1d5cc4d0cb4f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 16:11:40 -0400 Subject: [PATCH 323/460] Drop assert --- lib/solargraph/pin_cache.rb | 17 ----------------- lib/solargraph/workspace/gemspecs.rb | 8 ++------ 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index c72a6a770..2a0ec4639 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -7,23 +7,6 @@ module PinCache class << self include Logging - # @return [Array] a list of possible standard library names - def possible_stdlibs - @possible_stdlibs ||= begin - # all dirs and .rb files in Gem::RUBYGEMS_DIR - Dir.glob(File.join(Gem::RUBYGEMS_DIR, '*')).map do |file_or_dir| - basename = File.basename(file_or_dir) - # remove .rb - basename = basename[0..-4] if basename.end_with?('.rb') - basename - end.sort.uniq - rescue StandardError => e - logger.info { "Failed to get possible stdlibs: #{e.message}" } - logger.debug { e.backtrace.join("\n") } - [] - end - end - # The base directory where cached YARD documentation and serialized pins are serialized # # @return [String] diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 31c5908c3..05acb56c9 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -68,12 +68,8 @@ def resolve_require require # - need to expand Environ to provide a place to put gem # names and get new plugins out before retiring this. gemspec ||= find_gem(gem_name_guess) - if gemspec.nil? - if !PinCache.possible_stdlibs.include?(gem_name_guess) && !['rspec-rails', 'actionmailer', 'activesupport', 'activerecord', 'shoulda-matchers', 'rspec-sidekiq', 'airborne'].include?(gem_name_guess) - Solargraph.assert_or_log(:gemspecs_resolve_require_guess, "Could not find require based on #{require.inspect}") - end - return nil - end + return nil if gemspec.nil? + [gemspec_or_preference(gemspec)] end From 05b36ec01808b6af7d898f49a976eb7ec820ca30 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 16:25:18 -0400 Subject: [PATCH 324/460] Import spec to cover shell.rb changes --- lib/solargraph/shell.rb | 2 +- spec/shell_spec.rb | 159 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 153 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 907daccc6..117bbbe2d 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -267,7 +267,7 @@ def pin_description pin def do_cache gemspec, api_map # @todo if the rebuild: option is passed as a positional arg, # typecheck doesn't complain on the below line - api_map.cache_gem(gemspec, rebuild: options.rebuild, out: $stdout) + api_map.cache_gem(gemspec, rebuild: options[:rebuild], out: $stdout) end end end diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index 91f84b4c7..f56cebc7d 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -1,18 +1,23 @@ +# frozen_string_literal: true + require 'tmpdir' require 'open3' describe Solargraph::Shell do + let(:shell) { described_class.new } let(:temp_dir) { Dir.mktmpdir } before do File.open(File.join(temp_dir, 'Gemfile'), 'w') do |file| file.puts "source 'https://rubygems.org'" - file.puts "gem 'solargraph', path: #{File.expand_path('..', __dir__)}" + file.puts "gem 'solargraph', path: '#{File.expand_path('..', __dir__)}'" end output, status = Open3.capture2e("bundle install", chdir: temp_dir) raise "Failure installing bundle: #{output}" unless status.success? end + # @type cmd [Array] + # @return [String] def bundle_exec(*cmd) # run the command in the temporary directory with bundle exec output, status = Open3.capture2e("bundle exec #{cmd.join(' ')}", chdir: temp_dir) @@ -25,20 +30,160 @@ def bundle_exec(*cmd) FileUtils.rm_rf(temp_dir) end - describe "--version" do - it "returns a version when run" do - output = bundle_exec("solargraph", "--version") + describe '--version' do + let(:output) { bundle_exec('solargraph', '--version') } + it 'returns output' do expect(output).not_to be_empty + end + + it 'returns a version when run' do expect(output).to eq("#{Solargraph::VERSION}\n") end end - describe "uncache" do - it "uncaches without erroring out" do - output = bundle_exec("solargraph", "uncache", "solargraph") + describe 'uncache' do + it 'uncaches without erroring out' do + output = capture_stdout do + shell.uncache('backport') + end expect(output).to include('Clearing pin cache in') end + + it 'uncaches stdlib without erroring out' do + expect { shell.uncache('stdlib') }.not_to raise_error + end + + it 'uncaches core without erroring out' do + expect { shell.uncache('core') }.not_to raise_error + end + end + + describe 'scan' do + context 'with mocked dependencies' do + let(:api_map) { instance_double(Solargraph::ApiMap) } + + before do + allow(Solargraph::ApiMap).to receive(:load_with_cache).and_return(api_map) + end + + it 'scans without erroring out' do + allow(api_map).to receive(:pins).and_return([]) + output = capture_stdout do + shell.options = { directory: 'spec/fixtures/workspace' } + shell.scan + end + + expect(output).to include('Scanned ').and include(' seconds.') + end + end + end + + describe 'typecheck' do + context 'with mocked dependencies' do + let(:type_checker) { instance_double(Solargraph::TypeChecker) } + let(:api_map) { instance_double(Solargraph::ApiMap) } + + before do + allow(Solargraph::ApiMap).to receive(:load_with_cache).and_return(api_map) + allow(Solargraph::TypeChecker).to receive(:new).and_return(type_checker) + allow(type_checker).to receive(:problems).and_return([]) + end + + it 'typechecks without erroring out' do + output = capture_stdout do + shell.options = { level: 'normal', directory: '.' } + shell.typecheck('Gemfile') + end + + expect(output).to include('Typecheck finished in') + end + end + end + + describe 'gems' do + context 'without mocked ApiMap' do + it 'complains when gem does not exist' do + pending 'error message improvements' + + output = capture_both do + shell.gems('nonexistentgem') + end + + expect(output).to include("Gem 'nonexistentgem' not found") + end + + it 'caches core without erroring out' do + pending 'core caching suppport' + + capture_both do + shell.uncache('core') + end + + expect { shell.cache('core') }.not_to raise_error + end + + it 'gives sensible error for gem that does not exist' do + pending 'error message improvements' + + output = capture_both do + shell.gems('solargraph123') + end + + expect(output).to include("Gem 'solargraph123' not found") + end + end + + context 'with mocked Workspace' do + let(:api_map) { instance_double(Solargraph::ApiMap) } + let(:workspace) { instance_double(Solargraph::Workspace) } + let(:gemspec) { instance_double(Gem::Specification, name: 'backport') } + + before do + allow(Solargraph::Workspace).to receive(:new).and_return(workspace) + allow(Solargraph::ApiMap).to receive(:load).with('.').and_return(api_map) + allow(api_map).to receive(:cache_gem) + allow(api_map).to receive(:workspace).and_return(workspace) + end + + it 'caches all without erroring out' do + pending 'delegation to api_map' + + allow(api_map).to receive(:cache_all!) + + _output = capture_both { shell.gems } + + expect(api_map).to have_received(:cache_all!) + end + + it 'caches single gem without erroring out' do + allow(workspace).to receive(:find_gem).with('backport').and_return(gemspec) + + capture_both do + shell.options = { rebuild: false } + shell.gems('backport') + end + + expect(api_map).to have_received(:cache_gem).with(gemspec, out: an_instance_of(StringIO), rebuild: false) + end + end + end + + describe 'cache' do + it 'caches a stdlib gem without erroring out' do + expect { shell.cache('stringio') }.not_to raise_error + end + + context 'when gem does not exist' do + subject(:call) { shell.cache('nonexistentgem8675309') } + + it 'gives a good error message' do + pending 'better error message' + + # capture stderr output + expect { call }.to output(/not found/).to_stderr + end + end end end From a2834a58de4fba40a3736bf3ace9271445a1f6ed Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 20:56:08 -0400 Subject: [PATCH 325/460] Fix merge issue --- lib/solargraph/workspace/gemspecs.rb | 2 +- spec/yard_map/mapper/to_method_spec.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index cbe226227..848567fba 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -123,7 +123,7 @@ def gemspec_or_preference gemspec # @todo this code is unused but broken # @sg-ignore Unresolved call to by_path - change_gemspec_version gemspec, preference_map[by_path.name].version + change_gemspec_version gemspec, preference_map[gemspec.name].version end # @param gemspec [Gem::Specification] diff --git a/spec/yard_map/mapper/to_method_spec.rb b/spec/yard_map/mapper/to_method_spec.rb index c90fe75ed..f9225af2d 100644 --- a/spec/yard_map/mapper/to_method_spec.rb +++ b/spec/yard_map/mapper/to_method_spec.rb @@ -76,6 +76,7 @@ EOF pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) param = pin.parameters.first + expect(param).not_to be_nil expect(param.decl).to be(:blockarg) expect(param.name).to eq('') expect(param.full).to eq("&") From 955057276022ca9868ededc946e20a95b60009ee Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 21:12:18 -0400 Subject: [PATCH 326/460] Disable constant test --- spec/api_map_method_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index 857ef6897..fd3074b13 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -118,7 +118,7 @@ class B let(:external_requires) { ['yaml'] } let(:method_stack) { api_map.get_method_stack('YAML', 'safe_load', scope: :class) } - it 'handles the YAML gem aliased to Psych', time_limit_seconds: 240 do + xit 'handles the YAML gem aliased to Psych', time_limit_seconds: 240 do expect(method_stack).not_to be_empty end end From 9997273433137871eb2bc8ebe5bdc914dc671494 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 21:22:06 -0400 Subject: [PATCH 327/460] Mark things as no longer pending --- spec/workspace/gemspecs_resolve_require_spec.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb index 6ee0cecf0..b88bae269 100644 --- a/spec/workspace/gemspecs_resolve_require_spec.rb +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -261,8 +261,6 @@ def configure_bundler_spec stub_value end it 'returns the preferred gemspec' do - pending('https://github.com/castwide/solargraph/pull/1006') - gemspecs = described_class.new(dir_path, preferences: preferences) specs = gemspecs.resolve_require('backport') backport = specs.find { |spec| spec.name == 'backport' } @@ -281,8 +279,6 @@ def configure_bundler_spec stub_value end it 'returns the gemspec we do have' do - pending('https://github.com/castwide/solargraph/pull/1006') - gemspecs = described_class.new(dir_path, preferences: preferences) specs = gemspecs.resolve_require('backport') backport = specs.find { |spec| spec.name == 'backport' } From a89f298b3e7da14c2ecc93b61df85c92fcaf2c74 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 07:27:29 -0400 Subject: [PATCH 328/460] Add temporary @sg-ignores --- lib/solargraph/shell.rb | 1 + lib/solargraph/workspace.rb | 1 + lib/solargraph/workspace/config.rb | 1 + lib/solargraph/workspace/gemspecs.rb | 2 -- 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 418013235..770708a01 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -81,6 +81,7 @@ def config(directory = '.') end end File.open(File.join(directory, '.solargraph.yml'), 'w') do |file| + # @sg-ignore Unresolved call to to_yaml file.puts conf.to_yaml end STDOUT.puts "Configuration file initialized." diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 3cbc75fc3..7f38ab95b 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -277,6 +277,7 @@ def require_plugins def read_rbs_collection_path return unless rbs_collection_config_path + # @sg-ignore Unresolved call to load_file on Module path = YAML.load_file(rbs_collection_config_path)&.fetch('path') # make fully qualified File.expand_path(path, directory) diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 0b2d84a01..1e4173d55 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -143,6 +143,7 @@ def config_data def read_config config_path = '' return nil if config_path.empty? return nil unless File.file?(config_path) + # @sg-ignore Unresolved call to safe_load on Module YAML.safe_load(File.read(config_path)) end diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 848567fba..c5a849990 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -121,8 +121,6 @@ def gemspec_or_preference gemspec return gemspec unless preference_map.key?(gemspec.name) return gemspec if gemspec.version == preference_map[gemspec.name].version - # @todo this code is unused but broken - # @sg-ignore Unresolved call to by_path change_gemspec_version gemspec, preference_map[gemspec.name].version end From 5a1a02dc089b7562101551845da9956c58236500 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 18:12:45 -0400 Subject: [PATCH 329/460] More annotation fixes --- lib/solargraph/api_map.rb | 2 +- lib/solargraph/complex_type/unique_type.rb | 3 ++- .../convention/data_definition/data_definition_node.rb | 2 +- lib/solargraph/doc_map.rb | 6 +++--- lib/solargraph/language_server/host.rb | 9 +++++---- .../message/text_document/definition.rb | 4 ++-- .../message/text_document/formatting.rb | 3 ++- .../message/text_document/type_definition.rb | 2 +- lib/solargraph/language_server/request.rb | 4 +++- lib/solargraph/parser/flow_sensitive_typing.rb | 2 +- .../parser/parser_gem/node_processors/send_node.rb | 2 +- lib/solargraph/pin/base.rb | 2 +- lib/solargraph/pin/callable.rb | 2 +- lib/solargraph/pin/closure.rb | 1 - lib/solargraph/pin/local_variable.rb | 1 - lib/solargraph/pin/method.rb | 2 +- lib/solargraph/pin_cache.rb | 10 +++++----- lib/solargraph/workspace/config.rb | 6 +++--- 18 files changed, 33 insertions(+), 30 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index f8702ea2b..73c01fa2a 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -259,7 +259,7 @@ def namespace_exists? name, context = '' # # @param namespace [String] The namespace # @param contexts [Array] The contexts - # @return [Array] + # @return [Array] def get_constants namespace, *contexts namespace ||= '' contexts.push '' if contexts.empty? diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 21cf6934b..3d08eae5d 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -280,7 +280,7 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge end # @param generics_to_resolve [Enumerable] - # @param context_type [UniqueType] + # @param context_type [UniqueType, nil] # @param resolved_generic_values [Hash{String => ComplexType}] # @yieldreturn [Array] # @return [Array] @@ -446,6 +446,7 @@ def rooted? !can_root_name? || @rooted end + # @param name_to_check [String] def can_root_name?(name_to_check = name) self.class.can_root_name?(name_to_check) end diff --git a/lib/solargraph/convention/data_definition/data_definition_node.rb b/lib/solargraph/convention/data_definition/data_definition_node.rb index fb160c58c..e86161c2d 100644 --- a/lib/solargraph/convention/data_definition/data_definition_node.rb +++ b/lib/solargraph/convention/data_definition/data_definition_node.rb @@ -76,7 +76,7 @@ def body_node # @return [Parser::AST::Node] attr_reader :node - # @return [Parser::AST::Node] + # @return [Parser::AST::Node, nil] def data_node node.children[1] end diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 64ab926bc..bde450263 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -212,7 +212,7 @@ def preference_map end # @param gemspec [Gem::Specification] - # @return [Array] + # @return [Array, nil] def deserialize_yard_pin_cache gemspec if yard_pins_in_memory.key?([gemspec.name, gemspec.version]) return yard_pins_in_memory[[gemspec.name, gemspec.version]] @@ -379,7 +379,7 @@ def inspect self.class.inspect end - # @return [Array] + # @return [Array, nil] def gemspecs_required_from_bundler # @todo Handle projects with custom Bundler/Gemfile setups return unless workspace.gemfile? @@ -402,7 +402,7 @@ def gemspecs_required_from_bundler end end - # @return [Array] + # @return [Array, nil] def gemspecs_required_from_external_bundle logger.info 'Fetching gemspecs required from external bundle' return [] unless workspace&.directory diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index 65ef909fa..b6efe93a3 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -94,7 +94,8 @@ def process request # processed, caller is responsible for sending the response. # # @param request [Hash{String => unspecified}] The contents of the message. - # @return [Solargraph::LanguageServer::Message::Base, nil] The message handler. + # + # @return [Solargraph::LanguageServer::Message::Base, Solargraph::LanguageServer::Request, nil] The message handler. def receive request if request['method'] logger.info "Host received ##{request['id']} #{request['method']}" @@ -534,7 +535,7 @@ def formatter_config uri # @param uri [String] # @param line [Integer] # @param column [Integer] - # @return [Solargraph::SourceMap::Completion] + # @return [Solargraph::SourceMap::Completion, nil] def completions_at uri, line, column library = library_for(uri) library.completions_at uri_to_file(uri), line, column @@ -548,7 +549,7 @@ def has_pending_completions? # @param uri [String] # @param line [Integer] # @param column [Integer] - # @return [Array] + # @return [Array, nil] def definitions_at uri, line, column library = library_for(uri) library.definitions_at(uri_to_file(uri), line, column) @@ -557,7 +558,7 @@ def definitions_at uri, line, column # @param uri [String] # @param line [Integer] # @param column [Integer] - # @return [Array] + # @return [Array, nil] def type_definitions_at uri, line, column library = library_for(uri) library.type_definitions_at(uri_to_file(uri), line, column) diff --git a/lib/solargraph/language_server/message/text_document/definition.rb b/lib/solargraph/language_server/message/text_document/definition.rb index 5f143cc82..ea0942dd5 100644 --- a/lib/solargraph/language_server/message/text_document/definition.rb +++ b/lib/solargraph/language_server/message/text_document/definition.rb @@ -10,7 +10,7 @@ def process private - # @return [Array] + # @return [Array, nil] def code_location suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column) return nil if suggestions.empty? @@ -22,7 +22,7 @@ def code_location end end - # @return [Array] + # @return [Array, nil] def require_location # @todo Terrible hack lib = host.library_for(params['textDocument']['uri']) diff --git a/lib/solargraph/language_server/message/text_document/formatting.rb b/lib/solargraph/language_server/message/text_document/formatting.rb index e6dca4bdb..821de7ffc 100644 --- a/lib/solargraph/language_server/message/text_document/formatting.rb +++ b/lib/solargraph/language_server/message/text_document/formatting.rb @@ -96,8 +96,9 @@ def formatter_class(config) end # @param value [Array, String] - # @return [String] + # @return [String, nil] def cop_list(value) + # @type [String] value = value.join(',') if value.respond_to?(:join) return nil if value == '' || !value.is_a?(String) value diff --git a/lib/solargraph/language_server/message/text_document/type_definition.rb b/lib/solargraph/language_server/message/text_document/type_definition.rb index 8c95c231e..adb24038b 100644 --- a/lib/solargraph/language_server/message/text_document/type_definition.rb +++ b/lib/solargraph/language_server/message/text_document/type_definition.rb @@ -10,7 +10,7 @@ def process private - # @return [Array] + # @return [Array, nil] def code_location suggestions = host.type_definitions_at(params['textDocument']['uri'], @line, @column) return nil if suggestions.empty? diff --git a/lib/solargraph/language_server/request.rb b/lib/solargraph/language_server/request.rb index dcad7084d..2cc874613 100644 --- a/lib/solargraph/language_server/request.rb +++ b/lib/solargraph/language_server/request.rb @@ -11,7 +11,9 @@ def initialize id, &block end # @param result [Object] - # @return [void] + # @generic T + # @yieldreturn [generic] + # @return [generic, nil] def process result @block.call(result) unless @block.nil? end diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 8692e9762..e1a3eefef 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -171,7 +171,7 @@ def process_conditional(conditional_node, true_ranges) end # @param isa_node [Parser::AST::Node] - # @return [Array(String, String)] + # @return [Array(String, String), nil] def parse_isa(isa_node) return unless isa_node&.type == :send && isa_node.children[1] == :is_a? # Check if conditional node follows this pattern: diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index fff3addf6..645baf00f 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -198,7 +198,7 @@ def process_module_function elsif node.children[2].type == :sym || node.children[2].type == :str node.children[2..-1].each do |x| cn = x.children[0].to_s - # @type [Pin::Method] + # @type [Pin::Method, nil] ref = pins.find { |p| p.is_a?(Pin::Method) && p.namespace == region.closure.full_context.namespace && p.name == cn } unless ref.nil? pins.delete ref diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index c8c5e6d7f..15f519e98 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -579,7 +579,7 @@ def to_rbs return_type.to_rbs end - # @return [String] + # @return [String, nil] def type_desc rbs = to_rbs # RBS doesn't have a way to represent a Class type diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 8ab1bf733..207c2619b 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -35,6 +35,7 @@ def combine_blocks(other) elsif other.block.nil? block else + # @type [Pin::Signature, nil] choose_pin_attr(other, :block) end end @@ -212,7 +213,6 @@ def mandatory_positional_param_count parameters.count(&:arg?) end - # @return [String] def to_rbs rbs_generics + '(' + parameters.map { |param| param.to_rbs }.join(', ') + ') ' + (block.nil? ? '' : '{ ' + block.to_rbs + ' } ') + '-> ' + return_type.to_rbs end diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index ac95be10b..af3a8a372 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -60,7 +60,6 @@ def generics @generics ||= docstring.tags(:generic).map(&:name) end - # @return [String] def to_rbs rbs_generics + return_type.to_rbs end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 78e0b287d..9eae6cc6f 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -42,7 +42,6 @@ def visible_at?(other_closure, other_loc) match_named_closure(other_closure, closure) end - # @return [String] def to_rbs (name || '(anon)') + ' ' + (return_type&.to_rbs || 'untyped') end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 3ac7837fb..72f213a42 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -17,7 +17,7 @@ class Method < Callable # @param visibility [::Symbol] :public, :protected, or :private # @param explicit [Boolean] - # @param block [Pin::Signature, nil, ::Symbol] + # @param block [Pin::Signature, nil, :undefined] # @param node [Parser::AST::Node, nil] # @param attribute [Boolean] # @param signatures [::Array, nil] diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 2a0ec4639..b3c162a15 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -48,7 +48,7 @@ def stdlib_require_path require end # @param require [String] - # @return [Array] + # @return [Array, nil] def deserialize_stdlib_require require load(stdlib_require_path(require)) end @@ -65,7 +65,7 @@ def core_path File.join(work_dir, 'core.ser') end - # @return [Array] + # @return [Array, nil] def deserialize_core load(core_path) end @@ -83,7 +83,7 @@ def yard_gem_path gemspec end # @param gemspec [Gem::Specification] - # @return [Array] + # @return [Array, nil] def deserialize_yard_gem(gemspec) load(yard_gem_path(gemspec)) end @@ -116,7 +116,7 @@ def rbs_collection_path_prefix(gemspec) # @param gemspec [Gem::Specification] # @param hash [String, nil] - # @return [Array] + # @return [Array, nil] def deserialize_rbs_collection_gem(gemspec, hash) load(rbs_collection_path(gemspec, hash)) end @@ -152,7 +152,7 @@ def serialize_combined_gem(gemspec, hash, pins) # @param gemspec [Gem::Specification] # @param hash [String, nil] - # @return [Array] + # @return [Array, nil] def deserialize_combined_gem gemspec, hash load(combined_path(gemspec, hash)) end diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 0b2d84a01..d1e6c27b5 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -14,8 +14,8 @@ class Config # @return [String] attr_reader :directory - # @todo To make this strongly typed we'll need a record syntax - # @return [Hash{String => Array, Hash, Integer, nil}] + # @todo To make JSON strongly typed we'll need a record syntax + # @return [Hash{String => undefined, nil}] attr_reader :raw_data # @param directory [String] @@ -123,7 +123,7 @@ def workspace_config_path File.join(@directory, '.solargraph.yml') end - # @return [Hash{String => Array, Hash{String => undefined}, Integer}] + # @return [Hash{String => undefined}] def config_data workspace_config = read_config(workspace_config_path) global_config = read_config(global_config_path) From fd64e33f83e723eff9b37f163393ded38130cba0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 18:13:39 -0400 Subject: [PATCH 330/460] More YARD/TagTypeSyntax --- .rubocop_todo.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 89f703d23..3c66b31ca 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1287,6 +1287,7 @@ YARD/TagTypeSyntax: Exclude: - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/parser/comment_ripper.rb' + - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/type_checker.rb' # This cop supports safe autocorrection (--autocorrect). From db725f78fdb3756c917044e35dd6d1eec1c86095 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 13 Sep 2025 16:30:39 -0400 Subject: [PATCH 331/460] Update rubocop todo --- .rubocop_todo.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 89f703d23..f7e83b95f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -285,7 +285,6 @@ Layout/TrailingWhitespace: # Configuration parameters: AllowedMethods, AllowedPatterns. Lint/AmbiguousBlockAssociation: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/language_server/host.rb' # This cop supports safe autocorrection (--autocorrect). @@ -1108,7 +1107,6 @@ Style/RedundantFreeze: # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: - - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/source_map/mapper.rb' @@ -1133,7 +1131,6 @@ Style/RedundantRegexpEscape: # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/complex_type/type_methods.rb' - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/parser/parser_gem/node_methods.rb' @@ -1268,7 +1265,11 @@ Style/YAMLFileRead: # This cop supports unsafe autocorrection (--autocorrect-all). Style/ZeroLengthPredicate: - Enabled: false + Exclude: + - 'lib/solargraph/language_server/host.rb' + - 'lib/solargraph/pin/method.rb' + - 'lib/solargraph/source/chain/array.rb' + - 'spec/language_server/protocol_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. From 805276e023d95d8601f9f93b333935c0c2623b80 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 13 Sep 2025 16:34:41 -0400 Subject: [PATCH 332/460] Fix typechecking issues --- lib/solargraph/api_map/store.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index b82e5612c..d89d1a489 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -101,7 +101,7 @@ def qualify_superclass fq_sub_tag end # @param fqns [String] - # @return [Array] + # @return [Array] def get_includes fqns include_references[fqns] || [] end @@ -113,7 +113,7 @@ def get_prepends fqns end # @param fqns [String] - # @return [Array] + # @return [Array] def get_extends fqns extend_references[fqns] || [] end From b6d86ecb49631fc9201aa02e9e0626be85147c23 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 13 Sep 2025 17:37:08 -0400 Subject: [PATCH 333/460] Fix merge failure --- lib/solargraph/api_map.rb | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 194e6cd3a..c27fceb7b 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -705,21 +705,6 @@ def inner_get_methods_from_reference(fq_reference_tag, namespace_pin, type, scop methods end - # @param fq_sub_tag [String] - # @return [String, nil] - def qualify_superclass fq_sub_tag - fq_sub_type = ComplexType.try_parse(fq_sub_tag) - fq_sub_ns = fq_sub_type.name - sup_tag = store.get_superclass(fq_sub_tag) - sup_type = ComplexType.try_parse(sup_tag) - sup_ns = sup_type.name - return nil if sup_tag.nil? - parts = fq_sub_ns.split('::') - last = parts.pop - parts.pop if last == sup_ns - qualify(sup_tag, parts.join('::')) - end - private # A hash of source maps with filename keys. From 7c8d200225cc2e7817e26657ec0909b739669f6e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 20:16:34 -0400 Subject: [PATCH 334/460] Enable union type checking (at typed level temporarily, soon strict) --- lib/solargraph/api_map.rb | 5 +++ lib/solargraph/api_map/source_to_yard.rb | 3 ++ lib/solargraph/api_map/store.rb | 1 + lib/solargraph/complex_type.rb | 2 +- lib/solargraph/complex_type/unique_type.rb | 3 +- .../data_definition/data_assignment_node.rb | 1 + .../data_definition/data_definition_node.rb | 7 ++-- .../convention/struct_definition.rb | 1 + .../struct_assignment_node.rb | 1 + .../struct_definition_node.rb | 3 ++ lib/solargraph/diagnostics/type_check.rb | 1 + lib/solargraph/doc_map.rb | 36 +++++++++++++++++++ lib/solargraph/language_server/host.rb | 1 + .../language_server/host/dispatch.rb | 1 + .../message/extended/check_gem_version.rb | 1 + .../message/text_document/formatting.rb | 3 +- lib/solargraph/library.rb | 2 ++ lib/solargraph/location.rb | 1 + lib/solargraph/page.rb | 1 + lib/solargraph/parser/comment_ripper.rb | 3 ++ .../parser/flow_sensitive_typing.rb | 4 +++ .../parser/parser_gem/class_methods.rb | 1 + .../parser/parser_gem/node_methods.rb | 1 + .../parser_gem/node_processors/ivasgn_node.rb | 4 +++ .../parser_gem/node_processors/masgn_node.rb | 3 ++ .../parser_gem/node_processors/sclass_node.rb | 2 ++ .../parser_gem/node_processors/send_node.rb | 1 + lib/solargraph/pin/base.rb | 19 ++++++++-- lib/solargraph/pin/base_variable.rb | 1 + lib/solargraph/pin/callable.rb | 1 + lib/solargraph/pin/closure.rb | 1 + lib/solargraph/pin/common.rb | 1 + lib/solargraph/pin/constant.rb | 1 + lib/solargraph/pin/conversions.rb | 3 ++ lib/solargraph/pin/delegated_method.rb | 1 + lib/solargraph/pin/documenting.rb | 1 + lib/solargraph/pin/keyword.rb | 1 + lib/solargraph/pin/method.rb | 5 +++ lib/solargraph/pin/namespace.rb | 1 + lib/solargraph/pin/parameter.rb | 8 ++++- lib/solargraph/pin/signature.rb | 5 +++ lib/solargraph/pin/symbol.rb | 1 + lib/solargraph/pin_cache.rb | 2 ++ lib/solargraph/rbs_map.rb | 3 ++ lib/solargraph/source.rb | 5 +++ lib/solargraph/source/cursor.rb | 2 ++ lib/solargraph/source/source_chainer.rb | 3 ++ lib/solargraph/source/updater.rb | 1 + lib/solargraph/source_map.rb | 10 ++++-- lib/solargraph/source_map/clip.rb | 1 + lib/solargraph/type_checker/rules.rb | 14 +++++++- lib/solargraph/workspace.rb | 1 + lib/solargraph/workspace/config.rb | 1 + 53 files changed, 174 insertions(+), 12 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 1a6bf04e0..33b6e35eb 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -131,6 +131,7 @@ def doc_map @doc_map ||= DocMap.new([], Workspace.new('.')) end + # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [::Array] def uncached_gemspecs doc_map.uncached_gemspecs || [] @@ -214,6 +215,7 @@ class << self # any missing gems. # # + # @sg-ignore Declared type IO does not match inferred type IO, StringIO for variable out # @param directory [String] # @param out [IO] The output stream for messages # @@ -810,6 +812,9 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if scope == :instance store.get_include_pins(fqns).reverse.each do |ref| + # @sg-ignore Declared type Solargraph::Pin::Constant does + # not match inferred type Solargraph::Pin::Constant, + # Solargraph::Pin::Namespace, nil for variable const const = get_constants('', *ref.closure.gates).find { |pin| pin.path.end_with? ref.name } if const.is_a?(Pin::Namespace) result.concat inner_get_methods(const.path, scope, visibility, deep, skip, true) diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index b44ebbf1a..8da4780df 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -6,6 +6,9 @@ module SourceToYard # Get the YARD CodeObject at the specified path. # + # @sg-ignore Declared return type generic, nil does not match + # inferred type ::YARD::CodeObjects::Base, nil for + # Solargraph::ApiMap::SourceToYard#code_object_at # @generic T # @param path [String] # @param klass [Class>] diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 0d86e048a..9a238def5 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -271,6 +271,7 @@ def catalog pinsets true end + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Hash{::Array(String, String) => ::Array}] def fqns_pins_map @fqns_pins_map ||= Hash.new do |h, (base, name)| diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index a10c26fa1..6b83f54f9 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -362,7 +362,7 @@ class << self # # @todo Need ability to use a literal true as a type below # # @param partial [Boolean] True if the string is part of a another type # # @return [Array] - # @todo To be able to select the right signature above, + # @sg-ignore To be able to select the right signature above, # Chain::Call needs to know the decl type (:arg, :optarg, # :kwarg, etc) of the arguments given, instead of just having # an array of Chains as the arguments. diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index c4b652892..4524021df 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -254,6 +254,7 @@ def desc rooted_tags end + # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [String] def to_rbs if duck_type? @@ -263,7 +264,7 @@ def to_rbs elsif name.downcase == 'nil' 'nil' elsif name == GENERIC_TAG_NAME - all_params.first&.name + all_params.first&.name || 'untyped' elsif ['Class', 'Module'].include?(name) rbs_name elsif ['Tuple', 'Array'].include?(name) && fixed_parameters? diff --git a/lib/solargraph/convention/data_definition/data_assignment_node.rb b/lib/solargraph/convention/data_definition/data_assignment_node.rb index cffe77494..7b4393a5c 100644 --- a/lib/solargraph/convention/data_definition/data_assignment_node.rb +++ b/lib/solargraph/convention/data_definition/data_assignment_node.rb @@ -47,6 +47,7 @@ def class_name private + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Parser::AST::Node] def data_node if node.children[2].type == :block diff --git a/lib/solargraph/convention/data_definition/data_definition_node.rb b/lib/solargraph/convention/data_definition/data_definition_node.rb index fb160c58c..eacd848e0 100644 --- a/lib/solargraph/convention/data_definition/data_definition_node.rb +++ b/lib/solargraph/convention/data_definition/data_definition_node.rb @@ -66,7 +66,8 @@ def attributes end.compact end - # @return [Parser::AST::Node] + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 + # @return [Parser::AST::Node, nil] def body_node node.children[2] end @@ -76,11 +77,13 @@ def body_node # @return [Parser::AST::Node] attr_reader :node - # @return [Parser::AST::Node] + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 + # @return [Parser::AST::Node, nil] def data_node node.children[1] end + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Array] def data_attribute_nodes data_node.children[2..-1] diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index b34ae5494..a812727d1 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -137,6 +137,7 @@ def parse_comments # @param tag [YARD::Tags::Tag, nil] The param tag for this attribute.xtract_ # + # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [String] def tag_string(tag) tag&.types&.join(',') || 'undefined' diff --git a/lib/solargraph/convention/struct_definition/struct_assignment_node.rb b/lib/solargraph/convention/struct_definition/struct_assignment_node.rb index 2816de6ed..cc7600a4e 100644 --- a/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_assignment_node.rb @@ -48,6 +48,7 @@ def class_name private + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Parser::AST::Node] def struct_node if node.children[2].type == :block diff --git a/lib/solargraph/convention/struct_definition/struct_definition_node.rb b/lib/solargraph/convention/struct_definition/struct_definition_node.rb index 725e4227f..581117093 100644 --- a/lib/solargraph/convention/struct_definition/struct_definition_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_definition_node.rb @@ -77,6 +77,7 @@ def keyword_init? keyword_init_param.children[0].children[1].type == :true end + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Parser::AST::Node] def body_node node.children[2] @@ -87,11 +88,13 @@ def body_node # @return [Parser::AST::Node] attr_reader :node + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Parser::AST::Node] def struct_node node.children[1] end + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Array] def struct_attribute_nodes struct_node.children[2..-1] diff --git a/lib/solargraph/diagnostics/type_check.rb b/lib/solargraph/diagnostics/type_check.rb index 80f53eb7c..583abd077 100644 --- a/lib/solargraph/diagnostics/type_check.rb +++ b/lib/solargraph/diagnostics/type_check.rb @@ -45,6 +45,7 @@ def extract_first_line location, source # @param position [Solargraph::Position] # @param source [Solargraph::Source] + # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [Integer] def last_character position, source cursor = Position.to_offset(source.code, position) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 0aa60c0cd..a9f260d5b 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -89,6 +89,42 @@ def unresolved_requires @unresolved_requires ||= required_gems_map.select { |_, gemspecs| gemspecs.nil? }.keys end + # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version + def self.all_yard_gems_in_memory + @yard_gems_in_memory ||= {} + end + + # @return [Hash{String => Hash{Array(String, String) => Array}}] stored by RBS collection path + def self.all_rbs_collection_gems_in_memory + @rbs_collection_gems_in_memory ||= {} + end + + # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version + def yard_pins_in_memory + self.class.all_yard_gems_in_memory + end + + # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version + def rbs_collection_pins_in_memory + self.class.all_rbs_collection_gems_in_memory[rbs_collection_path] ||= {} + end + + # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version + def self.all_combined_pins_in_memory + @combined_pins_in_memory ||= {} + end + + # @todo this should also include an index by the hash of the RBS collection + # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version + def combined_pins_in_memory + self.class.all_combined_pins_in_memory + end + + # @return [Array] + def yard_plugins + @environ.yard_plugins + end + # @return [Set] # @param out [IO] def dependencies out: $stderr diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index 65ef909fa..09643eaac 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -723,6 +723,7 @@ def requests end # @param path [String] + # @sg-ignore Need to be able to choose signature on String#gsub # @return [String] def normalize_separators path return path if File::ALT_SEPARATOR.nil? diff --git a/lib/solargraph/language_server/host/dispatch.rb b/lib/solargraph/language_server/host/dispatch.rb index 1ff1227b8..48570028e 100644 --- a/lib/solargraph/language_server/host/dispatch.rb +++ b/lib/solargraph/language_server/host/dispatch.rb @@ -42,6 +42,7 @@ def update_libraries uri # Find the best libary match for the given URI. # # @param uri [String] + # @sg-ignore sensitive typing needs to handle || on nil types # @return [Library] def library_for uri result = explicit_library_for(uri) || diff --git a/lib/solargraph/language_server/message/extended/check_gem_version.rb b/lib/solargraph/language_server/message/extended/check_gem_version.rb index 0e676f813..008e26468 100644 --- a/lib/solargraph/language_server/message/extended/check_gem_version.rb +++ b/lib/solargraph/language_server/message/extended/check_gem_version.rb @@ -71,6 +71,7 @@ def process # @return [Gem::Version] attr_reader :current + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Gem::Version] def available if !@available && !@fetched diff --git a/lib/solargraph/language_server/message/text_document/formatting.rb b/lib/solargraph/language_server/message/text_document/formatting.rb index e6dca4bdb..48893822f 100644 --- a/lib/solargraph/language_server/message/text_document/formatting.rb +++ b/lib/solargraph/language_server/message/text_document/formatting.rb @@ -96,7 +96,8 @@ def formatter_class(config) end # @param value [Array, String] - # @return [String] + # @sg-ignore Need to handle this case in flow sensitive typing + # @return [String, nil] def cop_list(value) value = value.join(',') if value.respond_to?(:join) return nil if value == '' || !value.is_a?(String) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 43656b1cc..67ea0eaa8 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -492,6 +492,7 @@ def pins @pins ||= [] end + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Set] def external_requires @external_requires ||= source_map_external_require_hash.values.flatten.to_set @@ -543,6 +544,7 @@ def api_map # # @raise [FileNotFoundError] if the file does not exist # @param filename [String] + # @sg-ignore flow sensitive typing needs to handle if foo && ... # @return [Solargraph::Source] def read filename return @current if @current && @current.filename == filename diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index 131c8dc47..b520ced79 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -25,6 +25,7 @@ def initialize filename, range end # @param other [self] + # @sg-ignore Why does Solargraph think this should return 0, nil? def <=>(other) return nil unless other.is_a?(Location) if filename == other.filename diff --git a/lib/solargraph/page.rb b/lib/solargraph/page.rb index 5d879bbe1..7952310c5 100644 --- a/lib/solargraph/page.rb +++ b/lib/solargraph/page.rb @@ -17,6 +17,7 @@ class Binder < OpenStruct # @param locals [Hash] # @param render_method [Proc] def initialize locals, render_method + # @sg-ignore Too many arguments to BasicObject#initialize super(locals) define_singleton_method :render do |template, layout: false, locals: {}| render_method.call(template, layout: layout, locals: locals) diff --git a/lib/solargraph/parser/comment_ripper.rb b/lib/solargraph/parser/comment_ripper.rb index 92373df20..aa32cf498 100644 --- a/lib/solargraph/parser/comment_ripper.rb +++ b/lib/solargraph/parser/comment_ripper.rb @@ -40,18 +40,21 @@ def create_snippet(result) @comments[result[2][0]] = Snippet.new(Range.from_to(result[2][0] || 0, result[2][1] || 0, result[2][0] || 0, (result[2][1] || 0) + chomped.length), chomped) end + # @sg-ignore @override is adding, not overriding def on_embdoc_beg *args result = super create_snippet(result) result end + # @sg-ignore @override is adding, not overriding def on_embdoc *args result = super create_snippet(result) result end + # @sg-ignore @override is adding, not overriding def on_embdoc_end *args result = super create_snippet(result) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index b6c5322aa..8964607f3 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -15,8 +15,10 @@ def initialize(locals, enclosing_breakable_pin = nil) # # @return [void] def process_and(and_node, true_ranges = []) + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] lhs = and_node.children[0] + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] rhs = and_node.children[1] @@ -44,8 +46,10 @@ def process_if(if_node) # s(:send, nil, :bar)) # [4] pry(main)> conditional_node = if_node.children[0] + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] then_clause = if_node.children[1] + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] else_clause = if_node.children[2] diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index 2daf22fc7..4b5a86291 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -18,6 +18,7 @@ def parse_with_comments code, filename = nil # @param code [String] # @param filename [String, nil] # @param line [Integer] + # @sg-ignore need to understand that raise does not return # @return [Parser::AST::Node] def parse code, filename = nil, line = 0 buffer = ::Parser::Source::Buffer.new(filename, line) diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 1ee2bf754..5b3ebdca5 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -119,6 +119,7 @@ def convert_hash node result end + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 NIL_NODE = ::Parser::AST::Node.new(:nil) # @param node [Parser::AST::Node] diff --git a/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb b/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb index 021ae0ab1..59ef28255 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb @@ -19,6 +19,10 @@ def process ) if region.visibility == :module_function here = get_node_start_position(node) + # @sg-ignore Declared type Solargraph::Pin::Method does + # not match inferred type Solargraph::Pin::Closure, nil + # for variable named_path + # @type [Pin::Closure, nil] named_path = named_path_pin(here) if named_path.is_a?(Pin::Method) pins.push Solargraph::Pin::InstanceVariable.new( diff --git a/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb b/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb index dbef1e2d7..54a4a9899 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb @@ -22,10 +22,13 @@ def process # s(:int, 2), # s(:int, 3))) masgn = node + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] mlhs = masgn.children.fetch(0) + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Array] lhs_arr = mlhs.children + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] mass_rhs = node.children.fetch(1) diff --git a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb index 1b573ed93..da868ea80 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb @@ -5,7 +5,9 @@ module Parser module ParserGem module NodeProcessors class SclassNode < Parser::NodeProcessor::Base + # @sg-ignore @override is adding, not overriding def process + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 sclass = node.children[0] # @todo Changing Parser::AST::Node to AST::Node below will # cause type errors at strong level because the combined diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index 99b2f916c..2af236d28 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -7,6 +7,7 @@ module NodeProcessors class SendNode < Parser::NodeProcessor::Base include ParserGem::NodeMethods + # @sg-ignore @override is adding, not overriding def process # @sg-ignore Variable type could not be inferred for method_name # @type [Symbol] diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 3ef322142..db841f459 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -69,16 +69,19 @@ def assert_location_provided Solargraph.assert_or_log(:best_location, "Neither location nor type_location provided - #{path} #{source} #{self.class}") end - # @return [Pin::Closure, nil] + # @sg-ignore Won't be nil based on testing with assert above + # @return [Pin::Closure] def closure Solargraph.assert_or_log(:closure, "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") unless @closure - # @type [Pin::Closure, nil] + # @sg-ignore Won't be nil based on testing with assert above + # @type [Pin::Closure] @closure end # @param other [self] # @param attrs [Hash{::Symbol => Object}] # + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" # @return [self] def combine_with(other, attrs={}) raise "tried to combine #{other.class} with #{self.class}" unless other.class == self.class @@ -140,6 +143,7 @@ def choose_longer(other, attr) end # @param other [self] + # # @return [::Array, nil] def combine_directives(other) return self.directives if other.directives.empty? @@ -148,6 +152,8 @@ def combine_directives(other) end # @param other [self] + # @sg-ignore explicitly marked undefined return types should + # disable trying to infer return types # @return [String] def combine_name(other) if needs_consistent_name? || other.needs_consistent_name? @@ -207,6 +213,7 @@ def combine_return_type(other) end end + # @sg-ignore need boolish support for ? methods def dodgy_return_type_source? # uses a lot of 'Object' instead of 'self' location&.filename&.include?('core_ext/object/') @@ -217,7 +224,8 @@ def dodgy_return_type_source? # @param other [Pin::Base] # @param attr [::Symbol] # - # @return [Object, nil] + # @sg-ignore + # @return [undefined, nil] def choose(other, attr) results = [self, other].map(&attr).compact # true and false are different classes and can't be sorted @@ -254,6 +262,7 @@ def prefer_rbs_location(other, attr) end end + # @sg-ignore need boolish support for ? methods def rbs_location? type_location&.rbs? end @@ -479,12 +488,14 @@ def return_type @return_type ||= ComplexType::UNDEFINED end + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [YARD::Docstring] def docstring parse_comments unless @docstring @docstring ||= Solargraph::Source.parse_docstring('').to_docstring end + # @sg-ignore parse_comments will always set @directives # @return [::Array] def directives parse_comments unless @directives @@ -509,6 +520,7 @@ def maybe_directives? @maybe_directives ||= comments.include?('@!') end + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Boolean] def deprecated? @deprecated ||= docstring.has_tag?('deprecated') @@ -578,6 +590,7 @@ def proxy return_type result end + # @sg-ignore to understand @foo ||= 123 will never be nil # @deprecated # @return [String] def identity diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 764c1fb39..6df623bb7 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -39,6 +39,7 @@ def symbol_kind Solargraph::LanguageServer::SymbolKinds::VARIABLE end + # @sg-ignore Need to understand @foo ||= 123 will never be nil def return_type @return_type ||= generate_complex_type end diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 6fe170a7e..0a5782c65 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -77,6 +77,7 @@ def choose_parameters(other) end end + # @sg-ignore Need to figure if Array#[n..m] can return nil # @return [Array] def blockless_parameters if parameters.last&.block? diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index ac95be10b..04fce269a 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -55,6 +55,7 @@ def gates closure ? closure.gates : [''] end + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [::Array] def generics @generics ||= docstring.tags(:generic).map(&:name) diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 062099ee4..85d734fc9 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -63,6 +63,7 @@ def path # @return [ComplexType] def find_context + # @sg-ignore should not get type error here, as type changes later on here = closure until here.nil? if here.is_a?(Pin::Namespace) diff --git a/lib/solargraph/pin/constant.rb b/lib/solargraph/pin/constant.rb index 94a968e7e..8277723ab 100644 --- a/lib/solargraph/pin/constant.rb +++ b/lib/solargraph/pin/constant.rb @@ -12,6 +12,7 @@ def initialize visibility: :public, **splat @visibility = visibility end + # @sg-ignore Need to understand @foo ||= 123 will never be nil def return_type @return_type ||= generate_complex_type end diff --git a/lib/solargraph/pin/conversions.rb b/lib/solargraph/pin/conversions.rb index e40cc8990..cb307b08e 100644 --- a/lib/solargraph/pin/conversions.rb +++ b/lib/solargraph/pin/conversions.rb @@ -34,6 +34,7 @@ def proxied? raise NotImplementedError end + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Hash] def completion_item @completion_item ||= { @@ -49,6 +50,7 @@ def completion_item } end + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Hash] def resolve_completion_item @resolve_completion_item ||= begin @@ -80,6 +82,7 @@ def detail # Get a markdown-flavored link to a documentation page. # + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def link_documentation @link_documentation ||= generate_link diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index 9483fb058..3b1227bfc 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -13,6 +13,7 @@ class DelegatedMethod < Pin::Method # # @param method [Method, nil] an already resolved method pin. # @param receiver [Source::Chain, nil] the source code used to resolve the receiver for this delegated method. + # @sg-ignore flow sensitive typing needs to handle || on nil types # @param name [String] # @param receiver_method_name [String] the method name that will be called on the receiver (defaults to :name). def initialize(method: nil, receiver: nil, name: method&.name, receiver_method_name: name, **splat) diff --git a/lib/solargraph/pin/documenting.rb b/lib/solargraph/pin/documenting.rb index bd8b1fe9a..02026c08d 100644 --- a/lib/solargraph/pin/documenting.rb +++ b/lib/solargraph/pin/documenting.rb @@ -67,6 +67,7 @@ def to_code # @return [String] def to_markdown + # @sg-ignore Too many arguments to BasicObject.new ReverseMarkdown.convert Kramdown::Document.new(@plaintext, input: 'GFM').to_html end end diff --git a/lib/solargraph/pin/keyword.rb b/lib/solargraph/pin/keyword.rb index 089d0a417..2f76bb44c 100644 --- a/lib/solargraph/pin/keyword.rb +++ b/lib/solargraph/pin/keyword.rb @@ -8,6 +8,7 @@ def initialize(name, **kwargs) super(name: name, **kwargs) end + # @sg-ignore Need to understand @foo ||= 123 will never be nil def closure @closure ||= Pin::ROOT_PIN end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 978ae7380..b92f04f78 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -76,6 +76,7 @@ def combine_signatures(other) end end + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" def combine_with(other, attrs = {}) priority_choice = choose_priority(other) return priority_choice unless priority_choice.nil? @@ -155,6 +156,8 @@ def block? !block.nil? end + # @sg-ignore flow-sensitive typing needs to remove literal with + # this unless block # @return [Pin::Signature, nil] def block return @block unless @block == :undefined @@ -212,6 +215,7 @@ def generate_signature(parameters, return_type) signature end + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [::Array] def signatures @signatures ||= begin @@ -568,6 +572,7 @@ def resolve_reference ref, api_map nil end + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Parser::AST::Node, nil] def method_body_node return nil if node.nil? diff --git a/lib/solargraph/pin/namespace.rb b/lib/solargraph/pin/namespace.rb index 95bd1089a..c5e7b9117 100644 --- a/lib/solargraph/pin/namespace.rb +++ b/lib/solargraph/pin/namespace.rb @@ -26,6 +26,7 @@ def initialize type: :class, visibility: :public, gates: [''], name: '', **splat @type = type @visibility = visibility if name.start_with?('::') + # @sg-ignore flow sensitive typing needs to handle || on nil types # @type [String] name = name[2..-1] || '' @closure = Solargraph::Pin::ROOT_PIN diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 147e42991..ae08edaa4 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -140,6 +140,7 @@ def full end end + # @sg-ignore super always sets @return_type to something # @return [ComplexType] def return_type if @return_type.nil? @@ -156,12 +157,13 @@ def return_type end end end - super + super # always sets @return_type @return_type end # The parameter's zero-based location in the block's signature. # + # @sg-ignore this won't be nil if our code is correct # @return [Integer] def index # @type [Method, Block] @@ -190,6 +192,7 @@ def compatible_arg?(atype, api_map) ptype.generic? end + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" def documentation tag = param_tag return '' if tag.nil? || tag.text.nil? @@ -210,6 +213,7 @@ def param_tag # @param api_map [ApiMap] # @return [ComplexType] def typify_block_param api_map + # @sg-ignore type here should not be affected by later downcasting block_pin = closure if block_pin.is_a?(Pin::Block) && block_pin.receiver return block_pin.typify_parameters(api_map)[index] @@ -241,6 +245,8 @@ def typify_method_param api_map # @param heredoc [YARD::Docstring] # @param api_map [ApiMap] # @param skip [::Array] + # + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [::Array] def see_reference heredoc, api_map, skip = [] heredoc.ref_tags.each do |ref| diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 4c25e028b..1a36ef660 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -9,24 +9,29 @@ def initialize **splat super(**splat) end + # @sg-ignore Need to understand @foo ||= 123 will never be nil def generics @generics ||= [].freeze end + # @sg-ignore Need to understand @foo ||= 123 will never be nil def identity @identity ||= "signature#{object_id}" end attr_writer :closure + # @sg-ignore need boolish support for ? methods def dodgy_return_type_source? super || closure&.dodgy_return_type_source? end + # @sg-ignore need boolish support for ? methods def type_location super || closure&.type_location end + # @sg-ignore need boolish support for ? methods def location super || closure&.location end diff --git a/lib/solargraph/pin/symbol.rb b/lib/solargraph/pin/symbol.rb index 294363f5f..c97aeb262 100644 --- a/lib/solargraph/pin/symbol.rb +++ b/lib/solargraph/pin/symbol.rb @@ -20,6 +20,7 @@ def path '' end + # @sg-ignore Need to understand @foo ||= 123 will never be nil def closure @closure ||= Pin::ROOT_PIN end diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 921ce810f..897b19e78 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -427,6 +427,7 @@ def all_combined_pins_in_memory # The base directory where cached YARD documentation and serialized pins are serialized # + # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [String] def base_dir # The directory is not stored in a variable so it can be overridden @@ -605,6 +606,7 @@ def clear end # @param file [String] + # @sg-ignore Marshal.load evaluates to boolean here which is wrong # @return [Array, nil] def load file return nil unless File.file?(file) diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index f6309bb55..ab90d067f 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -138,6 +138,9 @@ def pins out: $stderr # @generic T # @param path [String] # @param klass [Class>] + # + # @sg-ignore Need to be able to resolve generics based on a + # Class> param # @return [generic, nil] def path_pin path, klass = Pin::Base pin = pins.find { |p| p.path == path } diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 0af8b0cdf..21282bf2a 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -60,6 +60,8 @@ def at range # @param c1 [Integer] # @param l2 [Integer] # @param c2 [Integer] + # + # @sg-ignore Need to figure if String#[n..m] can return nil # @return [String] def from_to l1, c1, l2, c2 b = Solargraph::Position.line_char_to_offset(code, l1, c1) @@ -188,6 +190,8 @@ def code_for(node) end # @param node [AST::Node] + # + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String, nil] def comments_for node rng = Range.from_node(node) @@ -459,6 +463,7 @@ def repaired private + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Array] def code_lines @code_lines ||= code.lines diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index a8226eb07..8d0231ea4 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -35,6 +35,7 @@ def word # The part of the word before the current position. Given the text # `foo.bar`, the start_of_word at position(0, 6) is `ba`. # + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def start_of_word @start_of_word ||= begin @@ -49,6 +50,7 @@ def start_of_word # The part of the word after the current position. Given the text # `foo.bar`, the end_of_word at position (0,6) is `r`. # + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def end_of_word @end_of_word ||= begin diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index 5758a9d35..b5bed1678 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -79,11 +79,13 @@ def chain # @return [Solargraph::Source] attr_reader :source + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def phrase @phrase ||= source.code[signature_data..offset-1] end + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def fixed_phrase @fixed_phrase ||= phrase[0..-(end_of_phrase.length+1)] @@ -94,6 +96,7 @@ def fixed_position @fixed_position ||= Position.from_offset(source.code, offset - end_of_phrase.length) end + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def end_of_phrase @end_of_phrase ||= begin diff --git a/lib/solargraph/source/updater.rb b/lib/solargraph/source/updater.rb index 496d534ab..72519e785 100644 --- a/lib/solargraph/source/updater.rb +++ b/lib/solargraph/source/updater.rb @@ -29,6 +29,7 @@ def initialize filename, version, changes # @param text [String] # @param nullable [Boolean] + # @sg-ignore changes doesn't mutate @output, so this can never be nil # @return [String] def write text, nullable = false can_nullify = (nullable and changes.length == 1) diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 5173be180..986a2f082 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -83,6 +83,8 @@ def conventions_environ end # all pins except Solargraph::Pin::Reference::Reference + # + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Array] def document_symbols @document_symbols ||= (pins + convention_pins).select do |pin| @@ -117,6 +119,7 @@ def locate_pins location # @param line [Integer] # @param character [Integer] + # @sg-ignore Need better generic inference here # @return [Pin::Method,Pin::Namespace] def locate_named_path_pin line, character _locate_pin line, character, Pin::Namespace, Pin::Method @@ -124,6 +127,7 @@ def locate_named_path_pin line, character # @param line [Integer] # @param character [Integer] + # @sg-ignore Need better generic inference here # @return [Pin::Namespace,Pin::Method,Pin::Block] def locate_block_pin line, character _locate_pin line, character, Pin::Namespace, Pin::Method, Pin::Block @@ -188,10 +192,12 @@ def convention_pins @convention_pins || [] end + # @generic T # @param line [Integer] # @param character [Integer] - # @param klasses [Array] - # @return [Pin::Base, nil] + # @param klasses [Array>>] + # @return [generic, nil] + # @sg-ignore Need better generic inference here def _locate_pin line, character, *klasses position = Position.new(line, character) found = nil diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index 16a4ec845..016d4b51f 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -75,6 +75,7 @@ def gates block.gates end + # @sg-ignore need boolish support for ? methods def in_block? return @in_block unless @in_block.nil? @in_block = begin diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 5290c8c12..bdb20fd66 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,8 +58,20 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end + # @todo need boolish support for ? methods + # @todo need to be able to disambiguate Array signatures + # @todo https://github.com/castwide/solargraph/pull/1005 + # @todo To make JSON strongly typed we'll need a record syntax + # @todo flow sensitive typing needs to handle "unless foo.nil?" + # @todo flow sensitive typing needs to handle || on nil types + # @todo Need to understand @foo ||= 123 will never be nil + # @todo add metatype - e.g., $stdout is both an IO as well as + # a StringIO. Marking it as [IO, StringIO] implies it is + # /either/ one, not both, which means you can't hand it to + # something that demands a regular IO and doesn't also claim + # to accept a StringIO. def require_all_unique_types_match_declared? - rank >= LEVELS[:alpha] + rank >= LEVELS[:typed] end def require_no_undefined_args? diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 7f38ab95b..1fa8ab772 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -44,6 +44,7 @@ def require_paths @require_paths ||= RequirePaths.new(directory_or_nil, config).generate end + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Solargraph::Workspace::Config] def config @config ||= Solargraph::Workspace::Config.new(directory) diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 1e4173d55..88995d9bb 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -111,6 +111,7 @@ def max_files private + # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [String] def global_config_path ENV['SOLARGRAPH_GLOBAL_CONFIG'] || From 7e8e49bb80bfe372ff665eeee810f9651855b690 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 20:17:35 -0400 Subject: [PATCH 335/460] More annotation fixes --- lib/solargraph/api_map.rb | 2 +- lib/solargraph/complex_type/unique_type.rb | 3 +- .../data_definition/data_definition_node.rb | 1 - lib/solargraph/doc_map.rb | 221 ++++++++++++++++++ lib/solargraph/language_server/host.rb | 9 +- .../message/text_document/definition.rb | 4 +- .../message/text_document/formatting.rb | 2 +- .../message/text_document/type_definition.rb | 2 +- lib/solargraph/language_server/request.rb | 4 +- .../parser/flow_sensitive_typing.rb | 2 +- .../parser_gem/node_processors/send_node.rb | 2 +- lib/solargraph/pin/base.rb | 2 +- lib/solargraph/pin/callable.rb | 2 +- lib/solargraph/pin/closure.rb | 1 - lib/solargraph/pin/local_variable.rb | 1 - lib/solargraph/pin/method.rb | 2 +- lib/solargraph/pin_cache.rb | 10 +- lib/solargraph/workspace/config.rb | 6 +- 18 files changed, 249 insertions(+), 27 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 33b6e35eb..9827b1811 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -257,7 +257,7 @@ def namespace_exists? name, context = '' # # @param namespace [String] The namespace # @param contexts [Array] The contexts - # @return [Array] + # @return [Array] def get_constants namespace, *contexts namespace ||= '' contexts.push '' if contexts.empty? diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 4524021df..c24f81d4a 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -348,7 +348,7 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge end # @param generics_to_resolve [Enumerable] - # @param context_type [UniqueType] + # @param context_type [UniqueType, nil] # @param resolved_generic_values [Hash{String => ComplexType}] # @yieldreturn [Array] # @return [Array] @@ -519,6 +519,7 @@ def rooted? !can_root_name? || @rooted end + # @param name_to_check [String] def can_root_name?(name_to_check = name) self.class.can_root_name?(name_to_check) end diff --git a/lib/solargraph/convention/data_definition/data_definition_node.rb b/lib/solargraph/convention/data_definition/data_definition_node.rb index eacd848e0..906ef5cbf 100644 --- a/lib/solargraph/convention/data_definition/data_definition_node.rb +++ b/lib/solargraph/convention/data_definition/data_definition_node.rb @@ -77,7 +77,6 @@ def body_node # @return [Parser::AST::Node] attr_reader :node - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Parser::AST::Node, nil] def data_node node.children[1] diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index a9f260d5b..f185064b3 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -205,8 +205,229 @@ def required_gems_map @required_gems_map ||= requires.to_h { |path| [path, workspace.resolve_require(path)] } end + # @return [Hash{String => Gem::Specification}] + def preference_map + @preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] } + end + + # @param gemspec [Gem::Specification] + # @return [Array, nil] + def deserialize_yard_pin_cache gemspec + if yard_pins_in_memory.key?([gemspec.name, gemspec.version]) + return yard_pins_in_memory[[gemspec.name, gemspec.version]] + end + + cached = PinCache.deserialize_yard_gem(gemspec) + if cached + logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" } + yard_pins_in_memory[[gemspec.name, gemspec.version]] = cached + cached + else + logger.debug "No YARD pin cache for #{gemspec.name}:#{gemspec.version}" + @uncached_yard_gemspecs.push gemspec + nil + end + end + + # @param gemspec [Gem::Specification] + # @return [void] + def deserialize_combined_pin_cache(gemspec) + unless combined_pins_in_memory[[gemspec.name, gemspec.version]].nil? + return combined_pins_in_memory[[gemspec.name, gemspec.version]] + end + + rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path) + rbs_version_cache_key = rbs_map.cache_key + + cached = PinCache.deserialize_combined_gem(gemspec, rbs_version_cache_key) + if cached + logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" } + combined_pins_in_memory[[gemspec.name, gemspec.version]] = cached + return combined_pins_in_memory[[gemspec.name, gemspec.version]] + end + + rbs_collection_pins = deserialize_rbs_collection_cache gemspec, rbs_version_cache_key + + yard_pins = deserialize_yard_pin_cache gemspec + + if !rbs_collection_pins.nil? && !yard_pins.nil? + logger.debug { "Combining pins for #{gemspec.name}:#{gemspec.version}" } + combined_pins = GemPins.combine(yard_pins, rbs_collection_pins) + PinCache.serialize_combined_gem(gemspec, rbs_version_cache_key, combined_pins) + combined_pins_in_memory[[gemspec.name, gemspec.version]] = combined_pins + logger.info { "Generated #{combined_pins_in_memory[[gemspec.name, gemspec.version]].length} combined pins for #{gemspec.name} #{gemspec.version}" } + return combined_pins + end + + if !yard_pins.nil? + logger.debug { "Using only YARD pins for #{gemspec.name}:#{gemspec.version}" } + combined_pins_in_memory[[gemspec.name, gemspec.version]] = yard_pins + return combined_pins_in_memory[[gemspec.name, gemspec.version]] + elsif !rbs_collection_pins.nil? + logger.debug { "Using only RBS collection pins for #{gemspec.name}:#{gemspec.version}" } + combined_pins_in_memory[[gemspec.name, gemspec.version]] = rbs_collection_pins + return combined_pins_in_memory[[gemspec.name, gemspec.version]] + else + logger.debug { "Pins not yet cached for #{gemspec.name}:#{gemspec.version}" } + return nil + end + end + + # @param path [String] require path that might be in the RBS stdlib collection + # @return [void] + def deserialize_stdlib_rbs_map path + map = RbsMap::StdlibMap.load(path) + if map.resolved? + logger.debug { "Loading stdlib pins for #{path}" } + @pins.concat map.pins + logger.debug { "Loaded #{map.pins.length} stdlib pins for #{path}" } + map.pins + else + # @todo Temporarily ignoring unresolved `require 'set'` + logger.debug { "Require path #{path} could not be resolved in RBS" } unless path == 'set' + nil + end + end + + # @param gemspec [Gem::Specification] + # @param rbs_version_cache_key [String] + # @return [Array, nil] + def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key + return if rbs_collection_pins_in_memory.key?([gemspec, rbs_version_cache_key]) + cached = PinCache.deserialize_rbs_collection_gem(gemspec, rbs_version_cache_key) + if cached + logger.info { "Loaded #{cached.length} pins from RBS collection cache for #{gemspec.name}:#{gemspec.version}" } unless cached.empty? + rbs_collection_pins_in_memory[[gemspec, rbs_version_cache_key]] = cached + cached + else + logger.debug "No RBS collection pin cache for #{gemspec.name} #{gemspec.version}" + @uncached_rbs_collection_gemspecs.push gemspec + nil + end + end + + # @param path [String] + # @return [::Array, nil] + def resolve_path_to_gemspecs path + return nil if path.empty? + return gemspecs_required_from_bundler if path == 'bundler/require' + + # @type [Gem::Specification, nil] + gemspec = Gem::Specification.find_by_path(path) + if gemspec.nil? + gem_name_guess = path.split('/').first + begin + # this can happen when the gem is included via a local path in + # a Gemfile; Gem doesn't try to index the paths in that case. + # + # See if we can make a good guess: + potential_gemspec = Gem::Specification.find_by_name(gem_name_guess) + file = "lib/#{path}.rb" + gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file } + rescue Gem::MissingSpecError + logger.debug { "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}" } + [] + end + end + return nil if gemspec.nil? + [gemspec_or_preference(gemspec)] + end + + # @param gemspec [Gem::Specification] + # @return [Gem::Specification] + def gemspec_or_preference gemspec + return gemspec unless preference_map.key?(gemspec.name) + return gemspec if gemspec.version == preference_map[gemspec.name].version + + change_gemspec_version gemspec, preference_map[by_path.name].version + end + + # @param gemspec [Gem::Specification] + # @param version [Gem::Version] + # @return [Gem::Specification] + def change_gemspec_version gemspec, version + Gem::Specification.find_by_name(gemspec.name, "= #{version}") + rescue Gem::MissingSpecError + Solargraph.logger.info "Gem #{gemspec.name} version #{version} not found. Using #{gemspec.version} instead" + gemspec + end + + # @param gemspec [Gem::Specification] + # @return [Array] + def fetch_dependencies gemspec + # @param spec [Gem::Dependency] + only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps| + Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}" + dep = Gem.loaded_specs[spec.name] + # @todo is next line necessary? + dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement) + deps.merge fetch_dependencies(dep) if deps.add?(dep) + rescue Gem::MissingSpecError + Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found in RubyGems." + end.to_a + end + + # @param gemspec [Gem::Specification] + # @return [Array] + def only_runtime_dependencies gemspec + gemspec.dependencies - gemspec.development_dependencies + end + + def inspect self.class.inspect end + + # @return [Array, nil] + def gemspecs_required_from_bundler + # @todo Handle projects with custom Bundler/Gemfile setups + return unless workspace.gemfile? + + if workspace.gemfile? && Bundler.definition&.lockfile&.to_s&.start_with?(workspace.directory) + # Find only the gems bundler is now using + Bundler.definition.locked_gems.specs.flat_map do |lazy_spec| + logger.info "Handling #{lazy_spec.name}:#{lazy_spec.version}" + [Gem::Specification.find_by_name(lazy_spec.name, lazy_spec.version)] + rescue Gem::MissingSpecError => e + logger.info("Could not find #{lazy_spec.name}:#{lazy_spec.version} with find_by_name, falling back to guess") + # can happen in local filesystem references + specs = resolve_path_to_gemspecs lazy_spec.name + logger.warn "Gem #{lazy_spec.name} #{lazy_spec.version} from bundle not found: #{e}" if specs.nil? + next specs + end.compact + else + logger.info 'Fetching gemspecs required from Bundler (bundler/require)' + gemspecs_required_from_external_bundle + end + end + + # @return [Array, nil] + def gemspecs_required_from_external_bundle + logger.info 'Fetching gemspecs required from external bundle' + return [] unless workspace&.directory + + Solargraph.with_clean_env do + cmd = [ + 'ruby', '-e', + "require 'bundler'; require 'json'; Dir.chdir('#{workspace&.directory}') { puts Bundler.definition.locked_gems.specs.map { |spec| [spec.name, spec.version] }.to_h.to_json }" + ] + o, e, s = Open3.capture3(*cmd) + if s.success? + Solargraph.logger.debug "External bundle: #{o}" + hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {} + hash.flat_map do |name, version| + Gem::Specification.find_by_name(name, version) + rescue Gem::MissingSpecError => e + logger.info("Could not find #{name}:#{version} with find_by_name, falling back to guess") + # can happen in local filesystem references + specs = resolve_path_to_gemspecs name + logger.warn "Gem #{name} #{version} from bundle not found: #{e}" if specs.nil? + next specs + end.compact + else + Solargraph.logger.warn "Failed to load gems from bundle at #{workspace&.directory}: #{e}" + end + end + end end end diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index 09643eaac..4fda6cf7b 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -94,7 +94,8 @@ def process request # processed, caller is responsible for sending the response. # # @param request [Hash{String => unspecified}] The contents of the message. - # @return [Solargraph::LanguageServer::Message::Base, nil] The message handler. + # + # @return [Solargraph::LanguageServer::Message::Base, Solargraph::LanguageServer::Request, nil] The message handler. def receive request if request['method'] logger.info "Host received ##{request['id']} #{request['method']}" @@ -534,7 +535,7 @@ def formatter_config uri # @param uri [String] # @param line [Integer] # @param column [Integer] - # @return [Solargraph::SourceMap::Completion] + # @return [Solargraph::SourceMap::Completion, nil] def completions_at uri, line, column library = library_for(uri) library.completions_at uri_to_file(uri), line, column @@ -548,7 +549,7 @@ def has_pending_completions? # @param uri [String] # @param line [Integer] # @param column [Integer] - # @return [Array] + # @return [Array, nil] def definitions_at uri, line, column library = library_for(uri) library.definitions_at(uri_to_file(uri), line, column) @@ -557,7 +558,7 @@ def definitions_at uri, line, column # @param uri [String] # @param line [Integer] # @param column [Integer] - # @return [Array] + # @return [Array, nil] def type_definitions_at uri, line, column library = library_for(uri) library.type_definitions_at(uri_to_file(uri), line, column) diff --git a/lib/solargraph/language_server/message/text_document/definition.rb b/lib/solargraph/language_server/message/text_document/definition.rb index 5f143cc82..ea0942dd5 100644 --- a/lib/solargraph/language_server/message/text_document/definition.rb +++ b/lib/solargraph/language_server/message/text_document/definition.rb @@ -10,7 +10,7 @@ def process private - # @return [Array] + # @return [Array, nil] def code_location suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column) return nil if suggestions.empty? @@ -22,7 +22,7 @@ def code_location end end - # @return [Array] + # @return [Array, nil] def require_location # @todo Terrible hack lib = host.library_for(params['textDocument']['uri']) diff --git a/lib/solargraph/language_server/message/text_document/formatting.rb b/lib/solargraph/language_server/message/text_document/formatting.rb index 48893822f..821de7ffc 100644 --- a/lib/solargraph/language_server/message/text_document/formatting.rb +++ b/lib/solargraph/language_server/message/text_document/formatting.rb @@ -96,9 +96,9 @@ def formatter_class(config) end # @param value [Array, String] - # @sg-ignore Need to handle this case in flow sensitive typing # @return [String, nil] def cop_list(value) + # @type [String] value = value.join(',') if value.respond_to?(:join) return nil if value == '' || !value.is_a?(String) value diff --git a/lib/solargraph/language_server/message/text_document/type_definition.rb b/lib/solargraph/language_server/message/text_document/type_definition.rb index 8c95c231e..adb24038b 100644 --- a/lib/solargraph/language_server/message/text_document/type_definition.rb +++ b/lib/solargraph/language_server/message/text_document/type_definition.rb @@ -10,7 +10,7 @@ def process private - # @return [Array] + # @return [Array, nil] def code_location suggestions = host.type_definitions_at(params['textDocument']['uri'], @line, @column) return nil if suggestions.empty? diff --git a/lib/solargraph/language_server/request.rb b/lib/solargraph/language_server/request.rb index dcad7084d..2cc874613 100644 --- a/lib/solargraph/language_server/request.rb +++ b/lib/solargraph/language_server/request.rb @@ -11,7 +11,9 @@ def initialize id, &block end # @param result [Object] - # @return [void] + # @generic T + # @yieldreturn [generic] + # @return [generic, nil] def process result @block.call(result) unless @block.nil? end diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 8964607f3..1fd4fcdb7 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -175,7 +175,7 @@ def process_conditional(conditional_node, true_ranges) end # @param isa_node [Parser::AST::Node] - # @return [Array(String, String)] + # @return [Array(String, String), nil] def parse_isa(isa_node) return unless isa_node&.type == :send && isa_node.children[1] == :is_a? # Check if conditional node follows this pattern: diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index 2af236d28..f9b0be49e 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -196,7 +196,7 @@ def process_module_function elsif node.children[2].type == :sym || node.children[2].type == :str node.children[2..-1].each do |x| cn = x.children[0].to_s - # @type [Pin::Method] + # @type [Pin::Method, nil] ref = pins.find { |p| p.is_a?(Pin::Method) && p.namespace == region.closure.full_context.namespace && p.name == cn } unless ref.nil? pins.delete ref diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index db841f459..e862c6af4 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -602,7 +602,7 @@ def to_rbs return_type.to_rbs end - # @return [String] + # @return [String, nil] def type_desc rbs = to_rbs # RBS doesn't have a way to represent a Class type diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 0a5782c65..c104b5ff7 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -35,6 +35,7 @@ def combine_blocks(other) elsif other.block.nil? block else + # @type [Pin::Signature, nil] choose_pin_attr(other, :block) end end @@ -218,7 +219,6 @@ def mandatory_positional_param_count parameters.count(&:arg?) end - # @return [String] def to_rbs rbs_generics + '(' + parameters.map { |param| param.to_rbs }.join(', ') + ') ' + (block.nil? ? '' : '{ ' + block.to_rbs + ' } ') + '-> ' + return_type.to_rbs end diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index 04fce269a..84a7ad25c 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -61,7 +61,6 @@ def generics @generics ||= docstring.tags(:generic).map(&:name) end - # @return [String] def to_rbs rbs_generics + return_type.to_rbs end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 36b75773c..cb2dda140 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -39,7 +39,6 @@ def visible_at?(other_closure, other_loc) match_named_closure(other_closure, closure) end - # @return [String] def to_rbs (name || '(anon)') + ' ' + (return_type&.to_rbs || 'untyped') end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index b92f04f78..e2fefef3e 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -17,7 +17,7 @@ class Method < Callable # @param visibility [::Symbol] :public, :protected, or :private # @param explicit [Boolean] - # @param block [Pin::Signature, nil, ::Symbol] + # @param block [Pin::Signature, nil, :undefined] # @param node [Parser::AST::Node, nil] # @param attribute [Boolean] # @param signatures [::Array, nil] diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 897b19e78..8080eedb3 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -484,7 +484,7 @@ def stdlib_require_path require end # @param require [String] - # @return [Array] + # @return [Array, nil] def deserialize_stdlib_require require load(stdlib_require_path(require)) end @@ -501,7 +501,7 @@ def core_path File.join(work_dir, 'core.ser') end - # @return [Array] + # @return [Array, nil] def deserialize_core load(core_path) end @@ -519,7 +519,7 @@ def yard_gem_path gemspec end # @param gemspec [Gem::Specification] - # @return [Array] + # @return [Array, nil] def deserialize_yard_gem(gemspec) load(yard_gem_path(gemspec)) end @@ -552,7 +552,7 @@ def rbs_collection_path_prefix(gemspec) # @param gemspec [Gem::Specification] # @param hash [String, nil] - # @return [Array] + # @return [Array, nil] def deserialize_rbs_collection_gem(gemspec, hash) load(rbs_collection_path(gemspec, hash)) end @@ -588,7 +588,7 @@ def serialize_combined_gem(gemspec, hash, pins) # @param gemspec [Gem::Specification] # @param hash [String, nil] - # @return [Array] + # @return [Array, nil] def deserialize_combined_gem gemspec, hash load(combined_path(gemspec, hash)) end diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 88995d9bb..2b9cd1d88 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -14,8 +14,8 @@ class Config # @return [String] attr_reader :directory - # @todo To make this strongly typed we'll need a record syntax - # @return [Hash{String => Array, Hash, Integer, nil}] + # @todo To make JSON strongly typed we'll need a record syntax + # @return [Hash{String => undefined, nil}] attr_reader :raw_data # @param directory [String] @@ -124,7 +124,7 @@ def workspace_config_path File.join(@directory, '.solargraph.yml') end - # @return [Hash{String => Array, Hash{String => undefined}, Integer}] + # @return [Hash{String => undefined}] def config_data workspace_config = read_config(workspace_config_path) global_config = read_config(global_config_path) From b036df719c7dabdcca1b6ec4ea7b0bddf9da876d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 20:17:56 -0400 Subject: [PATCH 336/460] More YARD/TagTypeSyntax --- .rubocop_todo.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index aa8a0c63d..33e766329 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1235,7 +1235,11 @@ YARD/MismatchName: Enabled: false YARD/TagTypeSyntax: - Enabled: false + Exclude: + - 'lib/solargraph/language_server/host.rb' + - 'lib/solargraph/parser/comment_ripper.rb' + - 'lib/solargraph/pin/method.rb' + - 'lib/solargraph/type_checker.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. From 2b06ca96a82d98d309f00a06d6c45ca86e88deb2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 20:19:04 -0400 Subject: [PATCH 337/460] Enable CI --- .github/workflows/linting.yml | 3 +-- .github/workflows/plugins.yml | 3 +-- .github/workflows/rspec.yml | 3 +-- .github/workflows/typecheck.yml | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index a67fff5df..17a6b4f00 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -8,8 +8,7 @@ name: Linting on: workflow_dispatch: {} pull_request: - branches: - - '*' + branches: [ * ] push: branches: - 'main' diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 01169ce7f..2c747b5d0 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -9,8 +9,7 @@ on: push: branches: [master] pull_request: - branches: - - '*' + branches: [*] permissions: contents: read diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index d8f440124..12399f50b 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -11,8 +11,7 @@ on: push: branches: [ master ] pull_request: - branches: - - '*' + branches: ['*'] permissions: contents: read diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index d31d42738..8b0de9759 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -11,8 +11,7 @@ on: push: branches: [ master ] pull_request: - branches: - - '*' + branches: ['*'] permissions: contents: read From ab0166d0ae83dd9b80741879b6ebbbc4857cc64b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 18:58:05 -0400 Subject: [PATCH 338/460] Enable CI --- .github/workflows/linting.yml | 2 +- .github/workflows/plugins.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 17a6b4f00..4d313ea11 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -8,7 +8,7 @@ name: Linting on: workflow_dispatch: {} pull_request: - branches: [ * ] + branches: ['*'] push: branches: - 'main' diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 2c747b5d0..871252fcc 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -9,7 +9,7 @@ on: push: branches: [master] pull_request: - branches: [*] + branches: ['*'] permissions: contents: read From aaed797af013ec603a285f68d131ddcc6ee7d487 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 19:06:59 -0400 Subject: [PATCH 339/460] Fix annotation --- lib/solargraph/api_map/store.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 9a238def5..c1beba7af 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -256,7 +256,7 @@ def index # @param pinsets [Array>] # - # @return [void] + # @return [true] def catalog pinsets @pinsets = pinsets # @type [Array] From e4cb1e3202d16f36286e613a01aa202705ee45a8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 19:16:58 -0400 Subject: [PATCH 340/460] Fix combination of directives This was creating two-dimensional arrays; we wanted a one-dimensional array --- lib/solargraph/pin/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index e862c6af4..b97a75b27 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -148,7 +148,7 @@ def choose_longer(other, attr) def combine_directives(other) return self.directives if other.directives.empty? return other.directives if directives.empty? - [directives + other.directives].uniq + (directives + other.directives).uniq end # @param other [self] From dcd5182beff40c8bf37a89e28b9453cd0b8815eb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 20:09:27 -0400 Subject: [PATCH 341/460] Add another @sg-ignore --- lib/solargraph/parser/flow_sensitive_typing.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 1fd4fcdb7..ba1063c9e 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -247,6 +247,7 @@ def type_name(node) end # @param clause_node [Parser::AST::Node] + # @sg-ignore need boolish support for ? methods def always_breaks?(clause_node) clause_node&.type == :break end From 28daabbf3058f5807a3e368dfcd6f84b57afc4a8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 20:19:25 -0400 Subject: [PATCH 342/460] Kill needless GitHub workflow tweaks --- .github/workflows/linting.yml | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 4d313ea11..6262dd494 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -33,28 +33,23 @@ jobs: ruby-version: 3.4 bundler: latest bundler-cache: true - cache-version: 2025-08 + cache-version: 2025-06-06 - name: Update to best available RBS run: | bundle update rbs # use latest available for this Ruby version - # - name: Restore cache of gem annotations - # id: dot-cache-restore - # uses: actions/cache/restore@v4 - # with: - # key: | - # 2025-08-${{ runner.os }}-dot-cache-${{ hashFiles('Gemfile.lock') }} - # restore-keys: | - # 2025-08-${{ runner.os }}-dot-cache - # 2025-08-${{ runner.os }}-dot-cache- - # path: | - # /home/runner/.cache/solargraph - - - name: Debug - run: | - bundle info rbs - bundle info thor + - name: Restore cache of gem annotations + id: dot-cache-restore + uses: actions/cache/restore@v4 + with: + key: | + 2025-06-26-09-${{ runner.os }}-dot-cache-${{ hashFiles('Gemfile.lock') }} + restore-keys: | + 2025-06-26-09-${{ runner.os }}-dot-cache + 2025-06-26-09-${{ runner.os }}-dot-cache- + path: | + /home/runner/.cache/solargraph - name: Install gem types run: bundle exec rbs collection install From 583e66dbbd8a6f62ba17db7b6530c6b7a1c8354a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 20:26:08 -0400 Subject: [PATCH 343/460] Small type fix --- lib/solargraph/doc_map.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index f185064b3..f551f2510 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -426,6 +426,7 @@ def gemspecs_required_from_external_bundle end.compact else Solargraph.logger.warn "Failed to load gems from bundle at #{workspace&.directory}: #{e}" + nil end end end From c799c2bd7bf756c366608754cc26f9ee304ea2e4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 20:45:28 -0400 Subject: [PATCH 344/460] Adjust specs to feature --- spec/type_checker/levels/alpha_spec.rb | 14 ++++++++++++ spec/type_checker/levels/strict_spec.rb | 30 ++++++++++++++----------- spec/type_checker/levels/strong_spec.rb | 21 ++++------------- 3 files changed, 35 insertions(+), 30 deletions(-) diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 3ff5aa6c3..7d3833469 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -5,6 +5,20 @@ def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :alpha) end + it 'does not falsely enforce nil in return types' do + pending('type inference fix') + + checker = type_checker(%( + # @return [Integer] + def foo + # @sg-ignore + # @type [Integer, nil] + a = bar + a || 123 + end + )) + expect(checker.problems.map(&:message)).to be_empty + end it 'reports nilable type issues' do checker = type_checker(%( diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 45a310cd6..361a4038c 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -5,6 +5,23 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strict) end + it 'ignores nilable type issues' do + pending("moving nilable handling back to strong") + + checker = type_checker(%( + # @param a [String] + # @return [void] + def foo(a); end + + # @param b [String, nil] + # @return [void] + def bar(b) + foo(b) + end + )) + expect(checker.problems.map(&:message)).to eq([]) + end + it 'handles compatible interfaces with self types on call' do checker = type_checker(%( # @param a [Enumerable] @@ -788,19 +805,6 @@ def meth(param1) expect(checker.problems.map(&:message)).to eq(['Unresolved call to upcase']) end - it 'does not falsely enforce nil in return types' do - checker = type_checker(%( - # @return [Integer] - def foo - # @sg-ignore - # @type [Integer, nil] - a = bar - a || 123 - end - )) - expect(checker.problems.map(&:message)).to be_empty - end - it 'refines types on is_a? and && to downcast and avoid false positives' do checker = type_checker(%( def foo diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 47919c870..57e43f26a 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -4,7 +4,7 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end - it 'does not complain on array dereference' do + it 'does gives correct complaint on array dereference with nilable type' do checker = type_checker(%( # @param idx [Integer, nil] an index # @param arr [Array] an array of integers @@ -14,7 +14,9 @@ def foo(idx, arr) arr[idx] end )) - expect(checker.problems.map(&:message)).to be_empty + # may also give errors about other arities; not really too + # worried about that + expect(checker.problems.map(&:message)).to include("Wrong argument type for Array#[]: index expected Integer, received Integer, nil") end it 'complains on bad @type assignment' do @@ -67,21 +69,6 @@ def bar; end expect(checker.problems.first.message).to include('Missing @return tag') end - it 'ignores nilable type issues' do - checker = type_checker(%( - # @param a [String] - # @return [void] - def foo(a); end - - # @param b [String, nil] - # @return [void] - def bar(b) - foo(b) - end - )) - expect(checker.problems.map(&:message)).to eq([]) - end - it 'calls out keyword issues even when required arg count matches' do checker = type_checker(%( # @param a [String] From 087ce9f325e882ea5e46ffbdec56362ded0bfa4a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 21:01:59 -0400 Subject: [PATCH 345/460] Adjust specs to feature --- spec/type_checker/levels/typed_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index 681d813d5..b4a62f369 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -253,6 +253,8 @@ def bar end it 'allows loose return tags' do + pending('temporary move is reversed') + checker = type_checker(%( class Foo # The tag is [String] but the inference is [String, nil] From df2573539463c71815ed97ee4ed1d3347a03cf64 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 20:45:04 -0400 Subject: [PATCH 346/460] Fix types --- lib/solargraph/api_map.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index eb86aa4b1..5ea0166f0 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -306,13 +306,13 @@ def dereference(pin) end # @param fqns [String] - # @return [Array] + # @return [Array] def get_extends(fqns) store.get_extends(fqns) end # @param fqns [String] - # @return [Array] + # @return [Array] def get_includes(fqns) store.get_includes(fqns) end @@ -762,6 +762,9 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if scope == :instance store.get_includes(fqns).reverse.each do |ref| + # @sg-ignore Declared type Solargraph::Pin::Constant does + # not match inferred type Solargraph::Pin::Constant, + # Solargraph::Pin::Namespace, nil for variable const const = get_constants('', *ref.closure.gates).find { |pin| pin.path.end_with? ref.name } if const.is_a?(Pin::Namespace) result.concat inner_get_methods(const.path, scope, visibility, deep, skip, true) From f74f255442ede1a9ae76dcc80d77012367186b68 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 20:45:23 -0400 Subject: [PATCH 347/460] Fix types --- lib/solargraph/api_map/index.rb | 8 ++++---- lib/solargraph/api_map/store.rb | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index b06366f97..c311a9782 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -43,7 +43,7 @@ def pins_by_class klass @pin_select_cache[klass] ||= pin_class_hash.each_with_object(s) { |(key, o), n| n.merge(o) if key <= klass } end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def include_references @include_references ||= Hash.new { |h, k| h[k] = [] } end @@ -53,17 +53,17 @@ def include_reference_pins @include_reference_pins ||= Hash.new { |h, k| h[k] = [] } end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def extend_references @extend_references ||= Hash.new { |h, k| h[k] = [] } end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def prepend_references @prepend_references ||= Hash.new { |h, k| h[k] = [] } end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def superclass_references @superclass_references ||= Hash.new { |h, k| h[k] = [] } end diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index d89d1a489..545d99540 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -79,7 +79,7 @@ def get_methods fqns, scope: :instance, visibility: [:public] OBJECT_SUPERCLASS_PIN = Pin::Reference::Superclass.new(name: 'Object', closure: Pin::ROOT_PIN, source: :solargraph) # @param fqns [String] - # @return [Pin::Reference::Superclass] + # @return [Pin::Reference::Superclass, nil] def get_superclass fqns return nil if fqns.nil? || fqns.empty? return BOOLEAN_SUPERCLASS_PIN if %w[TrueClass FalseClass].include?(fqns) @@ -298,12 +298,12 @@ def symbols index.pins_by_class(Pin::Symbol) end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def superclass_references index.superclass_references end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def include_references index.include_references end @@ -313,12 +313,12 @@ def include_reference_pins index.include_reference_pins end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def prepend_references index.prepend_references end - # @return [Hash{String => Array}] + # @return [Hash{String => Array}] def extend_references index.extend_references end From 135e50db2a27741618e9d76c39b4ec565d6df800 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 21:18:22 -0400 Subject: [PATCH 348/460] Fix types --- lib/solargraph/api_map/store.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 545d99540..a286b851b 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -101,7 +101,7 @@ def qualify_superclass fq_sub_tag end # @param fqns [String] - # @return [Array] + # @return [Array] def get_includes fqns include_references[fqns] || [] end @@ -113,7 +113,7 @@ def get_prepends fqns end # @param fqns [String] - # @return [Array] + # @return [Array] def get_extends fqns extend_references[fqns] || [] end From 27bde68aaf7e3cc0380d04b4a446f9fc387cdb92 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 18 Sep 2025 05:43:49 -0400 Subject: [PATCH 349/460] Unused argument --- lib/solargraph/api_map/constants.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index b9e7865d2..2080e1b4f 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -92,7 +92,7 @@ def resolve_uncached name, gates # @return [String, nil] def complex_resolve name, gates, internal resolved = nil - gates.each.with_index do |gate, idx| + gates.each do |gate| resolved = simple_resolve(name, gate, internal) return resolved if resolved store.get_ancestor_references(gate).each do |ref| From fe67c88823547655f30e931d369fc9b01bbaee0a Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 18 Sep 2025 05:56:08 -0400 Subject: [PATCH 350/460] Revert method and parameter pin changes --- lib/solargraph/pin/method.rb | 2 +- lib/solargraph/pin/parameter.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index fe6e16e54..295b72fbf 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -556,7 +556,7 @@ def resolve_reference ref, api_map if parts.first.empty? || parts.one? path = "#{namespace}#{ref}" else - fqns = api_map.resolve(parts.first, *gates) + fqns = api_map.qualify(parts.first, *gates) return ComplexType::UNDEFINED if fqns.nil? path = fqns + ref[parts.first.length] + parts.last end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 3b4458183..947513689 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -251,7 +251,7 @@ def resolve_reference ref, api_map, skip if parts.first.empty? path = "#{namespace}#{ref}" else - fqns = api_map.resolve(parts.first, namespace) + fqns = api_map.qualify(parts.first, namespace) return nil if fqns.nil? path = fqns + ref[parts.first.length] + parts.last end From 3c5d3c97fd64cf6c0db32201b90e991385d3a1d0 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 18 Sep 2025 07:34:45 -0400 Subject: [PATCH 351/460] Constants resumes open gates for deep name collisions --- lib/solargraph/api_map/constants.rb | 37 ++++++++++++++++++++--------- lib/solargraph/type_checker.rb | 2 +- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 2080e1b4f..a4c991f60 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -51,7 +51,16 @@ def qualify name, *gates return name if ['Boolean', 'self', nil].include?(name) gates.push '' unless gates.include?('') - resolve(name, gates) + fqns = resolve(name, gates) + return unless fqns + pin = store.get_path_pins(fqns).first + if pin.is_a?(Pin::Constant) + const = Solargraph::Parser::NodeMethods.unpack_name(pin.assignment) + return unless const + resolve(const, pin.gates) + else + fqns + end end # @return [void] @@ -78,10 +87,15 @@ def resolve_uncached name, gates resolved = nil base = gates parts = name.split('::') + first = nil parts.each.with_index do |nam, idx| - resolved = complex_resolve(nam, base, idx != parts.length - 1) - break unless resolved - base = [resolved] + resolved, remainder = complex_resolve(nam, base, idx != parts.length - 1) + first ||= remainder + if resolved + base = [resolved] + else + return resolve(name, first) unless first.empty? + end end resolved end @@ -89,25 +103,26 @@ def resolve_uncached name, gates # @param name [String] # @param gates [Array] # @param internal [Boolean] True if the name is not the last in the namespace - # @return [String, nil] + # @return [Array] def complex_resolve name, gates, internal resolved = nil - gates.each do |gate| + gates.each.with_index do |gate, idx| resolved = simple_resolve(name, gate, internal) - return resolved if resolved - store.get_ancestor_references(gate).each do |ref| + return [resolved, gates[idx + 1..]] if resolved + (store.get_ancestor_references(gate)).each do |ref| mixin = resolve(ref.name, ref.reference_gates - [gate]) + next unless mixin resolved = simple_resolve(name, mixin, internal) - return resolved if resolved + return [resolved, gates[idx + 1..]] if resolved end end - nil + [nil, []] end # @param name [String] # @param gate [String] # @param internal [Boolean] True if the name is not the last in the namespace - # @return [Pin::Constant, Pin::Namespace, nil] + # @return [String, nil] def simple_resolve name, gate, internal here = "#{gate}::#{name}".sub(/^::/, '').sub(/::$/, '') pin = store.get_path_pins(here).first diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index b8bb6e82a..88190409a 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -96,7 +96,7 @@ def method_tag_problems def method_return_type_problems_for pin return [] if pin.is_a?(Pin::MethodAlias) result = [] - declared = pin.typify(api_map).self_to_type(pin.full_context).qualify(api_map, *pin.closure.gates) + declared = pin.typify(api_map).self_to_type(pin.full_context).qualify(api_map, *pin.gates) if declared.undefined? if pin.return_type.undefined? && rules.require_type_tags? if pin.attribute? From 67f651719a7a6051b1823c738650d484e87639fd Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 18 Sep 2025 07:35:07 -0400 Subject: [PATCH 352/460] Typechecker specs --- spec/type_checker/levels/normal_spec.rb | 21 +++++++++++++++++++++ spec/type_checker/levels/typed_spec.rb | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/spec/type_checker/levels/normal_spec.rb b/spec/type_checker/levels/normal_spec.rb index 3b38f55d8..9af91efe1 100644 --- a/spec/type_checker/levels/normal_spec.rb +++ b/spec/type_checker/levels/normal_spec.rb @@ -909,5 +909,26 @@ def get_a_mutex; end )) expect(checker.problems).to be_empty end + + it 'resolves namespace gate conflicts' do + checker = type_checker(%( + class Base + class Target + end + end + + module Other + class Base + end + + class Deep + # @return [Base::Target] + def foo + end + end + end + )) + expect(checker.problems).to be_empty + end end end diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index b10bbd42c..b2071465e 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -388,7 +388,7 @@ def nil_assignment? 'foo'.nil? # infers as 'false' end )) - expect(checker.problems.map(&:message)).to be_empty + expect(checker.problems).to be_empty end end end From af51344e07825c8e363f9c53a5732e83f1f7b5a0 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 18 Sep 2025 07:42:02 -0400 Subject: [PATCH 353/460] Linting --- lib/solargraph/api_map/constants.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index a4c991f60..0c83d967d 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -109,7 +109,7 @@ def complex_resolve name, gates, internal gates.each.with_index do |gate, idx| resolved = simple_resolve(name, gate, internal) return [resolved, gates[idx + 1..]] if resolved - (store.get_ancestor_references(gate)).each do |ref| + store.get_ancestor_references(gate).each do |ref| mixin = resolve(ref.name, ref.reference_gates - [gate]) next unless mixin resolved = simple_resolve(name, mixin, internal) From 9009a2ca34c39cf42b7aa433e1cfbfff226dbfe0 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 18 Sep 2025 07:58:48 -0400 Subject: [PATCH 354/460] Documentation --- lib/solargraph/api_map.rb | 2 ++ lib/solargraph/api_map/constants.rb | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index f233ecb74..44ca19035 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -297,6 +297,8 @@ def qualify tag, *gates store.constants.qualify(tag, *gates) end + # @see Store::Constants#resolve + # # @param name [String] # @param gates [Array>] # @return [String, nil] diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 0c83d967d..2e38b9ad6 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -12,6 +12,11 @@ def initialize store # Resolve a name to a fully qualified namespace or constant. # + # `Constants#resolve` is similar to `Constants#qualify`` in that its + # purpose is to find fully qualified (absolute) namespaces, except + # `#resolve`` is only concerned with real namespaces. It disregards + # parametrized types and special types like literals, self, and Boolean. + # # @param name [String] # @param gates [Array, String>] # @return [String, nil] @@ -108,12 +113,12 @@ def complex_resolve name, gates, internal resolved = nil gates.each.with_index do |gate, idx| resolved = simple_resolve(name, gate, internal) - return [resolved, gates[idx + 1..]] if resolved + return [resolved, gates[(idx + 1)..]] if resolved store.get_ancestor_references(gate).each do |ref| mixin = resolve(ref.name, ref.reference_gates - [gate]) next unless mixin resolved = simple_resolve(name, mixin, internal) - return [resolved, gates[idx + 1..]] if resolved + return [resolved, gates[(idx + 1)..]] if resolved end end [nil, []] From 8e171845184a82d51fb8085c32ae3f2d1bd9a5d1 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 18 Sep 2025 08:05:51 -0400 Subject: [PATCH 355/460] Typechecking --- lib/solargraph/api_map/constants.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 2e38b9ad6..333b5f8fb 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -105,10 +105,14 @@ def resolve_uncached name, gates resolved end + # @todo I'm not sure of a better way to express the return value in YARD. + # It's a tuple where the first element is a nullable string. Something + # like `Array(String|nil, Array)` would be more accurate. + # # @param name [String] # @param gates [Array] # @param internal [Boolean] True if the name is not the last in the namespace - # @return [Array] + # @return [Array(Object, Array)] def complex_resolve name, gates, internal resolved = nil gates.each.with_index do |gate, idx| From ed3809f4695a3555892a84a48f36d11328de9003 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 18 Sep 2025 09:43:27 -0400 Subject: [PATCH 356/460] Fix ancestral recursion --- lib/solargraph/api_map/constants.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 333b5f8fb..552a6b04e 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -119,7 +119,8 @@ def complex_resolve name, gates, internal resolved = simple_resolve(name, gate, internal) return [resolved, gates[(idx + 1)..]] if resolved store.get_ancestor_references(gate).each do |ref| - mixin = resolve(ref.name, ref.reference_gates - [gate]) + return ref.name if ref.name.end_with?("::#{name}") + mixin = resolve(ref.name, ref.reference_gates - gates) next unless mixin resolved = simple_resolve(name, mixin, internal) return [resolved, gates[(idx + 1)..]] if resolved From 1864b67a3818113bdb8d108c8978ac4b5c48e783 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 18 Sep 2025 10:22:20 -0400 Subject: [PATCH 357/460] Constants strips leading namespace separators --- lib/solargraph/api_map/constants.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 552a6b04e..430303ae1 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -119,7 +119,7 @@ def complex_resolve name, gates, internal resolved = simple_resolve(name, gate, internal) return [resolved, gates[(idx + 1)..]] if resolved store.get_ancestor_references(gate).each do |ref| - return ref.name if ref.name.end_with?("::#{name}") + return ref.name.sub(/^::/, '') if ref.name.end_with?("::#{name}") mixin = resolve(ref.name, ref.reference_gates - gates) next unless mixin resolved = simple_resolve(name, mixin, internal) From e5d88a51307a948de85880fcbbde4a8ec790d199 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 19 Sep 2025 18:15:17 -0400 Subject: [PATCH 358/460] Show all evaluated types in typechecker --- lib/solargraph/type_checker.rb | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index c315a407f..9b98d3d74 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -295,15 +295,20 @@ def call_problems if type.undefined? && !rules.ignore_all_undefined? base = chain missing = chain + # @type [Solargraph::Pin::Base, nil] found = nil + # @type [Array] + all_found = [] closest = ComplexType::UNDEFINED until base.links.first.undefined? - found = base.define(api_map, block_pin, locals).first + all_found = base.define(api_map, block_pin, locals) + found = all_found.first break if found missing = base base = base.base end - closest = found.typify(api_map) if found + all_closest = all_found.map { |pin| pin.typify(api_map) } + closest = ComplexType.new(all_closest.flat_map(&:items).uniq) # @todo remove the internal_or_core? check at a higher-than-strict level if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) unless closest.generic? || ignored_pins.include?(found) @@ -603,15 +608,20 @@ def declared_externally? pin if type.undefined? && !rules.ignore_all_undefined? base = chain missing = chain + # @type [Solargraph::Pin::Base, nil] found = nil + # @type [Array] + all_found = [] closest = ComplexType::UNDEFINED until base.links.first.undefined? - found = base.define(api_map, block_pin, locals).first + all_found = base.define(api_map, block_pin, locals) + found = all_found.first break if found missing = base base = base.base end - closest = found.typify(api_map) if found + all_closest = all_found.map { |pin| pin.typify(api_map) } + closest = ComplexType.new(all_closest.flat_map(&:items).uniq) if !found || closest.defined? || internal?(found) return false end From 3399448f739d09b0a1297e878fac5499ff9bb816 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 23 Sep 2025 20:59:58 -0400 Subject: [PATCH 359/460] Add @sg-ignore --- lib/solargraph/api_map/store.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 5978e322d..91db664b5 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -297,6 +297,10 @@ def fqns_pins_map end end + # @sg-ignore Rooted type issue here - "Declared return type + # ::Enumerable<::Solargraph::Pin::Symbol> does not match + # inferred type ::Set<::Symbol> for + # Solargraph::ApiMap::Store#symbols" # @return [Enumerable] def symbols index.pins_by_class(Pin::Symbol) From 1f13916947c0b70ba8543e70a4ca174fc7af1b67 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 23 Sep 2025 21:43:41 -0400 Subject: [PATCH 360/460] Fix bug in new typechecking code --- lib/solargraph/api_map.rb | 4 ++-- lib/solargraph/type_checker.rb | 16 +++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 7784d2111..efcd0056b 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -315,13 +315,13 @@ def dereference(pin) end # @param fqns [String] - # @return [Array] + # @return [Array] def get_extends(fqns) store.get_extends(fqns) end # @param fqns [String] - # @return [Array] + # @return [Array] def get_includes(fqns) store.get_includes(fqns) end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index e354a52bd..7250b7e25 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -170,7 +170,6 @@ def method_param_type_problems_for pin end end end - # @todo Should be able to probe type of name and data here # @param name [String] # @param data [Hash{Symbol => BasicObject}] params.each_pair do |name, data| @@ -487,18 +486,25 @@ def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kw # @param param_details [Hash{String => Hash{Symbol => String, ComplexType}}] # @param pin [Pin::Method, Pin::Signature] + # @param relevant_pin [Pin::Method, Pin::Signature] the pin which is under inspection # @return [void] - def add_restkwarg_param_tag_details(param_details, pin) + def add_restkwarg_param_tag_details(param_details, pin, relevant_pin) + return unless pin.parameters.any? { |parameter| parameter.decl == :kwrestarg } + # see if we have additional tags to pay attention to from YARD - # e.g., kwargs in a **restkwargs splat tags = pin.docstring.tags(:param) tags.each do |tag| next if param_details.key? tag.name.to_s next if tag.types.nil? - param_details[tag.name.to_s] = { + details = { tagged: tag.types.join(', '), qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace) } + # don't complain about a param that didn't come from the pin we're looking at anyway + if details[:qualified].defined? || relevant_pin == pin + param_details[tag.name.to_s] = details + end end end @@ -557,12 +563,12 @@ def param_details_from_stack(signature, method_pin_stack) param_names = signature.parameter_names method_pin_stack.each do |method_pin| - add_restkwarg_param_tag_details(param_details, method_pin) + add_restkwarg_param_tag_details(param_details, method_pin, signature) # documentation of types in superclasses should fail back to # subclasses if the subclass hasn't documented something method_pin.signatures.each do |sig| - add_restkwarg_param_tag_details(param_details, sig) + add_restkwarg_param_tag_details(param_details, sig, signature) add_to_param_details param_details, param_names, signature_param_details(sig) end end From 2c050ea9ba947b507896f0f1718a43d49c9343ff Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 23 Sep 2025 21:56:26 -0400 Subject: [PATCH 361/460] Fix bug in new typechecking code --- lib/solargraph/type_checker.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 7250b7e25..44a700534 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -489,8 +489,6 @@ def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kw # @param relevant_pin [Pin::Method, Pin::Signature] the pin which is under inspection # @return [void] def add_restkwarg_param_tag_details(param_details, pin, relevant_pin) - return unless pin.parameters.any? { |parameter| parameter.decl == :kwrestarg } - # see if we have additional tags to pay attention to from YARD - # e.g., kwargs in a **restkwargs splat tags = pin.docstring.tags(:param) @@ -502,7 +500,8 @@ def add_restkwarg_param_tag_details(param_details, pin, relevant_pin) qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace) } # don't complain about a param that didn't come from the pin we're looking at anyway - if details[:qualified].defined? || relevant_pin == pin + if details[:qualified].defined? || + relevant_pin.parameters.any? { |parameter| parameter.decl == :kwrestarg || parameter.name == tag.name.to_s } param_details[tag.name.to_s] = details end end From 8b24ce9825950070eae4a85d02792d31a2069362 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 23 Sep 2025 22:08:39 -0400 Subject: [PATCH 362/460] Fix bug in new typechecking code --- lib/solargraph/type_checker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 44a700534..a79907bbc 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -501,7 +501,7 @@ def add_restkwarg_param_tag_details(param_details, pin, relevant_pin) } # don't complain about a param that didn't come from the pin we're looking at anyway if details[:qualified].defined? || - relevant_pin.parameters.any? { |parameter| parameter.decl == :kwrestarg || parameter.name == tag.name.to_s } + relevant_pin.parameter_names.include?(tag.name.to_s) param_details[tag.name.to_s] = details end end From f2ec1b8f1c9d44ee3c16c147fbc61b9b9394037b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 11:51:39 -0400 Subject: [PATCH 363/460] Fix merge --- .rubocop_todo.yml | 24 ++++++++++++++---------- lib/solargraph/doc_map.rb | 1 - 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 33e766329..d877296dd 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -271,7 +271,6 @@ Layout/TrailingWhitespace: # Configuration parameters: AllowedMethods, AllowedPatterns. Lint/AmbiguousBlockAssociation: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/language_server/host.rb' # This cop supports safe autocorrection (--autocorrect). @@ -663,7 +662,12 @@ RSpec/NotToNot: - 'spec/rbs_map/core_map_spec.rb' RSpec/PendingWithoutReason: - Enabled: false + Exclude: + - 'spec/api_map_method_spec.rb' + - 'spec/api_map_spec.rb' + - 'spec/doc_map_spec.rb' + - 'spec/pin/local_variable_spec.rb' + - 'spec/type_checker/levels/strict_spec.rb' # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Strict, EnforcedStyle, AllowedExplicitMatchers. @@ -1060,7 +1064,6 @@ Style/RedundantFreeze: # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: - - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/source_map/mapper.rb' @@ -1085,8 +1088,8 @@ Style/RedundantRegexpEscape: # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/complex_type/type_methods.rb' + - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/parser/parser_gem/node_methods.rb' - 'lib/solargraph/source/chain/z_super.rb' @@ -1118,6 +1121,7 @@ Style/SafeNavigation: # Configuration parameters: Max. Style/SafeNavigationChainLength: Exclude: + - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/workspace/gemspecs.rb' # This cop supports unsafe autocorrection (--autocorrect-all). @@ -1219,7 +1223,11 @@ Style/YAMLFileRead: # This cop supports unsafe autocorrection (--autocorrect-all). Style/ZeroLengthPredicate: - Enabled: false + Exclude: + - 'lib/solargraph/language_server/host.rb' + - 'lib/solargraph/pin/method.rb' + - 'lib/solargraph/source/chain/array.rb' + - 'spec/language_server/protocol_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. @@ -1235,11 +1243,7 @@ YARD/MismatchName: Enabled: false YARD/TagTypeSyntax: - Exclude: - - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/parser/comment_ripper.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/type_checker.rb' + Enabled: false # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index f551f2510..63599988b 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -373,7 +373,6 @@ def only_runtime_dependencies gemspec gemspec.dependencies - gemspec.development_dependencies end - def inspect self.class.inspect end From 8451621c3f24161b3df2c939e0bda549560d8b38 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 12:14:24 -0400 Subject: [PATCH 364/460] Fix merge --- lib/solargraph/api_map.rb | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index b40b048e7..e5263d7b7 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -723,21 +723,6 @@ def inner_get_methods_from_reference(fq_reference_tag, namespace_pin, type, scop methods end - # @param fq_sub_tag [String] - # @return [String, nil] - def qualify_superclass fq_sub_tag - fq_sub_type = ComplexType.try_parse(fq_sub_tag) - fq_sub_ns = fq_sub_type.name - sup_tag = store.get_superclass(fq_sub_tag) - sup_type = ComplexType.try_parse(sup_tag) - sup_ns = sup_type.name - return nil if sup_tag.nil? - parts = fq_sub_ns.split('::') - last = parts.pop - parts.pop if last == sup_ns - qualify(sup_tag, parts.join('::')) - end - private # A hash of source maps with filename keys. @@ -805,16 +790,14 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false type = const.infer(self) result.concat inner_get_methods(type.namespace, scope, visibility, deep, skip, true) if type.defined? else - referenced_type = ref.parametrized_tag - next unless referenced_type.defined? - rooted_include_tag = qualify(referenced_type.rooted_tag, rooted_tag) - result.concat inner_get_methods_from_reference(rooted_include_tag, namespace_pin, rooted_type, scope, visibility, deep, skip, true) + referenced_tag = ref.parametrized_tag + next unless referenced_tag.defined? + result.concat inner_get_methods_from_reference(referenced_tag.to_s, namespace_pin, rooted_type, scope, visibility, deep, skip, true) end end rooted_sc_tag = qualify_superclass(rooted_tag) unless rooted_sc_tag.nil? - result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, - visibility, true, skip, no_core) + result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, visibility, true, skip, no_core) end else logger.info { "ApiMap#inner_get_methods(#{fqns}, #{scope}, #{visibility}, #{deep}, #{skip}) - looking for get_extends() from #{fqns}" } @@ -824,8 +807,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false end rooted_sc_tag = qualify_superclass(rooted_tag) unless rooted_sc_tag.nil? - result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, - visibility, true, skip, true) + result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, visibility, true, skip, true) end unless no_core || fqns.empty? type = get_namespace_type(fqns) From 4eda431ce26211bf4d6955cbae997b0e79ffe4d4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 14:56:12 -0400 Subject: [PATCH 365/460] Handle RBS static method aliases Should fix solargraph-rails spec failures --- lib/solargraph/api_map.rb | 2 +- spec/rbs_map/conversions_spec.rb | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 44ca19035..e176a10cd 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -860,7 +860,7 @@ def prefer_non_nil_variables pins # @param alias_pin [Pin::MethodAlias] # @return [Pin::Method, nil] def resolve_method_alias(alias_pin) - ancestors = store.get_ancestors(alias_pin.full_context.tag) + ancestors = store.get_ancestors(alias_pin.full_context.reduce_class_type.tag) original = nil # Search each ancestor for the original method diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 1df43af26..8afdeca2d 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -25,6 +25,29 @@ attr_reader :temp_dir + context 'with self alias to self method' do + let(:rbs) do + <<~RBS + class Foo + def self.bar: () -> String + alias self.bar? self.bar + end + RBS + end + + subject(:alias_pin) { api_map.get_method_stack('Foo', 'bar?', scope: :class).first } + + let(:method_pin) { api_map.get_method_stack('Foo', 'bar', scope: :class).first } + + it { should_not be_nil } + + it { should be_instance_of(Solargraph::Pin::Method) } + + it 'finds the type' do + expect(alias_pin.return_type.tag).to eq('String') + end + end + context 'with untyped response' do let(:rbs) do <<~RBS From 037307ed9fc9945810c64ce867c9e09a4003cd7c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 15:17:37 -0400 Subject: [PATCH 366/460] Fix #reduce_class_type --- lib/solargraph/complex_type.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 8798ecb88..669a66900 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -246,6 +246,7 @@ def all_params def reduce_class_type new_items = items.flat_map do |type| next type unless ['Module', 'Class'].include?(type.name) + next type if type.all_params.empty? type.all_params end From 25b0770551d5b5c9a702dfbfc90dcd7f01685c1a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 15:20:10 -0400 Subject: [PATCH 367/460] linting --- spec/rbs_map/conversions_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 8afdeca2d..d1d3e564d 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -35,10 +35,10 @@ def self.bar: () -> String RBS end - subject(:alias_pin) { api_map.get_method_stack('Foo', 'bar?', scope: :class).first } - let(:method_pin) { api_map.get_method_stack('Foo', 'bar', scope: :class).first } + subject(:alias_pin) { api_map.get_method_stack('Foo', 'bar?', scope: :class).first } + it { should_not be_nil } it { should be_instance_of(Solargraph::Pin::Method) } From 6a7a37feedad44e0be8bf0fba8c5f9d4e9b83964 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 16:49:01 -0400 Subject: [PATCH 368/460] Fix RuboCop issues --- spec/rbs_map/conversions_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 4ed3f511d..31c354023 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -52,6 +52,8 @@ class C < ::B::C end context 'with self alias to self method' do + subject(:alias_pin) { api_map.get_method_stack('Foo', 'bar?', scope: :class).first } + let(:rbs) do <<~RBS class Foo @@ -63,11 +65,9 @@ def self.bar: () -> String let(:method_pin) { api_map.get_method_stack('Foo', 'bar', scope: :class).first } - subject(:alias_pin) { api_map.get_method_stack('Foo', 'bar?', scope: :class).first } - - it { should_not be_nil } + it { is_expected.not_to be_nil } - it { should be_instance_of(Solargraph::Pin::Method) } + it { is_expected.to be_instance_of(Solargraph::Pin::Method) } it 'finds the type' do expect(alias_pin.return_type.tag).to eq('String') From 9ef047fd417b41cc8b3e2bda2d5d0f5e938be252 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 17:12:31 -0400 Subject: [PATCH 369/460] Revert "Fix case where include tags need to be qualified [regression]" This reverts commit 3e3d802750203dbe6d953daf90c90178b1aebacb. --- spec/api_map/include_spec.rb | 92 ------------------------------------ 1 file changed, 92 deletions(-) delete mode 100644 spec/api_map/include_spec.rb diff --git a/spec/api_map/include_spec.rb b/spec/api_map/include_spec.rb deleted file mode 100644 index 7f3b6efa7..000000000 --- a/spec/api_map/include_spec.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -describe Solargraph::ApiMap do - let(:api_map) { described_class.new.map(source) } - - context 'with a non-rooted include in local source' do - let :source do - Solargraph::Source.load_string(%( - module A - module B - # @return [String] - def foo - 'foo' - end - end - end - - class A::C - include B - end - ), 'test.rb') - end - - it 'understands method' do - pin = api_map.get_method_stack('A::B', 'foo', scope: :instance) - expect(pin.map(&:return_type).map(&:tags)).to eq(['String']) - end - - it 'handles includes via relative name' do - api_map = described_class.new.map(source) - - pin = api_map.get_method_stack('A::C', 'foo', scope: :instance) - expect(pin.map(&:return_type).map(&:rooted_tags)).to eq(['String']) - end - end - - context 'with a non-rooted include in RBS' do - # create a temporary directory with the scope of the spec - around do |example| - require 'tmpdir' - Dir.mktmpdir('rspec-solargraph-') do |dir| - @temp_dir = dir - example.run - end - end - - attr_reader :temp_dir - - let(:rbs) do - <<~RBS - module A - module B - def foo: () -> String - end - - class E - def foo: () -> String - end - - class D < C - end - end - class A::C - include B - end - RBS - end - - let(:conversions) do - loader = RBS::EnvironmentLoader.new(core_root: nil, repository: RBS::Repository.new(no_stdlib: false)) - loader.add(path: Pathname(temp_dir)) - Solargraph::RbsMap::Conversions.new(loader: loader) - end - - let(:api_map) { described_class.new pins: conversions.pins } - - before do - rbs_file = File.join(temp_dir, 'foo.rbs') - File.write(rbs_file, rbs) - end - - it 'understands method' do - pin = api_map.get_method_stack('A::B', 'foo', scope: :instance) - expect(pin.map(&:return_type).map(&:tags)).to eq(['String']) - end - - it 'handles includes via relative name' do - pin = api_map.get_method_stack('A::C', 'foo', scope: :instance) - expect(pin.map(&:return_type).map(&:tags)).to eq(['String']) - end - end -end From 7bc60eb31ca7ecac24302e93ff0d3a92bfe30555 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 17:16:40 -0400 Subject: [PATCH 370/460] Mark specs as working --- spec/type_checker/levels/strict_spec.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 361a4038c..0a8a3d90c 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -557,8 +557,6 @@ def bar(baz:, bing:) end it 'requires strict return tags' do - pending 'nil? support in flow sensitive typing' - checker = type_checker(%( class Foo # The tag is [String] but the inference is [String, nil] @@ -574,8 +572,6 @@ def bar end it 'requires strict return tags' do - pending 'nil? support in flow sensitive typing' - checker = type_checker(%( class Foo # The tag is [String] but the inference is [String, nil] From 6a6cd95b4f91c085d713589441d4b22f70274032 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 17:33:29 -0400 Subject: [PATCH 371/460] Revert "Enable union type checking (at typed level temporarily, soon strict)" This reverts commit 7c8d200225cc2e7817e26657ec0909b739669f6e. --- lib/solargraph/api_map.rb | 2 -- lib/solargraph/api_map/source_to_yard.rb | 3 -- lib/solargraph/api_map/store.rb | 1 - lib/solargraph/complex_type.rb | 2 +- lib/solargraph/complex_type/unique_type.rb | 3 +- .../data_definition/data_assignment_node.rb | 1 - .../data_definition/data_definition_node.rb | 6 ++-- .../convention/struct_definition.rb | 1 - .../struct_assignment_node.rb | 1 - .../struct_definition_node.rb | 3 -- lib/solargraph/diagnostics/type_check.rb | 1 - lib/solargraph/doc_map.rb | 36 ------------------- lib/solargraph/language_server/host.rb | 1 - .../language_server/host/dispatch.rb | 1 - .../message/extended/check_gem_version.rb | 1 - .../message/text_document/formatting.rb | 3 +- lib/solargraph/library.rb | 2 -- lib/solargraph/location.rb | 1 - lib/solargraph/page.rb | 1 - lib/solargraph/parser/comment_ripper.rb | 3 -- .../parser/flow_sensitive_typing.rb | 4 --- .../parser/parser_gem/class_methods.rb | 1 - .../parser/parser_gem/node_methods.rb | 1 - .../parser_gem/node_processors/ivasgn_node.rb | 4 --- .../parser_gem/node_processors/masgn_node.rb | 3 -- .../parser_gem/node_processors/sclass_node.rb | 2 -- .../parser_gem/node_processors/send_node.rb | 1 - lib/solargraph/pin/base.rb | 19 ++-------- lib/solargraph/pin/base_variable.rb | 1 - lib/solargraph/pin/callable.rb | 1 - lib/solargraph/pin/closure.rb | 8 +++++ lib/solargraph/pin/common.rb | 1 - lib/solargraph/pin/constant.rb | 1 - lib/solargraph/pin/conversions.rb | 3 -- lib/solargraph/pin/delegated_method.rb | 1 - lib/solargraph/pin/documenting.rb | 1 - lib/solargraph/pin/keyword.rb | 1 - lib/solargraph/pin/method.rb | 5 --- lib/solargraph/pin/namespace.rb | 1 - lib/solargraph/pin/parameter.rb | 8 +---- lib/solargraph/pin/signature.rb | 5 --- lib/solargraph/pin/symbol.rb | 1 - lib/solargraph/pin_cache.rb | 2 -- lib/solargraph/rbs_map.rb | 3 -- lib/solargraph/source.rb | 5 --- lib/solargraph/source/cursor.rb | 2 -- lib/solargraph/source/source_chainer.rb | 3 -- lib/solargraph/source/updater.rb | 1 - lib/solargraph/source_map.rb | 10 ++---- lib/solargraph/source_map/clip.rb | 1 - lib/solargraph/type_checker/rules.rb | 14 +------- lib/solargraph/workspace.rb | 1 - lib/solargraph/workspace/config.rb | 1 - 53 files changed, 20 insertions(+), 169 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index d6dc059d8..6ae1e6f66 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -132,7 +132,6 @@ def doc_map @doc_map ||= DocMap.new([], Workspace.new('.')) end - # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [::Array] def uncached_gemspecs doc_map.uncached_gemspecs || [] @@ -216,7 +215,6 @@ class << self # any missing gems. # # - # @sg-ignore Declared type IO does not match inferred type IO, StringIO for variable out # @param directory [String] # @param out [IO] The output stream for messages # diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index f2a5d156a..ccbed3eb6 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -6,9 +6,6 @@ module SourceToYard # Get the YARD CodeObject at the specified path. # - # @sg-ignore Declared return type generic, nil does not match - # inferred type ::YARD::CodeObjects::Base, nil for - # Solargraph::ApiMap::SourceToYard#code_object_at # @generic T # @param path [String] # @param klass [Class>] diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 4622bc2f1..f4fc7a6dc 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -286,7 +286,6 @@ def catalog pinsets true end - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Hash{::Array(String, String) => ::Array}] def fqns_pins_map @fqns_pins_map ||= Hash.new do |h, (base, name)| diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index aea8420aa..630291092 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -363,7 +363,7 @@ class << self # # @todo Need ability to use a literal true as a type below # # @param partial [Boolean] True if the string is part of a another type # # @return [Array] - # @sg-ignore To be able to select the right signature above, + # @todo To be able to select the right signature above, # Chain::Call needs to know the decl type (:arg, :optarg, # :kwarg, etc) of the arguments given, instead of just having # an array of Chains as the arguments. diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index b0f8ba4f2..ef29f966e 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -254,7 +254,6 @@ def desc rooted_tags end - # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [String] def to_rbs if duck_type? @@ -264,7 +263,7 @@ def to_rbs elsif name.downcase == 'nil' 'nil' elsif name == GENERIC_TAG_NAME - all_params.first&.name || 'untyped' + all_params.first&.name elsif ['Class', 'Module'].include?(name) rbs_name elsif ['Tuple', 'Array'].include?(name) && fixed_parameters? diff --git a/lib/solargraph/convention/data_definition/data_assignment_node.rb b/lib/solargraph/convention/data_definition/data_assignment_node.rb index 7b4393a5c..cffe77494 100644 --- a/lib/solargraph/convention/data_definition/data_assignment_node.rb +++ b/lib/solargraph/convention/data_definition/data_assignment_node.rb @@ -47,7 +47,6 @@ def class_name private - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Parser::AST::Node] def data_node if node.children[2].type == :block diff --git a/lib/solargraph/convention/data_definition/data_definition_node.rb b/lib/solargraph/convention/data_definition/data_definition_node.rb index 906ef5cbf..fb160c58c 100644 --- a/lib/solargraph/convention/data_definition/data_definition_node.rb +++ b/lib/solargraph/convention/data_definition/data_definition_node.rb @@ -66,8 +66,7 @@ def attributes end.compact end - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 - # @return [Parser::AST::Node, nil] + # @return [Parser::AST::Node] def body_node node.children[2] end @@ -77,12 +76,11 @@ def body_node # @return [Parser::AST::Node] attr_reader :node - # @return [Parser::AST::Node, nil] + # @return [Parser::AST::Node] def data_node node.children[1] end - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Array] def data_attribute_nodes data_node.children[2..-1] diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index a812727d1..b34ae5494 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -137,7 +137,6 @@ def parse_comments # @param tag [YARD::Tags::Tag, nil] The param tag for this attribute.xtract_ # - # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [String] def tag_string(tag) tag&.types&.join(',') || 'undefined' diff --git a/lib/solargraph/convention/struct_definition/struct_assignment_node.rb b/lib/solargraph/convention/struct_definition/struct_assignment_node.rb index cc7600a4e..2816de6ed 100644 --- a/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_assignment_node.rb @@ -48,7 +48,6 @@ def class_name private - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Parser::AST::Node] def struct_node if node.children[2].type == :block diff --git a/lib/solargraph/convention/struct_definition/struct_definition_node.rb b/lib/solargraph/convention/struct_definition/struct_definition_node.rb index 581117093..725e4227f 100644 --- a/lib/solargraph/convention/struct_definition/struct_definition_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_definition_node.rb @@ -77,7 +77,6 @@ def keyword_init? keyword_init_param.children[0].children[1].type == :true end - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Parser::AST::Node] def body_node node.children[2] @@ -88,13 +87,11 @@ def body_node # @return [Parser::AST::Node] attr_reader :node - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Parser::AST::Node] def struct_node node.children[1] end - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Array] def struct_attribute_nodes struct_node.children[2..-1] diff --git a/lib/solargraph/diagnostics/type_check.rb b/lib/solargraph/diagnostics/type_check.rb index 583abd077..80f53eb7c 100644 --- a/lib/solargraph/diagnostics/type_check.rb +++ b/lib/solargraph/diagnostics/type_check.rb @@ -45,7 +45,6 @@ def extract_first_line location, source # @param position [Solargraph::Position] # @param source [Solargraph::Source] - # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [Integer] def last_character position, source cursor = Position.to_offset(source.code, position) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 63599988b..19eb0419d 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -89,42 +89,6 @@ def unresolved_requires @unresolved_requires ||= required_gems_map.select { |_, gemspecs| gemspecs.nil? }.keys end - # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version - def self.all_yard_gems_in_memory - @yard_gems_in_memory ||= {} - end - - # @return [Hash{String => Hash{Array(String, String) => Array}}] stored by RBS collection path - def self.all_rbs_collection_gems_in_memory - @rbs_collection_gems_in_memory ||= {} - end - - # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version - def yard_pins_in_memory - self.class.all_yard_gems_in_memory - end - - # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version - def rbs_collection_pins_in_memory - self.class.all_rbs_collection_gems_in_memory[rbs_collection_path] ||= {} - end - - # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version - def self.all_combined_pins_in_memory - @combined_pins_in_memory ||= {} - end - - # @todo this should also include an index by the hash of the RBS collection - # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version - def combined_pins_in_memory - self.class.all_combined_pins_in_memory - end - - # @return [Array] - def yard_plugins - @environ.yard_plugins - end - # @return [Set] # @param out [IO] def dependencies out: $stderr diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index 4fda6cf7b..b6efe93a3 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -724,7 +724,6 @@ def requests end # @param path [String] - # @sg-ignore Need to be able to choose signature on String#gsub # @return [String] def normalize_separators path return path if File::ALT_SEPARATOR.nil? diff --git a/lib/solargraph/language_server/host/dispatch.rb b/lib/solargraph/language_server/host/dispatch.rb index 48570028e..1ff1227b8 100644 --- a/lib/solargraph/language_server/host/dispatch.rb +++ b/lib/solargraph/language_server/host/dispatch.rb @@ -42,7 +42,6 @@ def update_libraries uri # Find the best libary match for the given URI. # # @param uri [String] - # @sg-ignore sensitive typing needs to handle || on nil types # @return [Library] def library_for uri result = explicit_library_for(uri) || diff --git a/lib/solargraph/language_server/message/extended/check_gem_version.rb b/lib/solargraph/language_server/message/extended/check_gem_version.rb index 008e26468..0e676f813 100644 --- a/lib/solargraph/language_server/message/extended/check_gem_version.rb +++ b/lib/solargraph/language_server/message/extended/check_gem_version.rb @@ -71,7 +71,6 @@ def process # @return [Gem::Version] attr_reader :current - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Gem::Version] def available if !@available && !@fetched diff --git a/lib/solargraph/language_server/message/text_document/formatting.rb b/lib/solargraph/language_server/message/text_document/formatting.rb index 821de7ffc..e6dca4bdb 100644 --- a/lib/solargraph/language_server/message/text_document/formatting.rb +++ b/lib/solargraph/language_server/message/text_document/formatting.rb @@ -96,9 +96,8 @@ def formatter_class(config) end # @param value [Array, String] - # @return [String, nil] + # @return [String] def cop_list(value) - # @type [String] value = value.join(',') if value.respond_to?(:join) return nil if value == '' || !value.is_a?(String) value diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 67ea0eaa8..43656b1cc 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -492,7 +492,6 @@ def pins @pins ||= [] end - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Set] def external_requires @external_requires ||= source_map_external_require_hash.values.flatten.to_set @@ -544,7 +543,6 @@ def api_map # # @raise [FileNotFoundError] if the file does not exist # @param filename [String] - # @sg-ignore flow sensitive typing needs to handle if foo && ... # @return [Solargraph::Source] def read filename return @current if @current && @current.filename == filename diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index b520ced79..131c8dc47 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -25,7 +25,6 @@ def initialize filename, range end # @param other [self] - # @sg-ignore Why does Solargraph think this should return 0, nil? def <=>(other) return nil unless other.is_a?(Location) if filename == other.filename diff --git a/lib/solargraph/page.rb b/lib/solargraph/page.rb index 7952310c5..5d879bbe1 100644 --- a/lib/solargraph/page.rb +++ b/lib/solargraph/page.rb @@ -17,7 +17,6 @@ class Binder < OpenStruct # @param locals [Hash] # @param render_method [Proc] def initialize locals, render_method - # @sg-ignore Too many arguments to BasicObject#initialize super(locals) define_singleton_method :render do |template, layout: false, locals: {}| render_method.call(template, layout: layout, locals: locals) diff --git a/lib/solargraph/parser/comment_ripper.rb b/lib/solargraph/parser/comment_ripper.rb index aa32cf498..92373df20 100644 --- a/lib/solargraph/parser/comment_ripper.rb +++ b/lib/solargraph/parser/comment_ripper.rb @@ -40,21 +40,18 @@ def create_snippet(result) @comments[result[2][0]] = Snippet.new(Range.from_to(result[2][0] || 0, result[2][1] || 0, result[2][0] || 0, (result[2][1] || 0) + chomped.length), chomped) end - # @sg-ignore @override is adding, not overriding def on_embdoc_beg *args result = super create_snippet(result) result end - # @sg-ignore @override is adding, not overriding def on_embdoc *args result = super create_snippet(result) result end - # @sg-ignore @override is adding, not overriding def on_embdoc_end *args result = super create_snippet(result) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index ba1063c9e..ffe046156 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -15,10 +15,8 @@ def initialize(locals, enclosing_breakable_pin = nil) # # @return [void] def process_and(and_node, true_ranges = []) - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] lhs = and_node.children[0] - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] rhs = and_node.children[1] @@ -46,10 +44,8 @@ def process_if(if_node) # s(:send, nil, :bar)) # [4] pry(main)> conditional_node = if_node.children[0] - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] then_clause = if_node.children[1] - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] else_clause = if_node.children[2] diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index 4b5a86291..2daf22fc7 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -18,7 +18,6 @@ def parse_with_comments code, filename = nil # @param code [String] # @param filename [String, nil] # @param line [Integer] - # @sg-ignore need to understand that raise does not return # @return [Parser::AST::Node] def parse code, filename = nil, line = 0 buffer = ::Parser::Source::Buffer.new(filename, line) diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 5b3ebdca5..1ee2bf754 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -119,7 +119,6 @@ def convert_hash node result end - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 NIL_NODE = ::Parser::AST::Node.new(:nil) # @param node [Parser::AST::Node] diff --git a/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb b/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb index 59ef28255..021ae0ab1 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb @@ -19,10 +19,6 @@ def process ) if region.visibility == :module_function here = get_node_start_position(node) - # @sg-ignore Declared type Solargraph::Pin::Method does - # not match inferred type Solargraph::Pin::Closure, nil - # for variable named_path - # @type [Pin::Closure, nil] named_path = named_path_pin(here) if named_path.is_a?(Pin::Method) pins.push Solargraph::Pin::InstanceVariable.new( diff --git a/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb b/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb index 54a4a9899..dbef1e2d7 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb @@ -22,13 +22,10 @@ def process # s(:int, 2), # s(:int, 3))) masgn = node - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] mlhs = masgn.children.fetch(0) - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Array] lhs_arr = mlhs.children - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] mass_rhs = node.children.fetch(1) diff --git a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb index da868ea80..1b573ed93 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb @@ -5,9 +5,7 @@ module Parser module ParserGem module NodeProcessors class SclassNode < Parser::NodeProcessor::Base - # @sg-ignore @override is adding, not overriding def process - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 sclass = node.children[0] # @todo Changing Parser::AST::Node to AST::Node below will # cause type errors at strong level because the combined diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index f9b0be49e..3b7ec74b1 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -7,7 +7,6 @@ module NodeProcessors class SendNode < Parser::NodeProcessor::Base include ParserGem::NodeMethods - # @sg-ignore @override is adding, not overriding def process # @sg-ignore Variable type could not be inferred for method_name # @type [Symbol] diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index c02386211..f58e5118e 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -69,19 +69,16 @@ def assert_location_provided Solargraph.assert_or_log(:best_location, "Neither location nor type_location provided - #{path} #{source} #{self.class}") end - # @sg-ignore Won't be nil based on testing with assert above - # @return [Pin::Closure] + # @return [Pin::Closure, nil] def closure Solargraph.assert_or_log(:closure, "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") unless @closure - # @sg-ignore Won't be nil based on testing with assert above - # @type [Pin::Closure] + # @type [Pin::Closure, nil] @closure end # @param other [self] # @param attrs [Hash{::Symbol => Object}] # - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" # @return [self] def combine_with(other, attrs={}) raise "tried to combine #{other.class} with #{self.class}" unless other.class == self.class @@ -143,7 +140,6 @@ def choose_longer(other, attr) end # @param other [self] - # # @return [::Array, nil] def combine_directives(other) return self.directives if other.directives.empty? @@ -152,8 +148,6 @@ def combine_directives(other) end # @param other [self] - # @sg-ignore explicitly marked undefined return types should - # disable trying to infer return types # @return [String] def combine_name(other) if needs_consistent_name? || other.needs_consistent_name? @@ -213,7 +207,6 @@ def combine_return_type(other) end end - # @sg-ignore need boolish support for ? methods def dodgy_return_type_source? # uses a lot of 'Object' instead of 'self' location&.filename&.include?('core_ext/object/') @@ -224,8 +217,7 @@ def dodgy_return_type_source? # @param other [Pin::Base] # @param attr [::Symbol] # - # @sg-ignore - # @return [undefined, nil] + # @return [Object, nil] def choose(other, attr) results = [self, other].map(&attr).compact # true and false are different classes and can't be sorted @@ -262,7 +254,6 @@ def prefer_rbs_location(other, attr) end end - # @sg-ignore need boolish support for ? methods def rbs_location? type_location&.rbs? end @@ -488,14 +479,12 @@ def return_type @return_type ||= ComplexType::UNDEFINED end - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [YARD::Docstring] def docstring parse_comments unless @docstring @docstring ||= Solargraph::Source.parse_docstring('').to_docstring end - # @sg-ignore parse_comments will always set @directives # @return [::Array] def directives parse_comments unless @directives @@ -520,7 +509,6 @@ def maybe_directives? @maybe_directives ||= comments.include?('@!') end - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Boolean] def deprecated? @deprecated ||= docstring.has_tag?('deprecated') @@ -590,7 +578,6 @@ def proxy return_type result end - # @sg-ignore to understand @foo ||= 123 will never be nil # @deprecated # @return [String] def identity diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 6df623bb7..764c1fb39 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -39,7 +39,6 @@ def symbol_kind Solargraph::LanguageServer::SymbolKinds::VARIABLE end - # @sg-ignore Need to understand @foo ||= 123 will never be nil def return_type @return_type ||= generate_complex_type end diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index c104b5ff7..626b75457 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -78,7 +78,6 @@ def choose_parameters(other) end end - # @sg-ignore Need to figure if Array#[n..m] can return nil # @return [Array] def blockless_parameters if parameters.last&.block? diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index 926cfcb00..ac95be10b 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -48,11 +48,19 @@ def binder @binder || context end + # @return [::Array] + def gates + # @todo This check might not be necessary. There should always be a + # root pin + closure ? closure.gates : [''] + end + # @return [::Array] def generics @generics ||= docstring.tags(:generic).map(&:name) end + # @return [String] def to_rbs rbs_generics + return_type.to_rbs end diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 85d734fc9..062099ee4 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -63,7 +63,6 @@ def path # @return [ComplexType] def find_context - # @sg-ignore should not get type error here, as type changes later on here = closure until here.nil? if here.is_a?(Pin::Namespace) diff --git a/lib/solargraph/pin/constant.rb b/lib/solargraph/pin/constant.rb index 8277723ab..94a968e7e 100644 --- a/lib/solargraph/pin/constant.rb +++ b/lib/solargraph/pin/constant.rb @@ -12,7 +12,6 @@ def initialize visibility: :public, **splat @visibility = visibility end - # @sg-ignore Need to understand @foo ||= 123 will never be nil def return_type @return_type ||= generate_complex_type end diff --git a/lib/solargraph/pin/conversions.rb b/lib/solargraph/pin/conversions.rb index cb307b08e..e40cc8990 100644 --- a/lib/solargraph/pin/conversions.rb +++ b/lib/solargraph/pin/conversions.rb @@ -34,7 +34,6 @@ def proxied? raise NotImplementedError end - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Hash] def completion_item @completion_item ||= { @@ -50,7 +49,6 @@ def completion_item } end - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Hash] def resolve_completion_item @resolve_completion_item ||= begin @@ -82,7 +80,6 @@ def detail # Get a markdown-flavored link to a documentation page. # - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def link_documentation @link_documentation ||= generate_link diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index 3b1227bfc..9483fb058 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -13,7 +13,6 @@ class DelegatedMethod < Pin::Method # # @param method [Method, nil] an already resolved method pin. # @param receiver [Source::Chain, nil] the source code used to resolve the receiver for this delegated method. - # @sg-ignore flow sensitive typing needs to handle || on nil types # @param name [String] # @param receiver_method_name [String] the method name that will be called on the receiver (defaults to :name). def initialize(method: nil, receiver: nil, name: method&.name, receiver_method_name: name, **splat) diff --git a/lib/solargraph/pin/documenting.rb b/lib/solargraph/pin/documenting.rb index 02026c08d..bd8b1fe9a 100644 --- a/lib/solargraph/pin/documenting.rb +++ b/lib/solargraph/pin/documenting.rb @@ -67,7 +67,6 @@ def to_code # @return [String] def to_markdown - # @sg-ignore Too many arguments to BasicObject.new ReverseMarkdown.convert Kramdown::Document.new(@plaintext, input: 'GFM').to_html end end diff --git a/lib/solargraph/pin/keyword.rb b/lib/solargraph/pin/keyword.rb index 2f76bb44c..089d0a417 100644 --- a/lib/solargraph/pin/keyword.rb +++ b/lib/solargraph/pin/keyword.rb @@ -8,7 +8,6 @@ def initialize(name, **kwargs) super(name: name, **kwargs) end - # @sg-ignore Need to understand @foo ||= 123 will never be nil def closure @closure ||= Pin::ROOT_PIN end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index e635a4dbb..47027864c 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -76,7 +76,6 @@ def combine_signatures(other) end end - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" def combine_with(other, attrs = {}) priority_choice = choose_priority(other) return priority_choice unless priority_choice.nil? @@ -156,8 +155,6 @@ def block? !block.nil? end - # @sg-ignore flow-sensitive typing needs to remove literal with - # this unless block # @return [Pin::Signature, nil] def block return @block unless @block == :undefined @@ -215,7 +212,6 @@ def generate_signature(parameters, return_type) signature end - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [::Array] def signatures @signatures ||= begin @@ -572,7 +568,6 @@ def resolve_reference ref, api_map nil end - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Parser::AST::Node, nil] def method_body_node return nil if node.nil? diff --git a/lib/solargraph/pin/namespace.rb b/lib/solargraph/pin/namespace.rb index c5e7b9117..95bd1089a 100644 --- a/lib/solargraph/pin/namespace.rb +++ b/lib/solargraph/pin/namespace.rb @@ -26,7 +26,6 @@ def initialize type: :class, visibility: :public, gates: [''], name: '', **splat @type = type @visibility = visibility if name.start_with?('::') - # @sg-ignore flow sensitive typing needs to handle || on nil types # @type [String] name = name[2..-1] || '' @closure = Solargraph::Pin::ROOT_PIN diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index fc8c1e016..1cfa073f0 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -140,7 +140,6 @@ def full end end - # @sg-ignore super always sets @return_type to something # @return [ComplexType] def return_type if @return_type.nil? @@ -157,13 +156,12 @@ def return_type end end end - super # always sets @return_type + super @return_type end # The parameter's zero-based location in the block's signature. # - # @sg-ignore this won't be nil if our code is correct # @return [Integer] def index # @type [Method, Block] @@ -192,7 +190,6 @@ def compatible_arg?(atype, api_map) ptype.generic? end - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" def documentation tag = param_tag return '' if tag.nil? || tag.text.nil? @@ -213,7 +210,6 @@ def param_tag # @param api_map [ApiMap] # @return [ComplexType] def typify_block_param api_map - # @sg-ignore type here should not be affected by later downcasting block_pin = closure if block_pin.is_a?(Pin::Block) && block_pin.receiver return block_pin.typify_parameters(api_map)[index] @@ -245,8 +241,6 @@ def typify_method_param api_map # @param heredoc [YARD::Docstring] # @param api_map [ApiMap] # @param skip [::Array] - # - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [::Array] def see_reference heredoc, api_map, skip = [] heredoc.ref_tags.each do |ref| diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 1a36ef660..4c25e028b 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -9,29 +9,24 @@ def initialize **splat super(**splat) end - # @sg-ignore Need to understand @foo ||= 123 will never be nil def generics @generics ||= [].freeze end - # @sg-ignore Need to understand @foo ||= 123 will never be nil def identity @identity ||= "signature#{object_id}" end attr_writer :closure - # @sg-ignore need boolish support for ? methods def dodgy_return_type_source? super || closure&.dodgy_return_type_source? end - # @sg-ignore need boolish support for ? methods def type_location super || closure&.type_location end - # @sg-ignore need boolish support for ? methods def location super || closure&.location end diff --git a/lib/solargraph/pin/symbol.rb b/lib/solargraph/pin/symbol.rb index c97aeb262..294363f5f 100644 --- a/lib/solargraph/pin/symbol.rb +++ b/lib/solargraph/pin/symbol.rb @@ -20,7 +20,6 @@ def path '' end - # @sg-ignore Need to understand @foo ||= 123 will never be nil def closure @closure ||= Pin::ROOT_PIN end diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 8080eedb3..9c2ac63bf 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -427,7 +427,6 @@ def all_combined_pins_in_memory # The base directory where cached YARD documentation and serialized pins are serialized # - # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [String] def base_dir # The directory is not stored in a variable so it can be overridden @@ -606,7 +605,6 @@ def clear end # @param file [String] - # @sg-ignore Marshal.load evaluates to boolean here which is wrong # @return [Array, nil] def load file return nil unless File.file?(file) diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index ab90d067f..f6309bb55 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -138,9 +138,6 @@ def pins out: $stderr # @generic T # @param path [String] # @param klass [Class>] - # - # @sg-ignore Need to be able to resolve generics based on a - # Class> param # @return [generic, nil] def path_pin path, klass = Pin::Base pin = pins.find { |p| p.path == path } diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 21282bf2a..0af8b0cdf 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -60,8 +60,6 @@ def at range # @param c1 [Integer] # @param l2 [Integer] # @param c2 [Integer] - # - # @sg-ignore Need to figure if String#[n..m] can return nil # @return [String] def from_to l1, c1, l2, c2 b = Solargraph::Position.line_char_to_offset(code, l1, c1) @@ -190,8 +188,6 @@ def code_for(node) end # @param node [AST::Node] - # - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String, nil] def comments_for node rng = Range.from_node(node) @@ -463,7 +459,6 @@ def repaired private - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Array] def code_lines @code_lines ||= code.lines diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 8d0231ea4..a8226eb07 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -35,7 +35,6 @@ def word # The part of the word before the current position. Given the text # `foo.bar`, the start_of_word at position(0, 6) is `ba`. # - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def start_of_word @start_of_word ||= begin @@ -50,7 +49,6 @@ def start_of_word # The part of the word after the current position. Given the text # `foo.bar`, the end_of_word at position (0,6) is `r`. # - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def end_of_word @end_of_word ||= begin diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index b5bed1678..5758a9d35 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -79,13 +79,11 @@ def chain # @return [Solargraph::Source] attr_reader :source - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def phrase @phrase ||= source.code[signature_data..offset-1] end - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def fixed_phrase @fixed_phrase ||= phrase[0..-(end_of_phrase.length+1)] @@ -96,7 +94,6 @@ def fixed_position @fixed_position ||= Position.from_offset(source.code, offset - end_of_phrase.length) end - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def end_of_phrase @end_of_phrase ||= begin diff --git a/lib/solargraph/source/updater.rb b/lib/solargraph/source/updater.rb index 72519e785..496d534ab 100644 --- a/lib/solargraph/source/updater.rb +++ b/lib/solargraph/source/updater.rb @@ -29,7 +29,6 @@ def initialize filename, version, changes # @param text [String] # @param nullable [Boolean] - # @sg-ignore changes doesn't mutate @output, so this can never be nil # @return [String] def write text, nullable = false can_nullify = (nullable and changes.length == 1) diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 986a2f082..5173be180 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -83,8 +83,6 @@ def conventions_environ end # all pins except Solargraph::Pin::Reference::Reference - # - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Array] def document_symbols @document_symbols ||= (pins + convention_pins).select do |pin| @@ -119,7 +117,6 @@ def locate_pins location # @param line [Integer] # @param character [Integer] - # @sg-ignore Need better generic inference here # @return [Pin::Method,Pin::Namespace] def locate_named_path_pin line, character _locate_pin line, character, Pin::Namespace, Pin::Method @@ -127,7 +124,6 @@ def locate_named_path_pin line, character # @param line [Integer] # @param character [Integer] - # @sg-ignore Need better generic inference here # @return [Pin::Namespace,Pin::Method,Pin::Block] def locate_block_pin line, character _locate_pin line, character, Pin::Namespace, Pin::Method, Pin::Block @@ -192,12 +188,10 @@ def convention_pins @convention_pins || [] end - # @generic T # @param line [Integer] # @param character [Integer] - # @param klasses [Array>>] - # @return [generic, nil] - # @sg-ignore Need better generic inference here + # @param klasses [Array] + # @return [Pin::Base, nil] def _locate_pin line, character, *klasses position = Position.new(line, character) found = nil diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index 016d4b51f..16a4ec845 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -75,7 +75,6 @@ def gates block.gates end - # @sg-ignore need boolish support for ? methods def in_block? return @in_block unless @in_block.nil? @in_block = begin diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index bdb20fd66..5290c8c12 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,20 +58,8 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo need boolish support for ? methods - # @todo need to be able to disambiguate Array signatures - # @todo https://github.com/castwide/solargraph/pull/1005 - # @todo To make JSON strongly typed we'll need a record syntax - # @todo flow sensitive typing needs to handle "unless foo.nil?" - # @todo flow sensitive typing needs to handle || on nil types - # @todo Need to understand @foo ||= 123 will never be nil - # @todo add metatype - e.g., $stdout is both an IO as well as - # a StringIO. Marking it as [IO, StringIO] implies it is - # /either/ one, not both, which means you can't hand it to - # something that demands a regular IO and doesn't also claim - # to accept a StringIO. def require_all_unique_types_match_declared? - rank >= LEVELS[:typed] + rank >= LEVELS[:alpha] end def require_no_undefined_args? diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 1fa8ab772..7f38ab95b 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -44,7 +44,6 @@ def require_paths @require_paths ||= RequirePaths.new(directory_or_nil, config).generate end - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [Solargraph::Workspace::Config] def config @config ||= Solargraph::Workspace::Config.new(directory) diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 2b9cd1d88..513d9179d 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -111,7 +111,6 @@ def max_files private - # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [String] def global_config_path ENV['SOLARGRAPH_GLOBAL_CONFIG'] || From 915c1dc587430d9d34b4f968b3554efe139da37a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 17:57:09 -0400 Subject: [PATCH 372/460] Drop dead methods and fix merge --- lib/solargraph/doc_map.rb | 221 -------------------------------------- 1 file changed, 221 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 19eb0419d..0aa60c0cd 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -169,229 +169,8 @@ def required_gems_map @required_gems_map ||= requires.to_h { |path| [path, workspace.resolve_require(path)] } end - # @return [Hash{String => Gem::Specification}] - def preference_map - @preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] } - end - - # @param gemspec [Gem::Specification] - # @return [Array, nil] - def deserialize_yard_pin_cache gemspec - if yard_pins_in_memory.key?([gemspec.name, gemspec.version]) - return yard_pins_in_memory[[gemspec.name, gemspec.version]] - end - - cached = PinCache.deserialize_yard_gem(gemspec) - if cached - logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" } - yard_pins_in_memory[[gemspec.name, gemspec.version]] = cached - cached - else - logger.debug "No YARD pin cache for #{gemspec.name}:#{gemspec.version}" - @uncached_yard_gemspecs.push gemspec - nil - end - end - - # @param gemspec [Gem::Specification] - # @return [void] - def deserialize_combined_pin_cache(gemspec) - unless combined_pins_in_memory[[gemspec.name, gemspec.version]].nil? - return combined_pins_in_memory[[gemspec.name, gemspec.version]] - end - - rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path) - rbs_version_cache_key = rbs_map.cache_key - - cached = PinCache.deserialize_combined_gem(gemspec, rbs_version_cache_key) - if cached - logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" } - combined_pins_in_memory[[gemspec.name, gemspec.version]] = cached - return combined_pins_in_memory[[gemspec.name, gemspec.version]] - end - - rbs_collection_pins = deserialize_rbs_collection_cache gemspec, rbs_version_cache_key - - yard_pins = deserialize_yard_pin_cache gemspec - - if !rbs_collection_pins.nil? && !yard_pins.nil? - logger.debug { "Combining pins for #{gemspec.name}:#{gemspec.version}" } - combined_pins = GemPins.combine(yard_pins, rbs_collection_pins) - PinCache.serialize_combined_gem(gemspec, rbs_version_cache_key, combined_pins) - combined_pins_in_memory[[gemspec.name, gemspec.version]] = combined_pins - logger.info { "Generated #{combined_pins_in_memory[[gemspec.name, gemspec.version]].length} combined pins for #{gemspec.name} #{gemspec.version}" } - return combined_pins - end - - if !yard_pins.nil? - logger.debug { "Using only YARD pins for #{gemspec.name}:#{gemspec.version}" } - combined_pins_in_memory[[gemspec.name, gemspec.version]] = yard_pins - return combined_pins_in_memory[[gemspec.name, gemspec.version]] - elsif !rbs_collection_pins.nil? - logger.debug { "Using only RBS collection pins for #{gemspec.name}:#{gemspec.version}" } - combined_pins_in_memory[[gemspec.name, gemspec.version]] = rbs_collection_pins - return combined_pins_in_memory[[gemspec.name, gemspec.version]] - else - logger.debug { "Pins not yet cached for #{gemspec.name}:#{gemspec.version}" } - return nil - end - end - - # @param path [String] require path that might be in the RBS stdlib collection - # @return [void] - def deserialize_stdlib_rbs_map path - map = RbsMap::StdlibMap.load(path) - if map.resolved? - logger.debug { "Loading stdlib pins for #{path}" } - @pins.concat map.pins - logger.debug { "Loaded #{map.pins.length} stdlib pins for #{path}" } - map.pins - else - # @todo Temporarily ignoring unresolved `require 'set'` - logger.debug { "Require path #{path} could not be resolved in RBS" } unless path == 'set' - nil - end - end - - # @param gemspec [Gem::Specification] - # @param rbs_version_cache_key [String] - # @return [Array, nil] - def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key - return if rbs_collection_pins_in_memory.key?([gemspec, rbs_version_cache_key]) - cached = PinCache.deserialize_rbs_collection_gem(gemspec, rbs_version_cache_key) - if cached - logger.info { "Loaded #{cached.length} pins from RBS collection cache for #{gemspec.name}:#{gemspec.version}" } unless cached.empty? - rbs_collection_pins_in_memory[[gemspec, rbs_version_cache_key]] = cached - cached - else - logger.debug "No RBS collection pin cache for #{gemspec.name} #{gemspec.version}" - @uncached_rbs_collection_gemspecs.push gemspec - nil - end - end - - # @param path [String] - # @return [::Array, nil] - def resolve_path_to_gemspecs path - return nil if path.empty? - return gemspecs_required_from_bundler if path == 'bundler/require' - - # @type [Gem::Specification, nil] - gemspec = Gem::Specification.find_by_path(path) - if gemspec.nil? - gem_name_guess = path.split('/').first - begin - # this can happen when the gem is included via a local path in - # a Gemfile; Gem doesn't try to index the paths in that case. - # - # See if we can make a good guess: - potential_gemspec = Gem::Specification.find_by_name(gem_name_guess) - file = "lib/#{path}.rb" - gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file } - rescue Gem::MissingSpecError - logger.debug { "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}" } - [] - end - end - return nil if gemspec.nil? - [gemspec_or_preference(gemspec)] - end - - # @param gemspec [Gem::Specification] - # @return [Gem::Specification] - def gemspec_or_preference gemspec - return gemspec unless preference_map.key?(gemspec.name) - return gemspec if gemspec.version == preference_map[gemspec.name].version - - change_gemspec_version gemspec, preference_map[by_path.name].version - end - - # @param gemspec [Gem::Specification] - # @param version [Gem::Version] - # @return [Gem::Specification] - def change_gemspec_version gemspec, version - Gem::Specification.find_by_name(gemspec.name, "= #{version}") - rescue Gem::MissingSpecError - Solargraph.logger.info "Gem #{gemspec.name} version #{version} not found. Using #{gemspec.version} instead" - gemspec - end - - # @param gemspec [Gem::Specification] - # @return [Array] - def fetch_dependencies gemspec - # @param spec [Gem::Dependency] - only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps| - Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}" - dep = Gem.loaded_specs[spec.name] - # @todo is next line necessary? - dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement) - deps.merge fetch_dependencies(dep) if deps.add?(dep) - rescue Gem::MissingSpecError - Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found in RubyGems." - end.to_a - end - - # @param gemspec [Gem::Specification] - # @return [Array] - def only_runtime_dependencies gemspec - gemspec.dependencies - gemspec.development_dependencies - end - def inspect self.class.inspect end - - # @return [Array, nil] - def gemspecs_required_from_bundler - # @todo Handle projects with custom Bundler/Gemfile setups - return unless workspace.gemfile? - - if workspace.gemfile? && Bundler.definition&.lockfile&.to_s&.start_with?(workspace.directory) - # Find only the gems bundler is now using - Bundler.definition.locked_gems.specs.flat_map do |lazy_spec| - logger.info "Handling #{lazy_spec.name}:#{lazy_spec.version}" - [Gem::Specification.find_by_name(lazy_spec.name, lazy_spec.version)] - rescue Gem::MissingSpecError => e - logger.info("Could not find #{lazy_spec.name}:#{lazy_spec.version} with find_by_name, falling back to guess") - # can happen in local filesystem references - specs = resolve_path_to_gemspecs lazy_spec.name - logger.warn "Gem #{lazy_spec.name} #{lazy_spec.version} from bundle not found: #{e}" if specs.nil? - next specs - end.compact - else - logger.info 'Fetching gemspecs required from Bundler (bundler/require)' - gemspecs_required_from_external_bundle - end - end - - # @return [Array, nil] - def gemspecs_required_from_external_bundle - logger.info 'Fetching gemspecs required from external bundle' - return [] unless workspace&.directory - - Solargraph.with_clean_env do - cmd = [ - 'ruby', '-e', - "require 'bundler'; require 'json'; Dir.chdir('#{workspace&.directory}') { puts Bundler.definition.locked_gems.specs.map { |spec| [spec.name, spec.version] }.to_h.to_json }" - ] - o, e, s = Open3.capture3(*cmd) - if s.success? - Solargraph.logger.debug "External bundle: #{o}" - hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {} - hash.flat_map do |name, version| - Gem::Specification.find_by_name(name, version) - rescue Gem::MissingSpecError => e - logger.info("Could not find #{name}:#{version} with find_by_name, falling back to guess") - # can happen in local filesystem references - specs = resolve_path_to_gemspecs name - logger.warn "Gem #{name} #{version} from bundle not found: #{e}" if specs.nil? - next specs - end.compact - else - Solargraph.logger.warn "Failed to load gems from bundle at #{workspace&.directory}: #{e}" - nil - end - end - end end end From fd743f14123db3f05d4ca264abdd22ec9bfe0ad2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 18:05:17 -0400 Subject: [PATCH 373/460] Fix specs --- spec/type_checker/levels/strict_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 0a8a3d90c..9fc423b7d 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -6,8 +6,6 @@ def type_checker(code) end it 'ignores nilable type issues' do - pending("moving nilable handling back to strong") - checker = type_checker(%( # @param a [String] # @return [void] @@ -557,6 +555,8 @@ def bar(baz:, bing:) end it 'requires strict return tags' do + pending 'nil? support in flow sensitive typing' + checker = type_checker(%( class Foo # The tag is [String] but the inference is [String, nil] @@ -572,6 +572,8 @@ def bar end it 'requires strict return tags' do + pending 'nil? support in flow sensitive typing' + checker = type_checker(%( class Foo # The tag is [String] but the inference is [String, nil] From 1842cb39d0ec0cac2a07102452a3dff025835ee0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 18:10:09 -0400 Subject: [PATCH 374/460] Add @sg-ignore --- lib/solargraph/type_checker.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 0ffcb3cca..e942beff0 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -487,6 +487,7 @@ def kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, ptype = data[:qualified] ptype = ptype.self_to_type(pin.context) unless ptype.undefined? + # @sg-ignore reassigned variable # @type [ComplexType] argtype = argchain.infer(api_map, block_pin, locals).self_to_type(block_pin.context) if argtype.defined? && ptype && !arg_conforms_to?(argtype, ptype) From ec8899be348968307ffa45ddfb7673cc8aaf2cd1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 18:32:15 -0400 Subject: [PATCH 375/460] Undo test changes --- spec/type_checker/levels/strong_spec.rb | 2 ++ spec/type_checker/levels/typed_spec.rb | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 57e43f26a..00754ad08 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -5,6 +5,8 @@ def type_checker(code) end it 'does gives correct complaint on array dereference with nilable type' do + pending('better nil enforcement') + checker = type_checker(%( # @param idx [Integer, nil] an index # @param arr [Array] an array of integers diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index 0a5c6e1ed..fbde9868d 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -253,8 +253,6 @@ def bar end it 'allows loose return tags' do - pending('temporary move is reversed') - checker = type_checker(%( class Foo # The tag is [String] but the inference is [String, nil] From a8602dc74ec51cccc9d511d79c340d3ab1594c22 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 25 Sep 2025 07:38:24 -0400 Subject: [PATCH 376/460] Annotate ApiMap::Store#get_ancestor_references --- lib/solargraph/api_map/store.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index fb1e32a74..c41e19c09 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -256,6 +256,9 @@ def get_ancestors(fqns) ancestors.compact.uniq end + # @param fqns [String] + # + # @return [Array] def get_ancestor_references(fqns) (get_prepends(fqns) + get_includes(fqns) + [get_superclass(fqns)]).compact end From 548396bff796ab485a6833f90a7e8c17188de244 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 25 Sep 2025 07:41:54 -0400 Subject: [PATCH 377/460] Fix merge issue --- lib/solargraph/api_map/index.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 5c1ee9dde..45063d994 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -115,7 +115,7 @@ def catalog new_pins end # @param klass [Class] - # @param hash [Hash{String => Array}] + # @param hash [Hash{String => Array}] # @return [void] def map_references klass, hash pins_by_class(klass).each do |pin| From f4399eba824fdd5aa8d605b2143ddef20c5617d8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 25 Sep 2025 11:15:45 -0400 Subject: [PATCH 378/460] [regression] Fix issue resolving mixins under same namespace --- lib/solargraph/api_map/constants.rb | 7 ++++- spec/api_map/constants_spec.rb | 36 +++++++++++++++++++++++++ spec/type_checker/levels/strong_spec.rb | 20 ++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 430303ae1..51d0a207c 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -120,8 +120,13 @@ def complex_resolve name, gates, internal return [resolved, gates[(idx + 1)..]] if resolved store.get_ancestor_references(gate).each do |ref| return ref.name.sub(/^::/, '') if ref.name.end_with?("::#{name}") - mixin = resolve(ref.name, ref.reference_gates - gates) + + # avoid infinite loops resolving mixin pin + next if ref.name == name && gates.to_set == ref.reference_gates.to_set + + mixin = resolve(ref.name, ref.reference_gates) next unless mixin + resolved = simple_resolve(name, mixin, internal) return [resolved, gates[(idx + 1)..]] if resolved end diff --git a/spec/api_map/constants_spec.rb b/spec/api_map/constants_spec.rb index 26eaf6b25..c0460e79a 100644 --- a/spec/api_map/constants_spec.rb +++ b/spec/api_map/constants_spec.rb @@ -20,6 +20,42 @@ module Quuz expect(resolved).to eq('Foo::Bar') end + it 'resolves straightforward mixins' do + source_map = Solargraph::SourceMap.load_string(%( + module Bar + Baz = 'baz' + end + + class Foo + include Bar + end + ), 'test.rb') + + store = Solargraph::ApiMap::Store.new(source_map.pins) + constants = described_class.new(store) + pin = source_map.first_pin('Foo') + resolved = constants.resolve('Baz', pin.gates) + expect(resolved).to eq('Bar::Baz') + end + + it 'resolves mixin living under same namespace' do + source_map = Solargraph::SourceMap.load_string(%( + class Foo + module Bar + Baz = 'baz' + end + + include Bar + end + ), 'test.rb') + + store = Solargraph::ApiMap::Store.new(source_map.pins) + constants = described_class.new(store) + pin = source_map.first_pin('Foo') + resolved = constants.resolve('Baz', pin.gates) + expect(resolved).to eq('Foo::Bar::Baz') + end + it 'returns namespaces for nested namespaces' do source_map = Solargraph::SourceMap.load_string(%( module Foo diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index a03e6eb5d..4bf3b7163 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -272,5 +272,25 @@ def meth arg )) expect(checker.problems).to be_empty end + + it 'resolves constants inside modules inside classes' do + checker = type_checker(%( + class Bar + module Foo + CONSTANT = 'hi' + end + end + + class Bar + include Foo + + # @return [String] + def baz + CONSTANT + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end end end From 3324b4c7fb392405c39252701a9494d70d63dcf0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 25 Sep 2025 12:11:22 -0400 Subject: [PATCH 379/460] Prevent recursion via caching mechanism --- lib/solargraph/api_map/constants.rb | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 51d0a207c..bc508b330 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -24,8 +24,16 @@ def resolve(name, *gates) return store.get_path_pins(name[2..]).first&.path if name.start_with?('::') flat = gates.flatten - flat.push '' if flat.empty? - cached_resolve[[name, flat]] || resolve_and_cache(name, flat) + if flat.empty? + flat.push '' + end + if cached_resolve.include? [name, flat] + cached_result = cached_resolve[[name, flat]] + # don't recurse + return nil if cached_result == :in_process + return cached_result + end + resolve_and_cache(name, flat) end # Get a fully qualified namespace from a reference pin. @@ -82,6 +90,7 @@ def clear # @param gates [Array] # @return [String, nil] def resolve_and_cache name, gates + cached_resolve[[name, gates]] = :in_process cached_resolve[[name, gates]] = resolve_uncached(name, gates) end @@ -121,9 +130,6 @@ def complex_resolve name, gates, internal store.get_ancestor_references(gate).each do |ref| return ref.name.sub(/^::/, '') if ref.name.end_with?("::#{name}") - # avoid infinite loops resolving mixin pin - next if ref.name == name && gates.to_set == ref.reference_gates.to_set - mixin = resolve(ref.name, ref.reference_gates) next unless mixin @@ -159,7 +165,7 @@ def collect_and_cache gates end end - # @return [Hash{Array(Name, Array) => String, nil}] + # @return [Hash{Array(String, Array) => String, :in_process, nil}] def cached_resolve @cached_resolve ||= {} end From 174bf4e430edf7198c9ae3d23d701e359a74146c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 25 Sep 2025 12:15:46 -0400 Subject: [PATCH 380/460] Linting fix, ignore rubocop-yard issue pending yard PR merge --- .rubocop_todo.yml | 33 ++--------------------------- lib/solargraph/api_map/constants.rb | 4 +--- 2 files changed, 3 insertions(+), 34 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 89fd47c5d..f17d08a94 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.80.2. +# using RuboCop version 1.80.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -280,7 +280,6 @@ Layout/MultilineMethodCallBraceLayout: # SupportedStyles: aligned, indented, indented_relative_to_receiver Layout/MultilineMethodCallIndentation: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/diagnostics/type_check.rb' - 'lib/solargraph/language_server/message/completion_item/resolve.rb' - 'lib/solargraph/language_server/message/text_document/hover.rb' @@ -356,7 +355,6 @@ Layout/SpaceBeforeBlockBraces: - 'lib/solargraph/source.rb' - 'lib/solargraph/source/chain/call.rb' - 'lib/solargraph/source/chain/class_variable.rb' - - 'lib/solargraph/source/chain/constant.rb' - 'lib/solargraph/source/chain/global_variable.rb' - 'lib/solargraph/source/chain/instance_variable.rb' - 'lib/solargraph/source/chain/variable.rb' @@ -433,13 +431,11 @@ Layout/TrailingWhitespace: Exclude: - 'lib/solargraph/language_server/message/client/register_capability.rb' - 'spec/api_map/config_spec.rb' - - 'spec/convention_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedMethods, AllowedPatterns. Lint/AmbiguousBlockAssociation: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/language_server/host.rb' # This cop supports safe autocorrection (--autocorrect). @@ -512,11 +508,6 @@ Lint/DuplicateMethods: - 'lib/solargraph/rbs_map/core_map.rb' - 'lib/solargraph/source/chain/link.rb' -# Configuration parameters: AllowComments, AllowEmptyLambdas. -Lint/EmptyBlock: - Exclude: - - 'spec/convention_spec.rb' - # Configuration parameters: AllowComments. Lint/EmptyClass: Exclude: @@ -998,11 +989,6 @@ RSpec/DescribedClass: - 'spec/yard_map/mapper/to_method_spec.rb' - 'spec/yard_map/mapper_spec.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -RSpec/EmptyExampleGroup: - Exclude: - - 'spec/convention_spec.rb' - # This cop supports safe autocorrection (--autocorrect). RSpec/EmptyLineAfterFinalLet: Exclude: @@ -1321,7 +1307,6 @@ Style/AccessorGrouping: # SupportedStyles: always, conditionals Style/AndOr: Exclude: - - 'lib/solargraph/api_map/source_to_yard.rb' - 'lib/solargraph/complex_type/unique_type.rb' - 'lib/solargraph/language_server/message/base.rb' - 'lib/solargraph/page.rb' @@ -1782,7 +1767,6 @@ Style/GlobalStdStream: # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/library.rb' - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - 'lib/solargraph/pin_cache.rb' @@ -1835,7 +1819,6 @@ Style/IfInsideElse: # This cop supports safe autocorrection (--autocorrect). Style/IfUnlessModifier: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/unique_type.rb' @@ -1903,7 +1886,6 @@ Style/MethodDefParentheses: Exclude: - 'lib/solargraph.rb' - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/type_methods.rb' @@ -1947,7 +1929,6 @@ Style/MethodDefParentheses: - 'lib/solargraph/shell.rb' - 'lib/solargraph/source.rb' - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/chain/constant.rb' - 'lib/solargraph/source_map.rb' - 'lib/solargraph/source_map/mapper.rb' - 'lib/solargraph/type_checker.rb' @@ -2049,8 +2030,6 @@ Style/NumericLiterals: Style/NumericPredicate: Exclude: - 'spec/**/*' - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/type_methods.rb' @@ -2131,7 +2110,6 @@ Style/RedundantFreeze: # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: - - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/source_map/mapper.rb' @@ -2143,7 +2121,6 @@ Style/RedundantParentheses: - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/pin/search.rb' - 'lib/solargraph/source.rb' - 'lib/solargraph/type_checker.rb' @@ -2170,7 +2147,6 @@ Style/RedundantRegexpEscape: # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/complex_type/type_methods.rb' - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/parser/parser_gem/node_methods.rb' @@ -2269,8 +2245,6 @@ Style/StderrPuts: # Configuration parameters: Mode. Style/StringConcatenation: Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/pin/base.rb' - 'lib/solargraph/pin/callable.rb' - 'lib/solargraph/pin/closure.rb' @@ -2286,7 +2260,6 @@ Style/StringLiterals: Exclude: - 'Gemfile' - 'Rakefile' - - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/unique_type.rb' - 'lib/solargraph/convention/struct_definition.rb' @@ -2504,8 +2477,6 @@ Style/YAMLFileRead: # This cop supports unsafe autocorrection (--autocorrect-all). Style/ZeroLengthPredicate: Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/source/chain/array.rb' @@ -2551,6 +2522,7 @@ YARD/MismatchName: YARD/TagTypeSyntax: Exclude: + - 'lib/solargraph/api_map/constants.rb' - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/parser/comment_ripper.rb' - 'lib/solargraph/type_checker.rb' @@ -2614,7 +2586,6 @@ Layout/LineLength: - 'lib/solargraph/workspace.rb' - 'lib/solargraph/workspace/config.rb' - 'lib/solargraph/yard_map/mapper/to_method.rb' - - 'spec/api_map_spec.rb' - 'spec/complex_type_spec.rb' - 'spec/language_server/message/completion_item/resolve_spec.rb' - 'spec/language_server/message/extended/check_gem_version_spec.rb' diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index bc508b330..0df8d83ce 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -24,9 +24,7 @@ def resolve(name, *gates) return store.get_path_pins(name[2..]).first&.path if name.start_with?('::') flat = gates.flatten - if flat.empty? - flat.push '' - end + flat.push '' if flat.empty? if cached_resolve.include? [name, flat] cached_result = cached_resolve[[name, flat]] # don't recurse From 4898313a80331277b733ce827962ff5eaadf5a16 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 25 Sep 2025 16:03:39 -0400 Subject: [PATCH 381/460] [regression] Fix resolution in deep YARD namespace hierarchies YARD-parsed namespaces weren't correctly setting their gates, leading to unresolved types from methods. --- lib/solargraph/yard_map/mapper/to_namespace.rb | 1 + spec/yard_map/mapper_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/lib/solargraph/yard_map/mapper/to_namespace.rb b/lib/solargraph/yard_map/mapper/to_namespace.rb index 054ba3306..f7063e3d6 100644 --- a/lib/solargraph/yard_map/mapper/to_namespace.rb +++ b/lib/solargraph/yard_map/mapper/to_namespace.rb @@ -21,6 +21,7 @@ def self.make code_object, spec, closure = nil type: code_object.is_a?(YARD::CodeObjects::ClassObject) ? :class : :module, visibility: code_object.visibility, closure: closure, + gates: closure.gates, source: :yardoc, ) end diff --git a/spec/yard_map/mapper_spec.rb b/spec/yard_map/mapper_spec.rb index 6b00e5c33..63efc3835 100644 --- a/spec/yard_map/mapper_spec.rb +++ b/spec/yard_map/mapper_spec.rb @@ -74,6 +74,14 @@ expect(inc).to be_a(Solargraph::Pin::Reference::Include) end + it 'adds corect gates' do + # Asssuming the ast gem exists because it's a known dependency + pin = pins_with('ast').find do |pin| + pin.is_a?(Solargraph::Pin::Namespace) && pin.name == 'Mixin' && pin.closure.path == 'AST::Processor' + end + expect(pin.gates).to eq(["AST::Processor::Mixin", "AST::Processor", "AST", ""]) + end + it 'adds extend references' do # Asssuming the yard gem exists because it's a known dependency gemspec = Gem::Specification.find_by_name('yard') From cb47db054f16e625bba1331738376068e9899dbb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 25 Sep 2025 17:31:01 -0400 Subject: [PATCH 382/460] [regression] Fix resolution of ambiguous argument types This is a case where incorrect gates result in the wrong type being used - found by strong typechecking on a branch ::Solargraph::Pin::Symbol was resolved as ::Symbol in a generics scenario. --- lib/solargraph/pin/proxy_type.rb | 3 +- lib/solargraph/source/chain/call.rb | 5 +++- spec/source/chain/call_spec.rb | 37 +++++++++++++++++++++++++ spec/type_checker/levels/strong_spec.rb | 32 +++++++++++++++++++++ 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/pin/proxy_type.rb b/lib/solargraph/pin/proxy_type.rb index 2323489a7..6babeb353 100644 --- a/lib/solargraph/pin/proxy_type.rb +++ b/lib/solargraph/pin/proxy_type.rb @@ -5,8 +5,9 @@ module Pin class ProxyType < Base # @param return_type [ComplexType] # @param binder [ComplexType, ComplexType::UniqueType, nil] - def initialize return_type: ComplexType::UNDEFINED, binder: nil, **splat + def initialize return_type: ComplexType::UNDEFINED, binder: nil, gates: [''], **splat super(**splat) + @gates = gates @return_type = return_type @binder = binder if binder end diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 24d10656d..5dca071de 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -98,7 +98,10 @@ def inferred_pins pins, api_map, name_pin, locals match = ol.parameters.any?(&:restarg?) break end - atype = atypes[idx] ||= arg.infer(api_map, Pin::ProxyType.anonymous(name_pin.context, source: :chain), locals) + name_pin = Pin::ProxyType.anonymous(name_pin.context, + gates: name_pin.gates, + source: :chain) + atype = atypes[idx] ||= arg.infer(api_map, name_pin, locals) unless param.compatible_arg?(atype, api_map) || param.restarg? match = false break diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index 8b67a3c66..3725686a7 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -627,4 +627,41 @@ def bl clip = api_map.clip_at('test.rb', [3, 8]) expect(clip.infer.rooted_tags).to eq('::String') end + + it 'sends proper gates in ProxyType' do + source = Solargraph::Source.load_string(%( + module Foo + module Bar + class Symbol + end + end + end + + module Foo + module Baz + class Quux + # @return [void] + def foo + s = objects_by_class(Bar::Symbol) + s + end + + # @generic T + # @param klass [Class>] + # @return [Set>] + def objects_by_class klass + # @type [Set>] + s = Set.new + s + end + end + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new + api_map.map source + + clip = api_map.clip_at('test.rb', [14, 14]) + expect(clip.infer.rooted_tags).to eq('::Set<::Foo::Bar::Symbol>') + end end diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index a03e6eb5d..f9ff1ebb5 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -272,5 +272,37 @@ def meth arg )) expect(checker.problems).to be_empty end + + it 'resolves class name correctly in generic resolution' do + checker = type_checker(%( + module Foo + module Bar + class Symbol + end + end + end + + module Foo + module Baz + class Quux + # @return [void] + def foo + objects_by_class(Bar::Symbol) + end + + # @generic T + # @param klass [Class>] + # @return [Set>] + def objects_by_class klass + # @type [Set>] + s = Set.new + s + end + end + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end end end From 97e5519600f9b1d4bdf39c400f20f2cd104bf079 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 25 Sep 2025 17:43:19 -0400 Subject: [PATCH 383/460] Fix gates default value to match existing behavior --- lib/solargraph/pin/proxy_type.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/pin/proxy_type.rb b/lib/solargraph/pin/proxy_type.rb index 6babeb353..ecaa94fdd 100644 --- a/lib/solargraph/pin/proxy_type.rb +++ b/lib/solargraph/pin/proxy_type.rb @@ -5,7 +5,7 @@ module Pin class ProxyType < Base # @param return_type [ComplexType] # @param binder [ComplexType, ComplexType::UniqueType, nil] - def initialize return_type: ComplexType::UNDEFINED, binder: nil, gates: [''], **splat + def initialize return_type: ComplexType::UNDEFINED, binder: nil, gates: nil, **splat super(**splat) @gates = gates @return_type = return_type From 7840f9e91cbf0bad3d849f9e3fc565b1af25101e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 25 Sep 2025 17:47:37 -0400 Subject: [PATCH 384/460] Linting --- spec/type_checker/levels/strong_spec.rb | 53 ++++++++++++++----------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index f9ff1ebb5..0991ad9ad 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -273,36 +273,41 @@ def meth arg expect(checker.problems).to be_empty end - it 'resolves class name correctly in generic resolution' do - checker = type_checker(%( - module Foo - module Bar - class Symbol + context 'with class name available in more than one gate' do + let(:checker) do + type_checker(%( + module Foo + module Bar + class Symbol + end end end - end - - module Foo - module Baz - class Quux - # @return [void] - def foo - objects_by_class(Bar::Symbol) - end - # @generic T - # @param klass [Class>] - # @return [Set>] - def objects_by_class klass - # @type [Set>] - s = Set.new - s + module Foo + module Baz + class Quux + # @return [void] + def foo + objects_by_class(Bar::Symbol) + end + + # @generic T + # @param klass [Class>] + # @return [Set>] + def objects_by_class klass + # @type [Set>] + s = Set.new + s + end end end end - end - )) - expect(checker.problems.map(&:message)).to be_empty + )) + end + + it 'resolves class name correctly in generic resolution' do + expect(checker.problems.map(&:message)).to be_empty + end end end end From f8f7ca27b1dc40864a4ac49e3c26caa1fcb33b84 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 09:02:13 -0400 Subject: [PATCH 385/460] Linting --- spec/yard_map/mapper_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/yard_map/mapper_spec.rb b/spec/yard_map/mapper_spec.rb index 63efc3835..6fa778f2c 100644 --- a/spec/yard_map/mapper_spec.rb +++ b/spec/yard_map/mapper_spec.rb @@ -79,7 +79,7 @@ pin = pins_with('ast').find do |pin| pin.is_a?(Solargraph::Pin::Namespace) && pin.name == 'Mixin' && pin.closure.path == 'AST::Processor' end - expect(pin.gates).to eq(["AST::Processor::Mixin", "AST::Processor", "AST", ""]) + expect(pin.gates).to eq(['AST::Processor::Mixin', 'AST::Processor', 'AST', '']) end it 'adds extend references' do From 9ad0cbd40ee384d0a5b9830ce552ebe3c2fae6cd Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 25 Sep 2025 18:18:47 -0400 Subject: [PATCH 386/460] Linting --- lib/solargraph/pin/proxy_type.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/pin/proxy_type.rb b/lib/solargraph/pin/proxy_type.rb index ecaa94fdd..1fed841a3 100644 --- a/lib/solargraph/pin/proxy_type.rb +++ b/lib/solargraph/pin/proxy_type.rb @@ -5,6 +5,7 @@ module Pin class ProxyType < Base # @param return_type [ComplexType] # @param binder [ComplexType, ComplexType::UniqueType, nil] + # @param gates [Array, nil] def initialize return_type: ComplexType::UNDEFINED, binder: nil, gates: nil, **splat super(**splat) @gates = gates From b49c33baef2cf471b56ab056efc0a7969ec5682e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 08:58:35 -0400 Subject: [PATCH 387/460] Fix re-used variable name issue --- lib/solargraph/pin/proxy_type.rb | 1 + lib/solargraph/source/chain/call.rb | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/pin/proxy_type.rb b/lib/solargraph/pin/proxy_type.rb index 1fed841a3..452536834 100644 --- a/lib/solargraph/pin/proxy_type.rb +++ b/lib/solargraph/pin/proxy_type.rb @@ -4,6 +4,7 @@ module Solargraph module Pin class ProxyType < Base # @param return_type [ComplexType] + # @param gates [Array, nil] Namespaces to try while resolving non-rooted types # @param binder [ComplexType, ComplexType::UniqueType, nil] # @param gates [Array, nil] def initialize return_type: ComplexType::UNDEFINED, binder: nil, gates: nil, **splat diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 5dca071de..74afff4e0 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -98,10 +98,10 @@ def inferred_pins pins, api_map, name_pin, locals match = ol.parameters.any?(&:restarg?) break end - name_pin = Pin::ProxyType.anonymous(name_pin.context, - gates: name_pin.gates, - source: :chain) - atype = atypes[idx] ||= arg.infer(api_map, name_pin, locals) + arg_name_pin = Pin::ProxyType.anonymous(name_pin.context, + gates: name_pin.gates, + source: :chain) + atype = atypes[idx] ||= arg.infer(api_map, arg_name_pin, locals) unless param.compatible_arg?(atype, api_map) || param.restarg? match = false break From 8ce4bb91e5f2f7a57c8125150427e67b8e465ee7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 09:26:26 -0400 Subject: [PATCH 388/460] Fix merge --- spec/yard_map/mapper_spec.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/yard_map/mapper_spec.rb b/spec/yard_map/mapper_spec.rb index 6fa778f2c..d45af985b 100644 --- a/spec/yard_map/mapper_spec.rb +++ b/spec/yard_map/mapper_spec.rb @@ -76,7 +76,10 @@ it 'adds corect gates' do # Asssuming the ast gem exists because it's a known dependency - pin = pins_with('ast').find do |pin| + gemspec = Gem::Specification.find_by_name('ast') + Solargraph::Yardoc.cache([], gemspec) + pins = Solargraph::YardMap::Mapper.new(Solargraph::Yardoc.load!(gemspec)).map + pin = pins.find do |pin| pin.is_a?(Solargraph::Pin::Namespace) && pin.name == 'Mixin' && pin.closure.path == 'AST::Processor' end expect(pin.gates).to eq(['AST::Processor::Mixin', 'AST::Processor', 'AST', '']) From a398b84bd2f09c4e9148aee79c8ee0a643c651a1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 12:45:46 -0400 Subject: [PATCH 389/460] Resolve constants in references Also replaces 'include' logic with call to ApiMap::Constants Fixes #1099 --- lib/solargraph/api_map.rb | 13 +--- lib/solargraph/api_map/constants.rb | 23 ++++--- lib/solargraph/parser/node_methods.rb | 97 --------------------------- spec/api_map/constants_spec.rb | 27 ++++++++ 4 files changed, 44 insertions(+), 116 deletions(-) delete mode 100644 lib/solargraph/parser/node_methods.rb diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 44ca19035..7f53e7596 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -772,17 +772,8 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if scope == :instance store.get_includes(fqns).reverse.each do |ref| - const = get_constants('', *ref.closure.gates).find { |pin| pin.path.end_with? ref.name } - if const.is_a?(Pin::Namespace) - result.concat inner_get_methods(const.path, scope, visibility, deep, skip, true) - elsif const.is_a?(Pin::Constant) - type = const.infer(self) - result.concat inner_get_methods(type.namespace, scope, visibility, deep, skip, true) if type.defined? - else - referenced_tag = ref.parametrized_tag - next unless referenced_tag.defined? - result.concat inner_get_methods_from_reference(referenced_tag.to_s, namespace_pin, rooted_type, scope, visibility, deep, skip, true) - end + fqin = dereference(ref) + result.concat inner_get_methods(fqin, scope, visibility, deep, skip, true) end rooted_sc_tag = qualify_superclass(rooted_tag) unless rooted_sc_tag.nil? diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 430303ae1..5dbcd4b67 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -12,14 +12,21 @@ def initialize store # Resolve a name to a fully qualified namespace or constant. # - # `Constants#resolve` is similar to `Constants#qualify`` in that its - # purpose is to find fully qualified (absolute) namespaces, except - # `#resolve`` is only concerned with real namespaces. It disregards - # parametrized types and special types like literals, self, and Boolean. + # `Constants#resolve` finds fully qualified (absolute) + # namespaces based on relative names and the open gates + # (namespaces) provided. Names must be runtime-visible (erased) + # non-literal types - e.g., TrueClass, NilClass, Integer and + # Hash instead of true, nil, 96, or Hash{String => Symbol} # - # @param name [String] - # @param gates [Array, String>] - # @return [String, nil] + # Note: You may want to be using #qualify. Notably, #resolve: + # - will not gracefully handle nil, self and Boolean + # - will return a constant name instead of following its assignment + # + # @param name [String] Namespace which may relative and not be rooted. + # @param gates [Array, String>] Namespaces to search while resolving the name + # + # @return [String, nil] fully qualified namespace (i.e., is + # absolute, but will not start with ::) def resolve(name, *gates) return store.get_path_pins(name[2..]).first&.path if name.start_with?('::') @@ -33,7 +40,7 @@ def resolve(name, *gates) # @param pin [Pin::Reference] # @return [String, nil] def dereference pin - resolve(pin.name, pin.reference_gates) + qualify(pin.name, pin.reference_gates) end # Collect a list of all constants defined in the specified gates. diff --git a/lib/solargraph/parser/node_methods.rb b/lib/solargraph/parser/node_methods.rb deleted file mode 100644 index f33a924c1..000000000 --- a/lib/solargraph/parser/node_methods.rb +++ /dev/null @@ -1,97 +0,0 @@ -module Solargraph - module Parser - module NodeMethods - module_function - - # @abstract - # @param node [Parser::AST::Node] - # @return [String] - def unpack_name node - raise NotImplementedError - end - - # @abstract - # @todo Temporarily here for testing. Move to Solargraph::Parser. - # @param node [Parser::AST::Node] - # @return [Array] - def call_nodes_from node - raise NotImplementedError - end - - # Find all the nodes within the provided node that potentially return a - # value. - # - # The node parameter typically represents a method's logic, e.g., the - # second child (after the :args node) of a :def node. A simple one-line - # method would typically return itself, while a node with conditions - # would return the resulting node from each conditional branch. Nodes - # that follow a :return node are assumed to be unreachable. Nil values - # are converted to nil node types. - # - # @abstract - # @param node [Parser::AST::Node] - # @return [Array] - def returns_from_method_body node - raise NotImplementedError - end - - # @abstract - # @param node [Parser::AST::Node] - # - # @return [Array] - def const_nodes_from node - raise NotImplementedError - end - - # @abstract - # @param cursor [Solargraph::Source::Cursor] - # @return [Parser::AST::Node, nil] - def find_recipient_node cursor - raise NotImplementedError - end - - # @abstract - # @param node [Parser::AST::Node] - # @return [Array] low-level value nodes in - # value position. Does not include explicit return - # statements - def value_position_nodes_only(node) - raise NotImplementedError - end - - # @abstract - # @param nodes [Enumerable] - def any_splatted_call?(nodes) - raise NotImplementedError - end - - # @abstract - # @param node [Parser::AST::Node] - # @return [void] - def process node - raise NotImplementedError - end - - # @abstract - # @param node [Parser::AST::Node] - # @return [Hash{Symbol => Source::Chain}] - def convert_hash node - raise NotImplementedError - end - - # @abstract - # @param node [Parser::AST::Node] - # @return [Position] - def get_node_start_position(node) - raise NotImplementedError - end - - # @abstract - # @param node [Parser::AST::Node] - # @return [Position] - def get_node_end_position(node) - raise NotImplementedError - end - end - end -end diff --git a/spec/api_map/constants_spec.rb b/spec/api_map/constants_spec.rb index 26eaf6b25..e09c12c29 100644 --- a/spec/api_map/constants_spec.rb +++ b/spec/api_map/constants_spec.rb @@ -20,6 +20,33 @@ module Quuz expect(resolved).to eq('Foo::Bar') end + it 'resolves constants in includes' do + code = %( + module A + module Parser + module C + module_function + + # @return [String] + def baz; "abc"; end + end + + B = C + end + + class Foo + include Parser::B + + # @return [String] + def bar + baz + end + end + end) + checker = Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) + expect(checker.problems.map(&:message)).to be_empty + end + it 'returns namespaces for nested namespaces' do source_map = Solargraph::SourceMap.load_string(%( module Foo From dd06a479b9f12e797b12caef53bdff482f686426 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 12:51:45 -0400 Subject: [PATCH 390/460] Linting --- spec/api_map/constants_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/api_map/constants_spec.rb b/spec/api_map/constants_spec.rb index e09c12c29..ef07c00b7 100644 --- a/spec/api_map/constants_spec.rb +++ b/spec/api_map/constants_spec.rb @@ -25,8 +25,6 @@ module Quuz module A module Parser module C - module_function - # @return [String] def baz; "abc"; end end From cc86aec403ca74694a408e85e6ccd1f5f5e32343 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 13:00:55 -0400 Subject: [PATCH 391/460] Ratchet RuboCop --- .rubocop_todo.yml | 35 +---------------------------------- lib/solargraph/api_map.rb | 2 -- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 89fd47c5d..0de27608a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.80.2. +# using RuboCop version 1.80.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -280,7 +280,6 @@ Layout/MultilineMethodCallBraceLayout: # SupportedStyles: aligned, indented, indented_relative_to_receiver Layout/MultilineMethodCallIndentation: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/diagnostics/type_check.rb' - 'lib/solargraph/language_server/message/completion_item/resolve.rb' - 'lib/solargraph/language_server/message/text_document/hover.rb' @@ -356,7 +355,6 @@ Layout/SpaceBeforeBlockBraces: - 'lib/solargraph/source.rb' - 'lib/solargraph/source/chain/call.rb' - 'lib/solargraph/source/chain/class_variable.rb' - - 'lib/solargraph/source/chain/constant.rb' - 'lib/solargraph/source/chain/global_variable.rb' - 'lib/solargraph/source/chain/instance_variable.rb' - 'lib/solargraph/source/chain/variable.rb' @@ -433,13 +431,11 @@ Layout/TrailingWhitespace: Exclude: - 'lib/solargraph/language_server/message/client/register_capability.rb' - 'spec/api_map/config_spec.rb' - - 'spec/convention_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedMethods, AllowedPatterns. Lint/AmbiguousBlockAssociation: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/language_server/host.rb' # This cop supports safe autocorrection (--autocorrect). @@ -512,11 +508,6 @@ Lint/DuplicateMethods: - 'lib/solargraph/rbs_map/core_map.rb' - 'lib/solargraph/source/chain/link.rb' -# Configuration parameters: AllowComments, AllowEmptyLambdas. -Lint/EmptyBlock: - Exclude: - - 'spec/convention_spec.rb' - # Configuration parameters: AllowComments. Lint/EmptyClass: Exclude: @@ -998,11 +989,6 @@ RSpec/DescribedClass: - 'spec/yard_map/mapper/to_method_spec.rb' - 'spec/yard_map/mapper_spec.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -RSpec/EmptyExampleGroup: - Exclude: - - 'spec/convention_spec.rb' - # This cop supports safe autocorrection (--autocorrect). RSpec/EmptyLineAfterFinalLet: Exclude: @@ -1321,7 +1307,6 @@ Style/AccessorGrouping: # SupportedStyles: always, conditionals Style/AndOr: Exclude: - - 'lib/solargraph/api_map/source_to_yard.rb' - 'lib/solargraph/complex_type/unique_type.rb' - 'lib/solargraph/language_server/message/base.rb' - 'lib/solargraph/page.rb' @@ -1510,7 +1495,6 @@ Style/Documentation: - 'lib/solargraph/parser.rb' - 'lib/solargraph/parser/comment_ripper.rb' - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/parser/node_methods.rb' - 'lib/solargraph/parser/node_processor/base.rb' - 'lib/solargraph/parser/parser_gem.rb' - 'lib/solargraph/parser/parser_gem/class_methods.rb' @@ -1651,7 +1635,6 @@ Style/FrozenStringLiteralComment: - 'lib/solargraph/parser.rb' - 'lib/solargraph/parser/comment_ripper.rb' - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/parser/node_methods.rb' - 'lib/solargraph/parser/parser_gem.rb' - 'lib/solargraph/parser/snippet.rb' - 'lib/solargraph/pin/breakable.rb' @@ -1782,7 +1765,6 @@ Style/GlobalStdStream: # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/library.rb' - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - 'lib/solargraph/pin_cache.rb' @@ -1835,7 +1817,6 @@ Style/IfInsideElse: # This cop supports safe autocorrection (--autocorrect). Style/IfUnlessModifier: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/unique_type.rb' @@ -1903,7 +1884,6 @@ Style/MethodDefParentheses: Exclude: - 'lib/solargraph.rb' - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/type_methods.rb' @@ -1925,7 +1905,6 @@ Style/MethodDefParentheses: - 'lib/solargraph/location.rb' - 'lib/solargraph/parser/comment_ripper.rb' - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/parser/node_methods.rb' - 'lib/solargraph/parser/node_processor/base.rb' - 'lib/solargraph/parser/parser_gem/flawed_builder.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' @@ -1947,7 +1926,6 @@ Style/MethodDefParentheses: - 'lib/solargraph/shell.rb' - 'lib/solargraph/source.rb' - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/chain/constant.rb' - 'lib/solargraph/source_map.rb' - 'lib/solargraph/source_map/mapper.rb' - 'lib/solargraph/type_checker.rb' @@ -2049,8 +2027,6 @@ Style/NumericLiterals: Style/NumericPredicate: Exclude: - 'spec/**/*' - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/type_methods.rb' @@ -2131,7 +2107,6 @@ Style/RedundantFreeze: # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: - - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/source_map/mapper.rb' @@ -2143,7 +2118,6 @@ Style/RedundantParentheses: - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/pin/search.rb' - 'lib/solargraph/source.rb' - 'lib/solargraph/type_checker.rb' @@ -2170,7 +2144,6 @@ Style/RedundantRegexpEscape: # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/complex_type/type_methods.rb' - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/parser/parser_gem/node_methods.rb' @@ -2269,8 +2242,6 @@ Style/StderrPuts: # Configuration parameters: Mode. Style/StringConcatenation: Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/pin/base.rb' - 'lib/solargraph/pin/callable.rb' - 'lib/solargraph/pin/closure.rb' @@ -2286,7 +2257,6 @@ Style/StringLiterals: Exclude: - 'Gemfile' - 'Rakefile' - - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/complex_type/unique_type.rb' - 'lib/solargraph/convention/struct_definition.rb' @@ -2504,8 +2474,6 @@ Style/YAMLFileRead: # This cop supports unsafe autocorrection (--autocorrect-all). Style/ZeroLengthPredicate: Exclude: - - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/source/chain/array.rb' @@ -2614,7 +2582,6 @@ Layout/LineLength: - 'lib/solargraph/workspace.rb' - 'lib/solargraph/workspace/config.rb' - 'lib/solargraph/yard_map/mapper/to_method.rb' - - 'spec/api_map_spec.rb' - 'spec/complex_type_spec.rb' - 'spec/language_server/message/completion_item/resolve_spec.rb' - 'spec/language_server/message/extended/check_gem_version_spec.rb' diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 7f53e7596..7bac1395c 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -737,7 +737,6 @@ def store # @param skip [Set] # @param no_core [Boolean] Skip core classes if true # @return [Array] - # rubocop:disable Metrics/CyclomaticComplexity def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false rooted_type = ComplexType.parse(rooted_tag).force_rooted fqns = rooted_type.namespace @@ -802,7 +801,6 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false end result end - # rubocop:enable Metrics/CyclomaticComplexity # @return [Hash] def path_macros From 343cd23307e00ae0c0d4952514f326eac209de5b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 14:36:29 -0400 Subject: [PATCH 392/460] Fix lack of parameters in include types --- lib/solargraph/api_map.rb | 6 ++-- lib/solargraph/api_map/constants.rb | 37 +++++++++++++-------- lib/solargraph/api_map/store.rb | 2 +- lib/solargraph/complex_type/type_methods.rb | 4 +++ lib/solargraph/pin/reference.rb | 13 ++------ 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 7bac1395c..14d33b1d7 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -771,8 +771,8 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if scope == :instance store.get_includes(fqns).reverse.each do |ref| - fqin = dereference(ref) - result.concat inner_get_methods(fqin, scope, visibility, deep, skip, true) + in_tag = dereference(ref) + result.concat inner_get_methods_from_reference(in_tag, namespace_pin, rooted_type, scope, visibility, deep, skip, true) end rooted_sc_tag = qualify_superclass(rooted_tag) unless rooted_sc_tag.nil? @@ -781,7 +781,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false else logger.info { "ApiMap#inner_get_methods(#{fqns}, #{scope}, #{visibility}, #{deep}, #{skip}) - looking for get_extends() from #{fqns}" } store.get_extends(fqns).reverse.each do |em| - fqem = store.constants.dereference(em) + fqem = dereference(em) result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil? end rooted_sc_tag = qualify_superclass(rooted_tag) diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 5dbcd4b67..0b6070507 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -15,10 +15,12 @@ def initialize store # `Constants#resolve` finds fully qualified (absolute) # namespaces based on relative names and the open gates # (namespaces) provided. Names must be runtime-visible (erased) - # non-literal types - e.g., TrueClass, NilClass, Integer and - # Hash instead of true, nil, 96, or Hash{String => Symbol} + # non-literal types, non-duck, non-signature types - e.g., + # TrueClass, NilClass, Integer and Hash instead of true, nil, + # 96, or Hash{String => Symbol} # # Note: You may want to be using #qualify. Notably, #resolve: + # - does not handle anything with type parameters # - will not gracefully handle nil, self and Boolean # - will return a constant name instead of following its assignment # @@ -40,7 +42,7 @@ def resolve(name, *gates) # @param pin [Pin::Reference] # @return [String, nil] def dereference pin - qualify(pin.name, pin.reference_gates) + qualify_type(pin.type, pin.reference_gates)&.tag end # Collect a list of all constants defined in the specified gates. @@ -52,27 +54,36 @@ def collect(*gates) cached_collect[flat] || collect_and_cache(flat) end - # Determine a fully qualified namespace for a given name referenced - # from the specified open gates. This method will search in each gate - # until it finds a match for the name. + # Determine a fully qualified namespace for a given tag + # referenced from the specified open gates. This method will + # search in each gate until it finds a match for the name. # - # @param name [String, nil] The namespace to match + # @param tag [String, nil] The type to match # @param gates [Array] # @return [String, nil] fully qualified tag - def qualify name, *gates - return name if ['Boolean', 'self', nil].include?(name) + def qualify tag, *gates + type = ComplexType.try_parse(tag) + qualify_type(type)&.tag + end + + # @param type [ComplexType, nil] The type to match + # @param gates [Array] + # + # @return [ComplexType, nil] A new rooted ComplexType + def qualify_type type, *gates + return nil if type.nil? + return type if type.selfy? || type.literal? || type.tag == 'nil' || type.interface? gates.push '' unless gates.include?('') - fqns = resolve(name, gates) + fqns = resolve(type.namespace, type.namespace) return unless fqns pin = store.get_path_pins(fqns).first if pin.is_a?(Pin::Constant) const = Solargraph::Parser::NodeMethods.unpack_name(pin.assignment) return unless const - resolve(const, pin.gates) - else - fqns + fqns = resolve(const, pin.gates) end + type.recreate(new_name: fqns, rooted: true) end # @return [void] diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index b3953cfec..a0b2ae856 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -244,7 +244,7 @@ def get_ancestors(fqns) # Add includes, prepends, and extends [get_includes(current), get_prepends(current), get_extends(current)].each do |refs| next if refs.nil? - refs.map(&:parametrized_tag).map(&:to_s).each do |ref| + refs.map(&:type).map(&:to_s).each do |ref| next if ref.nil? || ref.empty? || visited.include?(ref) ancestors << ref queue << ref diff --git a/lib/solargraph/complex_type/type_methods.rb b/lib/solargraph/complex_type/type_methods.rb index d8d4fc7d7..1145bd034 100644 --- a/lib/solargraph/complex_type/type_methods.rb +++ b/lib/solargraph/complex_type/type_methods.rb @@ -43,6 +43,10 @@ def rooted_tag @rooted_tag ||= rooted_name + rooted_substring end + def interface? + name.start_with?('_') + end + # @return [Boolean] def duck_type? @duck_type ||= name.start_with?('#') diff --git a/lib/solargraph/pin/reference.rb b/lib/solargraph/pin/reference.rb index d678ab7b7..d456fbbf8 100644 --- a/lib/solargraph/pin/reference.rb +++ b/lib/solargraph/pin/reference.rb @@ -18,18 +18,9 @@ def initialize generic_values: [], **splat @generic_values = generic_values end - # @return [String] - def parameter_tag - @parameter_tag ||= if generic_values&.any? - "<#{generic_values.join(', ')}>" - else - '' - end - end - # @return [ComplexType] - def parametrized_tag - @parametrized_tag ||= ComplexType.try_parse( + def type + @type ||= ComplexType.try_parse( name + if generic_values&.length&.> 0 "<#{generic_values.join(', ')}>" From d787886b956aa9b5d748fe4003f0ceb1b4dcd4ca Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 15:16:22 -0400 Subject: [PATCH 393/460] Bug fixes --- lib/solargraph/api_map.rb | 2 +- lib/solargraph/api_map/constants.rb | 13 +++++++------ lib/solargraph/api_map/source_to_yard.rb | 4 ++-- lib/solargraph/api_map/store.rb | 2 +- lib/solargraph/complex_type.rb | 15 +++++++++++++++ .../convention/active_support_concern.rb | 2 +- 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 14d33b1d7..286761db2 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -662,7 +662,7 @@ def super_and_sub?(sup, sub) # # @return [Boolean] def type_include?(host_ns, module_ns) - store.get_includes(host_ns).map { |inc_tag| inc_tag.parametrized_tag.name }.include?(module_ns) + store.get_includes(host_ns).map { |inc_tag| inc_tag.type.name }.include?(module_ns) end # @param pins [Enumerable] diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 0b6070507..9463e2037 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -42,7 +42,7 @@ def resolve(name, *gates) # @param pin [Pin::Reference] # @return [String, nil] def dereference pin - qualify_type(pin.type, pin.reference_gates)&.tag + qualify_type(pin.type, *pin.reference_gates)&.tag end # Collect a list of all constants defined in the specified gates. @@ -63,7 +63,7 @@ def collect(*gates) # @return [String, nil] fully qualified tag def qualify tag, *gates type = ComplexType.try_parse(tag) - qualify_type(type)&.tag + qualify_type(type, *gates)&.tag end # @param type [ComplexType, nil] The type to match @@ -72,18 +72,19 @@ def qualify tag, *gates # @return [ComplexType, nil] A new rooted ComplexType def qualify_type type, *gates return nil if type.nil? - return type if type.selfy? || type.literal? || type.tag == 'nil' || type.interface? + return type if type.selfy? || type.literal? || type.tag == 'nil' || type.interface? || + type.tag == 'Boolean' gates.push '' unless gates.include?('') - fqns = resolve(type.namespace, type.namespace) + fqns = resolve(type.rooted_namespace, *gates) return unless fqns pin = store.get_path_pins(fqns).first if pin.is_a?(Pin::Constant) const = Solargraph::Parser::NodeMethods.unpack_name(pin.assignment) return unless const - fqns = resolve(const, pin.gates) + fqns = resolve(const, *pin.gates) end - type.recreate(new_name: fqns, rooted: true) + type.recreate(new_name: fqns, make_rooted: true) end # @return [void] diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index ccbed3eb6..39d86a85c 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -46,13 +46,13 @@ def rake_yard store store.get_includes(pin.path).each do |ref| include_object = code_object_at(pin.path, YARD::CodeObjects::ClassObject) unless include_object.nil? || include_object.nil? - include_object.instance_mixins.push code_object_map[ref.parametrized_tag.to_s] + include_object.instance_mixins.push code_object_map[ref.type.to_s] end end store.get_extends(pin.path).each do |ref| extend_object = code_object_at(pin.path, YARD::CodeObjects::ClassObject) next unless extend_object - code_object = code_object_map[ref.parametrized_tag.to_s] + code_object = code_object_map[ref.type.to_s] next unless code_object extend_object.class_mixins.push code_object # @todo add spec showing why this next line is necessary diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index a0b2ae856..d97d1d342 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -97,7 +97,7 @@ def qualify_superclass fq_sub_tag return unless ref res = constants.dereference(ref) return unless res - res + type.substring + res end # @param fqns [String] diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 8798ecb88..db378743f 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -105,6 +105,21 @@ def can_assign?(api_map, atype) any? { |ut| ut.can_assign?(api_map, atype) } end + # @param new_name [String, nil] + # @param make_rooted [Boolean, nil] + # @param new_key_types [Array, nil] + # @param rooted [Boolean, nil] + # @param new_subtypes [Array, nil] + # @return [self] + def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: nil) + ComplexType.new(map do |ut| + ut.recreate(new_name: new_name, + make_rooted: make_rooted, + new_key_types: new_key_types, + new_subtypes: new_subtypes) + end) + end + # @return [Integer] def length @items.length diff --git a/lib/solargraph/convention/active_support_concern.rb b/lib/solargraph/convention/active_support_concern.rb index 74c9ce765..ed1fba175 100644 --- a/lib/solargraph/convention/active_support_concern.rb +++ b/lib/solargraph/convention/active_support_concern.rb @@ -80,7 +80,7 @@ def process_include include_tag "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ "Handling class include include_tag=#{include_tag}" end - module_extends = api_map.get_extends(rooted_include_tag).map(&:parametrized_tag).map(&:to_s) + module_extends = api_map.get_extends(rooted_include_tag).map(&:type).map(&:to_s) logger.debug do "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ "found module extends of #{rooted_include_tag}: #{module_extends}" From 63a53dbed3be0c68d0eac7a7a61dd031b6c8466d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 15:35:50 -0400 Subject: [PATCH 394/460] Add actual type for Mutexes --- lib/solargraph/language_server/progress.rb | 2 +- lib/solargraph/library.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/language_server/progress.rb b/lib/solargraph/language_server/progress.rb index 10900a37e..98b155714 100644 --- a/lib/solargraph/language_server/progress.rb +++ b/lib/solargraph/language_server/progress.rb @@ -134,7 +134,7 @@ def keep_alive host end end - # @return [Mutex] + # @return [Thread::Mutex] def mutex @mutex ||= Mutex.new end diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 9d5162431..bdbd1354f 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -522,7 +522,7 @@ def find_external_requires source_map @external_requires = nil end - # @return [Mutex] + # @return [Thread::Mutex] def mutex @mutex ||= Mutex.new end From 9d4ba443abbe73a9bbab2cc3179ecee6e54d7d46 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 16:25:40 -0400 Subject: [PATCH 395/460] Allow more valid method pin paths --- lib/solargraph/shell.rb | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index cb919476c..f9b655664 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -257,7 +257,21 @@ def list # @return [void] def pin path api_map = Solargraph::ApiMap.load_with_cache('.', $stderr) - pins = api_map.get_path_pins path + is_method = path.include?('#') || path.include?('.') + 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 + 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 @@ -265,19 +279,6 @@ def pin path $stderr.puts "Pin not found for path '#{path}'" exit 1 when Pin::Method - # @sg-ignore Unresolved call to options - if 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 - pins = api_map.get_method_stack(ns, meth, scope: scope) - end when Pin::Namespace # @sg-ignore Unresolved call to options if options[:references] From 2712e6624275f341fffcb03aa62c2da881c3feaf Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 16:31:05 -0400 Subject: [PATCH 396/460] RuboCop fix --- lib/solargraph/shell.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index f9b655664..40410d909 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -278,7 +278,6 @@ def pin path when nil $stderr.puts "Pin not found for path '#{path}'" exit 1 - when Pin::Method when Pin::Namespace # @sg-ignore Unresolved call to options if options[:references] From 7bc2092b7844aaa78298f1d94d5f5e7e7cc84f1f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 16:36:10 -0400 Subject: [PATCH 397/460] Linting --- lib/solargraph/shell.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 40410d909..046a74296 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -258,6 +258,7 @@ def list 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)] From 0077357fcbbcda42bea6806fae8810333879cb98 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 16:41:37 -0400 Subject: [PATCH 398/460] Drop @sg-ignores --- lib/solargraph/api_map/store.rb | 4 ---- lib/solargraph/language_server/host.rb | 1 - lib/solargraph/parser/flow_sensitive_typing.rb | 1 - 3 files changed, 6 deletions(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index b76230925..00aa110eb 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -297,10 +297,6 @@ def fqns_pins_map end end - # @sg-ignore Rooted type issue here - "Declared return type - # ::Enumerable<::Solargraph::Pin::Symbol> does not match - # inferred type ::Set<::Symbol> for - # Solargraph::ApiMap::Store#symbols" # @return [Enumerable] def symbols index.pins_by_class(Pin::Symbol) diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index b6efe93a3..a8bd00a39 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -760,7 +760,6 @@ def check_diff uri, change return change if source.code.length + 1 != change['text'].length diffs = Diff::LCS.diff(source.code, change['text']) return change if diffs.length.zero? || diffs.length > 1 || diffs.first.length > 1 - # @sg-ignore push this upstream # @type [Diff::LCS::Change] diff = diffs.first.first return change unless diff.adding? && ['.', ':', '(', ',', ' '].include?(diff.element) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index ffe046156..41ce6eeaf 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -243,7 +243,6 @@ def type_name(node) end # @param clause_node [Parser::AST::Node] - # @sg-ignore need boolish support for ? methods def always_breaks?(clause_node) clause_node&.type == :break end From 46a838b2be193cf0c5392bde13ed3789687c6a48 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 16:41:50 -0400 Subject: [PATCH 399/460] Drop @sg-ignores --- lib/solargraph/shell.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 0d942686c..d85b35a99 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -287,7 +287,6 @@ def list 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)] From 1c15c8bc6304e4fc0cb23a23fbeb92c9450293ef Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 16:43:16 -0400 Subject: [PATCH 400/460] Add @sg-ignores --- lib/solargraph/workspace/config.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 513d9179d..4ab153681 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -62,6 +62,7 @@ def calculated # A domain is a namespace that the ApiMap should include in the global # namespace. It's typically used to identify available DSLs. # + # @sg-ignore need validated config # @return [Array] def domains raw_data['domains'] @@ -69,6 +70,7 @@ def domains # An array of required paths to add to the workspace. # + # @sg-ignore need validated config # @return [Array] def required raw_data['require'] @@ -83,6 +85,7 @@ def require_paths # An array of reporters to use for diagnostics. # + # @sg-ignore need validated config # @return [Array] def reporters raw_data['reporters'] @@ -90,6 +93,7 @@ def reporters # A hash of options supported by the formatter # + # @sg-ignore need validated config # @return [Hash] def formatter raw_data['formatter'] @@ -97,6 +101,7 @@ def formatter # An array of plugins to require. # + # @sg-ignore need validated config # @return [Array] def plugins raw_data['plugins'] @@ -104,6 +109,7 @@ def plugins # The maximum number of files to parse from the workspace. # + # @sg-ignore need validated config # @return [Integer] def max_files raw_data['max_files'] From 17caac789f84bd9e2a960e6d0389675ece19db07 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 16:49:42 -0400 Subject: [PATCH 401/460] More annotation tweaks --- lib/solargraph/doc_map.rb | 1 + lib/solargraph/pin/delegated_method.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 0aa60c0cd..7e158eea7 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -125,6 +125,7 @@ def gemspecs def load_serialized_gem_pins out: @out serialized_pins = [] with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v } + # @sg-ignore need to teach solargraph about Hash[] # @type [Array] missing_paths = Hash[without_gemspecs].keys # @type [Array] diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index 9483fb058..cc58a0643 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -11,9 +11,9 @@ class DelegatedMethod < Pin::Method # given closure/scope, and the delegated method will then be resolved # to a method pin on that type. # - # @param method [Method, nil] an already resolved method pin. + # @param method [Pin::Method, nil] an already resolved method pin. # @param receiver [Source::Chain, nil] the source code used to resolve the receiver for this delegated method. - # @param name [String] + # @param name [String, nil] # @param receiver_method_name [String] the method name that will be called on the receiver (defaults to :name). def initialize(method: nil, receiver: nil, name: method&.name, receiver_method_name: name, **splat) raise ArgumentError, 'either :method or :receiver is required' if (method && receiver) || (!method && !receiver) From 44dfdd2f59dba25042e557886b6184816f31f144 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 16:55:36 -0400 Subject: [PATCH 402/460] Ratchet RuboCop --- .rubocop_todo.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d877296dd..f6cf377b8 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1089,7 +1089,6 @@ Style/RedundantRegexpEscape: Style/RedundantReturn: Exclude: - 'lib/solargraph/complex_type/type_methods.rb' - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/parser/parser_gem/node_methods.rb' - 'lib/solargraph/source/chain/z_super.rb' @@ -1121,7 +1120,6 @@ Style/SafeNavigation: # Configuration parameters: Max. Style/SafeNavigationChainLength: Exclude: - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/workspace/gemspecs.rb' # This cop supports unsafe autocorrection (--autocorrect-all). From e1b26b0ed3ffcd744f9622d7dff775d3a625c8d7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 17:00:45 -0400 Subject: [PATCH 403/460] Update rubocop todo --- .rubocop_todo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f6cf377b8..55d6915e1 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -445,7 +445,7 @@ Metrics/AbcSize: # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. # AllowedMethods: refine Metrics/BlockLength: - Max: 54 + Max: 56 # Configuration parameters: CountBlocks, CountModifierForms. Metrics/BlockNesting: From 22a78b72ba5eaa865a60c14d0acc8064538122c8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 17:12:13 -0400 Subject: [PATCH 404/460] Fix spec --- spec/yard_map/mapper_spec.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/yard_map/mapper_spec.rb b/spec/yard_map/mapper_spec.rb index ce029fe36..01c224ad7 100644 --- a/spec/yard_map/mapper_spec.rb +++ b/spec/yard_map/mapper_spec.rb @@ -66,13 +66,10 @@ def pins_with require it 'adds corect gates' do # Asssuming the ast gem exists because it's a known dependency - gemspec = Gem::Specification.find_by_name('ast') - Solargraph::Yardoc.cache([], gemspec) - pins = Solargraph::YardMap::Mapper.new(Solargraph::Yardoc.load!(gemspec)).map - pin = pins.find do |pin| + inc = pins_with('ast').find do |pin| pin.is_a?(Solargraph::Pin::Namespace) && pin.name == 'Mixin' && pin.closure.path == 'AST::Processor' end - expect(pin.gates).to eq(['AST::Processor::Mixin', 'AST::Processor', 'AST', '']) + expect(inc.gates).to eq(['AST::Processor::Mixin', 'AST::Processor', 'AST', '']) end it 'adds extend references' do From 8c4cfe6943b06de23cdc2836fa967e5f78b90b15 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 17:50:13 -0400 Subject: [PATCH 405/460] Standardize on RBS version for typechecking --- .github/workflows/typecheck.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 8b0de9759..c067623e3 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -21,6 +21,9 @@ jobs: name: Solargraph / strong runs-on: ubuntu-latest + strategy: + matrix: + rbs-version: ['4.0.0.dev.4'] steps: - uses: actions/checkout@v3 @@ -29,6 +32,8 @@ jobs: with: ruby-version: 3.4 bundler-cache: false + - name: Set rbs version + run: echo "gem 'rbs', '${{ matrix.rbs-version }}'" >> .Gemfile - name: Install gems run: | bundle install From 6211ce232195b8757f25ed234f8bb522beb1ceb0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 20:33:19 -0400 Subject: [PATCH 406/460] Add bundle list (debug) --- .github/workflows/typecheck.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index c067623e3..291fd43f5 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -38,6 +38,7 @@ jobs: run: | bundle install bundle update rbs # use latest available for this Ruby version + bundle list - name: Install gem types run: bundle exec rbs collection install - name: Debug From d56bb21ab670c58d872ecbed160bc64558d2f6b1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 27 Sep 2025 09:42:26 -0400 Subject: [PATCH 407/460] Fix merge --- lib/solargraph/api_map.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index cedc9b145..328867fa9 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -515,7 +515,8 @@ def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private, fqns = rooted_type.namespace namespace_pin = store.get_path_pins(fqns).first methods = if namespace_pin.is_a?(Pin::Constant) - type = namespace_pin.infer(self) + type = namespace_pin.typify(self) + type = namespace_pin.probe(self) unless type.defined? if type.defined? namespace_pin = store.get_path_pins(type.namespace).first get_methods(type.namespace, scope: scope, visibility: visibility).select { |p| p.name == name } From 455957528e5fc029fe30519810aba85c0391993c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 27 Sep 2025 09:49:53 -0400 Subject: [PATCH 408/460] Debug --- .github/workflows/typecheck.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 291fd43f5..c81ca4e0b 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -45,5 +45,8 @@ jobs: run: | bundle info rbs bundle info thor + bundle exec solargraph pin 'YARD::DocstringParser#to_docstring' + bundle exec solargraph pin --stack 'YARD::DocstringParser#to_docstring' + bundle exec solargraph pin --typify 'YARD::DocstringParser#to_docstring' - name: Typecheck self run: SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strong From 55cf9c32da1375e9213d656e83d865b320131c69 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 27 Sep 2025 10:12:21 -0400 Subject: [PATCH 409/460] Fix pin combination consistency issue I was hitting a strange 'works on my machine' issue and figured it out that I wasn't seeing it because of a difference in where files lived in CI vs my machine. This resulted in different pin info being selected during combination, as we were using the file location when we had no better way to prefer one over the other. This should make the result more consistent, making user and CI pin-combination-triggered issues easier to reproduce. --- lib/solargraph/pin/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index c0eecabb2..d11c9455e 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -354,7 +354,7 @@ def choose_pin_attr(other, attr) end # arbitrary way of choosing a pin # @sg-ignore Need _1 support - [val1, val2].compact.min_by { _1.best_location.to_s } + [val1, val2].compact.max_by { File.basename(_1.best_location.to_s) } end # @return [void] From 5d4bbc3b37c01eadfac7c60b17035e7412346e77 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 27 Sep 2025 22:09:31 -0400 Subject: [PATCH 410/460] Closure merging fixes 1) Prefer closures with more gates, to maximize compatibility 2) Look at basename of location, to make choice consistent --- lib/solargraph/pin/base.rb | 11 +++++++++-- spec/pin/base_spec.rb | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index c0eecabb2..bb6608d0f 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -353,8 +353,15 @@ def choose_pin_attr(other, attr) # :nocov: end # arbitrary way of choosing a pin - # @sg-ignore Need _1 support - [val1, val2].compact.min_by { _1.best_location.to_s } + [val1, val2].compact.max_by do |closure| + [ + # maximize number of gates, as types in other combined pins may + # depend on those gates + closure.gates.length, + # use basename so that results don't vary system to system + File.basename(closure.best_location.to_s) + ] + end end # @return [void] diff --git a/spec/pin/base_spec.rb b/spec/pin/base_spec.rb index 6bcce6cef..d1d2f0cf4 100644 --- a/spec/pin/base_spec.rb +++ b/spec/pin/base_spec.rb @@ -48,4 +48,18 @@ pin = Solargraph::Pin::Base.new(name: 'Foo', comments: '@return [undefined]') expect(pin.link_documentation).to eq('Foo') end + + it 'deals well with known closure combination issue' do + Solargraph::Shell.new.uncache('yard') + api_map = Solargraph::ApiMap.load_with_cache('.', $stderr) + pins = api_map.get_method_stack('YARD::Docstring', 'parser', scope: :class) + expect(pins.length).to eq(1) + parser_method_pin = pins.first + expect(parser_method_pin.source).to eq(:combined) + return_type = parser_method_pin.typify(api_map) + expect(parser_method_pin.closure.name).to eq("Docstring") + expect(parser_method_pin.closure.gates).to eq(["YARD::Docstring", "YARD", '']) + expect(return_type).to be_defined + expect(parser_method_pin.typify(api_map).rooted_tags).to eq('::YARD::DocstringParser') + end end From b5898e2a97b81a0219ee145769c978d1fabcb6c3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 27 Sep 2025 22:21:02 -0400 Subject: [PATCH 411/460] Drop incidental requirement --- spec/pin/base_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/pin/base_spec.rb b/spec/pin/base_spec.rb index d1d2f0cf4..1a6cfd1e8 100644 --- a/spec/pin/base_spec.rb +++ b/spec/pin/base_spec.rb @@ -55,7 +55,6 @@ pins = api_map.get_method_stack('YARD::Docstring', 'parser', scope: :class) expect(pins.length).to eq(1) parser_method_pin = pins.first - expect(parser_method_pin.source).to eq(:combined) return_type = parser_method_pin.typify(api_map) expect(parser_method_pin.closure.name).to eq("Docstring") expect(parser_method_pin.closure.gates).to eq(["YARD::Docstring", "YARD", '']) From 19aa4f78f4e5a49b0e603278bfdea5d6da868675 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 07:44:39 -0400 Subject: [PATCH 412/460] Revert change --- lib/solargraph/language_server/host.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index a8bd00a39..b6efe93a3 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -760,6 +760,7 @@ def check_diff uri, change return change if source.code.length + 1 != change['text'].length diffs = Diff::LCS.diff(source.code, change['text']) return change if diffs.length.zero? || diffs.length > 1 || diffs.first.length > 1 + # @sg-ignore push this upstream # @type [Diff::LCS::Change] diff = diffs.first.first return change unless diff.adding? && ['.', ':', '(', ',', ' '].include?(diff.element) From 1416e1d1e3f51c5c44dea3807796cbccccd870d3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 12:08:05 -0400 Subject: [PATCH 413/460] Reduce number of build jobs for faster CI feedback This takes out some lower value combinations - ideally we could keep the number of jobs to <= 20, which is the max that GHA will run simultaneously here. --- .github/workflows/rspec.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index ecc3d9771..33d09b579 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -23,12 +23,26 @@ jobs: matrix: ruby-version: ['3.0', '3.1', '3.2', '3.3', '3.4', 'head'] rbs-version: ['3.6.1', '3.9.4', '4.0.0.dev.4'] - # Ruby 3.0 doesn't work with RBS 3.9.4 or 4.0.0.dev.4 exclude: + # Ruby 3.0 doesn't work with RBS 3.9.4 or 4.0.0.dev.4 - ruby-version: '3.0' rbs-version: '3.9.4' - ruby-version: '3.0' rbs-version: '4.0.0.dev.4' + # only include the 3.1 variants we include later + - ruby-version: '3.1' + # only include the 3.2 variants we include later + - ruby-version: '3.2' + # only include the 3.3 variants we include later + - ruby-version: '3.3' + include: + - ruby-version: '3.1' + rbs_version: '3.6.1' + - ruby-version: '3.2' + rbs_version: '3.9.4' + - ruby-version: '3.3' + rbs_version: '4.0.0.dev.4' + steps: - uses: actions/checkout@v3 - name: Set up Ruby From f2abb735f39f7adb08d4a487dd4c6a1b76acbfd2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 12:10:38 -0400 Subject: [PATCH 414/460] Fix punctuation --- .github/workflows/rspec.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 33d09b579..76003b412 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -37,12 +37,11 @@ jobs: - ruby-version: '3.3' include: - ruby-version: '3.1' - rbs_version: '3.6.1' + rbs-version: '3.6.1' - ruby-version: '3.2' - rbs_version: '3.9.4' + rbs-version: '3.9.4' - ruby-version: '3.3' - rbs_version: '4.0.0.dev.4' - + rbs-version: '4.0.0.dev.4' steps: - uses: actions/checkout@v3 - name: Set up Ruby From eabe151b3dc4fce0600d0e5c62850907b74101c2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 10:41:55 -0400 Subject: [PATCH 415/460] Update rubocop todo --- .rubocop_todo.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4ab62a41f..7d2e33136 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -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' @@ -89,13 +88,6 @@ Layout/EmptyLineBetweenDefs: Layout/EmptyLines: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only -Layout/EmptyLinesAroundClassBody: - Exclude: - - 'lib/solargraph/rbs_map/core_map.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines @@ -1266,6 +1258,7 @@ YARD/TagTypeSyntax: Exclude: - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/parser/comment_ripper.rb' + - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/type_checker.rb' # This cop supports safe autocorrection (--autocorrect). From ce3ce8c3ed11ec0031d53ad01163b43bc029d9eb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 10:51:49 -0400 Subject: [PATCH 416/460] Fix merge issue --- lib/solargraph/api_map.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index a7462fb50..695de0230 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -109,7 +109,6 @@ def catalog bench if recreate_docmap @doc_map = DocMap.new(unresolved_requires, [], bench.workspace, out: nil) # @todo Implement gem preferences - @gemspecs = @doc_map.workspace.gemspecs @unresolved_requires = @doc_map.unresolved_requires end @cache.clear if store.update(@@core_map.pins, @doc_map.pins, conventions_environ.pins, iced_pins, live_pins) From a6bdfd6336f0e12833fc7a52d87bc47eab39b14b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 10:57:41 -0400 Subject: [PATCH 417/460] Drop no-longer-needed @sg-ignores --- lib/solargraph/library.rb | 1 - lib/solargraph/yardoc.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 1faa6a9ba..a14dc40ee 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -609,7 +609,6 @@ def cache_next_gemspec report_cache_progress spec.name, pending kwargs = {} kwargs[:chdir] = workspace.directory.to_s if workspace.directory && !workspace.directory.empty? - # @sg-ignore Unresolved call to capture3 on Module _o, e, s = Open3.capture3(workspace.command_path, 'cache', spec.name, spec.version.to_s, **kwargs) if s.success? diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index ffe7da4c3..50f212f13 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -28,7 +28,6 @@ def build_docs gem_yardoc_path, yard_plugins, gemspec return end - # @sg-ignore stdout_and_stderr_str, status = Open3.capture2e(current_bundle_env_tweaks, cmd, chdir: gemspec.gem_dir) return if status.success? Solargraph.logger.warn { "YARD failed running #{cmd.inspect} in #{gemspec.gem_dir}" } From 94de7b5c34c1da22e694289ef4f2cb2c4ef7064e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 11:19:06 -0400 Subject: [PATCH 418/460] Ratchet RuboCop --- .rubocop_todo.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 38cf122ee..e05aa4ccf 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -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' @@ -88,13 +87,6 @@ Layout/EmptyLineBetweenDefs: Layout/EmptyLines: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only -Layout/EmptyLinesAroundClassBody: - Exclude: - - 'lib/solargraph/rbs_map/core_map.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines From 3ab765d51bbe781a8c746005f476e01db49a0926 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 12:11:50 -0400 Subject: [PATCH 419/460] Drop unneeded @sg-ignores --- lib/solargraph/workspace/gemspecs.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index c42b2d843..38b46da30 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -38,7 +38,6 @@ def resolve_require require return nil if require.empty? return gemspecs_required_from_bundler if require == 'bundler/require' - # @sg-ignore Variable type could not be inferred for gemspec # @type [Gem::Specification, nil] gemspec = Gem::Specification.find_by_path(require) if gemspec.nil? @@ -50,7 +49,6 @@ def resolve_require require # See if we can make a good guess: potential_gemspec = Gem::Specification.find_by_name(gem_name_guess) file = "lib/#{require}.rb" - # @sg-ignore Unresolved call to files gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file } rescue Gem::MissingSpecError logger.debug do @@ -161,7 +159,6 @@ def gemspecs_required_from_external_bundle 'puts Bundler.definition.locked_gems.specs.map { |spec| [spec.name, spec.version] }' \ '.to_h.to_json }' ] - # @sg-ignore Unresolved call to capture3 on Module o, e, s = Open3.capture3(*cmd) if s.success? Solargraph.logger.debug "External bundle: #{o}" From 5cf497eb52f49e209e46ae8db49aab82ee829b83 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 17:33:05 -0400 Subject: [PATCH 420/460] Fix merge --- lib/solargraph/type_checker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index efe8460e5..782b0b25e 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -309,7 +309,7 @@ def call_problems all_found = [] closest = ComplexType::UNDEFINED until base.links.first.undefined? - all_found = base.define(api_map, block_pin, locals) + all_found = base.define(api_map, closure_pin, locals) found = all_found.first break if found missing = base From bcf5f115621f47bb49e3307654c95a172c4976d9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 17:34:24 -0400 Subject: [PATCH 421/460] Fix merge --- lib/solargraph/type_checker.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 782b0b25e..a440c7048 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -497,7 +497,7 @@ def kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pi ptype = data[:qualified] ptype = ptype.self_to_type(pin.context) unless ptype.undefined? - argtype = argchain.infer(api_map, closure_pin, locals).self_to_type(block_pin.context) + argtype = argchain.infer(api_map, closure_pin, locals).self_to_type(closure_pin.context) if argtype.defined? && ptype && !arg_conforms_to?(argtype, ptype) result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") end @@ -525,7 +525,7 @@ def kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, ptype = params[pname.to_s][:qualified] ptype = ptype.self_to_type(pin.context) argtype = argchain.infer(api_map, closure_pin, locals) - argtype = argtype.self_to_type(block_pin.context) + argtype = argtype.self_to_type(closure_pin.context) if argtype.defined? && ptype && !arg_conforms_to?(argtype, ptype) result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}") end @@ -660,7 +660,7 @@ def declared_externally? pin all_found = [] closest = ComplexType::UNDEFINED until base.links.first.undefined? - all_found = base.define(api_map, block_pin, locals) + all_found = base.define(api_map, closure_pin, locals) found = all_found.first break if found missing = base From 0610e2113d985f1a0f9021bc5b17eb770cde8797 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 17:35:53 -0400 Subject: [PATCH 422/460] Drop @sg-ignores --- lib/solargraph/doc_map.rb | 1 - lib/solargraph/pin/delegated_method.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 4458e0241..7e158eea7 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -128,7 +128,6 @@ def load_serialized_gem_pins out: @out # @sg-ignore need to teach solargraph about Hash[] # @type [Array] missing_paths = Hash[without_gemspecs].keys - # @sg-ignore Need support for RBS duck interfaces like _ToHash # @type [Array] gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies(out: out).to_a diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index cc58a0643..84da35b04 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -51,7 +51,6 @@ def type_location %i[typify realize infer probe].each do |method| # @param api_map [ApiMap] define_method(method) do |api_map| - # @sg-ignore Unresolved call to resolve_method resolve_method(api_map) # @sg-ignore Need to set context correctly in define_method blocks @resolved_method ? @resolved_method.send(method, api_map) : super(api_map) From aab4cce1cd3da57fabf0a00a016fb85e7074ef26 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 18:19:37 -0400 Subject: [PATCH 423/460] Fix merge --- lib/solargraph/convention/active_support_concern.rb | 2 +- lib/solargraph/parser/node_processor/base.rb | 2 +- .../parser/parser_gem/node_processors/opasgn_node.rb | 6 ++++++ lib/solargraph/rbs_map/core_map.rb | 5 +++++ lib/solargraph/type_checker.rb | 1 + 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/convention/active_support_concern.rb b/lib/solargraph/convention/active_support_concern.rb index ed1fba175..06c9fb722 100644 --- a/lib/solargraph/convention/active_support_concern.rb +++ b/lib/solargraph/convention/active_support_concern.rb @@ -17,7 +17,7 @@ class ActiveSupportConcern < Base # @param visibility [Array] :public, :protected, and/or :private # @param deep [Boolean] whether to include methods from included modules # @param skip [Set] - # @param _no_core [Boolean]n whether to skip core methods + # @param _no_core [Boolean] whether to skip core methods def object api_map, rooted_tag, scope, visibility, deep, skip, _no_core moo = ObjectProcessor.new(api_map, rooted_tag, scope, visibility, deep, skip) moo.environ diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index fad31e95b..ecd482ec0 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -4,7 +4,7 @@ module Solargraph module Parser module NodeProcessor class Base - # @return [Parser::AST::Node] + # @return [AST::Node] attr_reader :node # @return [Region] diff --git a/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb b/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb index 0e4d7b26a..209537a58 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb @@ -13,8 +13,14 @@ def process operator = node.children[1] argument = node.children[2] if target.type == :send + # @sg-ignore Wrong argument type for + # Solargraph::Parser::ParserGem::NodeProcessors::OpasgnNode#process_send_target: + # operator expected Symbol, received AST::Node process_send_target(target, operator, argument) elsif target.type.to_s.end_with?('vasgn') + # @sg-ignore Wrong argument type for + # Solargraph::Parser::ParserGem::NodeProcessors::OpasgnNode#process_send_target: + # operator expected Symbol, received AST::Node process_vasgn_target(target, operator, argument) else Solargraph.assert_or_log(:opasgn_unknown_target, diff --git a/lib/solargraph/rbs_map/core_map.rb b/lib/solargraph/rbs_map/core_map.rb index 82accd830..e7352a660 100644 --- a/lib/solargraph/rbs_map/core_map.rb +++ b/lib/solargraph/rbs_map/core_map.rb @@ -19,7 +19,12 @@ def initialize; end # @return [Enumerable] def pins out: $stderr return @pins if @pins + @pins = cache_core(out: out) + end + # @param out [IO, nil] output stream for logging + # @return [Array] + def cache_core out: $stderr @pins = [] cache = PinCache.deserialize_core if cache diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index a440c7048..e529c1c03 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -498,6 +498,7 @@ def kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pi ptype = ptype.self_to_type(pin.context) unless ptype.undefined? argtype = argchain.infer(api_map, closure_pin, locals).self_to_type(closure_pin.context) + # @sg-ignore Unresolved call to defined? if argtype.defined? && ptype && !arg_conforms_to?(argtype, ptype) result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") end From d486e6485d4cce9d182da079c588c3821c69cfa6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 22:36:37 -0400 Subject: [PATCH 424/460] Trim more matrix entries to make room for solargraph-rspec specs --- .github/workflows/rspec.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 76003b412..628bbc8ab 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -35,6 +35,8 @@ jobs: - ruby-version: '3.2' # only include the 3.3 variants we include later - ruby-version: '3.3' + # only include the 3.4 variants we include later + - ruby-version: '3.3' include: - ruby-version: '3.1' rbs-version: '3.6.1' @@ -42,6 +44,8 @@ jobs: rbs-version: '3.9.4' - ruby-version: '3.3' rbs-version: '4.0.0.dev.4' + - ruby-version: '3.4' + rbs-version: '4.0.0.dev.4' steps: - uses: actions/checkout@v3 - name: Set up Ruby From 56342d4a9ff7c1141372404883cd2ddd1f337de7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 22:50:42 -0400 Subject: [PATCH 425/460] Fix version number --- .github/workflows/rspec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 628bbc8ab..cc4efda4b 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -36,7 +36,7 @@ jobs: # only include the 3.3 variants we include later - ruby-version: '3.3' # only include the 3.4 variants we include later - - ruby-version: '3.3' + - ruby-version: '3.4' include: - ruby-version: '3.1' rbs-version: '3.6.1' From ac05669348b9e5518a2defd0c4fc207393567bf5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 5 Oct 2025 18:57:44 -0400 Subject: [PATCH 426/460] Document method --- lib/solargraph/workspace/gemspecs.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 05acb56c9..03ecc6f0a 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -144,6 +144,9 @@ def only_runtime_dependencies gemspec gemspec.dependencies - gemspec.development_dependencies end + # Return the gems which would be required by Bundler.require + # + # @see https://bundler.io/guides/groups.html # @return [Array] def auto_required_gemspecs_from_bundler # @todo Handle projects with custom Bundler/Gemfile setups From 84fb04996c8d90ec445091b3c38b662bba869f92 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 5 Oct 2025 19:34:39 -0400 Subject: [PATCH 427/460] Drop untested change --- lib/solargraph/workspace/gemspecs.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 03ecc6f0a..a956e1c5d 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -63,11 +63,6 @@ def resolve_require require end end end - # @todo the 'requires' provided in Environ is being used - # by plugins to pass gem names instead of require paths - # - need to expand Environ to provide a place to put gem - # names and get new plugins out before retiring this. - gemspec ||= find_gem(gem_name_guess) return nil if gemspec.nil? [gemspec_or_preference(gemspec)] From 4fb2b2c74fb54c5b9a598aa34756fa2336eac4ae Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 5 Oct 2025 19:35:20 -0400 Subject: [PATCH 428/460] Fix typo in existing code --- lib/solargraph/workspace/gemspecs.rb | 4 +--- spec/workspace/gemspecs_resolve_require_spec.rb | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index a956e1c5d..ea21ef203 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -118,9 +118,7 @@ def gemspec_or_preference gemspec return gemspec unless preference_map.key?(gemspec.name) return gemspec if gemspec.version == preference_map[gemspec.name].version - # @todo this code is unused but broken - # @sg-ignore Unresolved call to by_path - change_gemspec_version gemspec, preference_map[by_path.name].version + change_gemspec_version gemspec, preference_map[gemspec.name].version end # @param gemspec [Gem::Specification] diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb index 6ee0cecf0..b88bae269 100644 --- a/spec/workspace/gemspecs_resolve_require_spec.rb +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -261,8 +261,6 @@ def configure_bundler_spec stub_value end it 'returns the preferred gemspec' do - pending('https://github.com/castwide/solargraph/pull/1006') - gemspecs = described_class.new(dir_path, preferences: preferences) specs = gemspecs.resolve_require('backport') backport = specs.find { |spec| spec.name == 'backport' } @@ -281,8 +279,6 @@ def configure_bundler_spec stub_value end it 'returns the gemspec we do have' do - pending('https://github.com/castwide/solargraph/pull/1006') - gemspecs = described_class.new(dir_path, preferences: preferences) specs = gemspecs.resolve_require('backport') backport = specs.find { |spec| spec.name == 'backport' } From 88e1f7391e2a330185fe626de6ee6212e5076d48 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 5 Oct 2025 21:36:50 -0400 Subject: [PATCH 429/460] Fix merge --- spec/yard_map/mapper_spec.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/yard_map/mapper_spec.rb b/spec/yard_map/mapper_spec.rb index 84d2db90c..14451b97f 100644 --- a/spec/yard_map/mapper_spec.rb +++ b/spec/yard_map/mapper_spec.rb @@ -64,12 +64,9 @@ def pins_with require expect(inc).to be_a(Solargraph::Pin::Reference::Include) end - it 'adds corect gates' do + it 'adds correct gates' do # Asssuming the ast gem exists because it's a known dependency - gemspec = Gem::Specification.find_by_name('ast') - Solargraph::Yardoc.cache([], gemspec) - pins = Solargraph::YardMap::Mapper.new(Solargraph::Yardoc.load!(gemspec)).map - pin = pins.find do |pin| + pin = pins_with('ast').find do |pin| pin.is_a?(Solargraph::Pin::Namespace) && pin.name == 'Mixin' && pin.closure.path == 'AST::Processor' end expect(pin.gates).to eq(['AST::Processor::Mixin', 'AST::Processor', 'AST', '']) From e25702f3c948c58e56870f104988ba2ddb9b31b6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 08:26:23 -0400 Subject: [PATCH 430/460] Back out spec --- spec/workspace/gemspecs_resolve_require_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb index b88bae269..aa7b8d79d 100644 --- a/spec/workspace/gemspecs_resolve_require_spec.rb +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -217,7 +217,9 @@ def configure_bundler_spec stub_value let(:require) { 'bundler/gem_tasks' } - it 'returns gems' do + xit 'returns gems' do + pending('improved logic for require lookups') + expect(specs&.map(&:name)).to include('bundler') end end From 0aefc35b6035601d04fef46b387f10a55a9ec25a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 08:44:31 -0400 Subject: [PATCH 431/460] Add more solargraph-rspec requires --- spec/doc_map_spec.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index fad6d3363..94a7ebf3b 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -42,13 +42,17 @@ # These are auto-required by solargraph-rspec in case the bundle # includes these gems. In our case, it doesn't! unprovided_solargraph_rspec_requires = [ - 'rspec-rails', 'actionmailer', + 'actionpack' 'activerecord', - 'shoulda-matchers', - 'rspec-sidekiq', + 'activesupport', 'airborne', - 'activesupport' + 'rspec-core', + 'rspec-expectations', + 'rspec-mocks', + 'rspec-rails', + 'rspec-sidekiq', + 'shoulda-matchers', ] expect(doc_map.unresolved_requires - unprovided_solargraph_rspec_requires) .to eq(['not_a_gem']) From 358ff37e529914ab15e1a91f18fc63e24b16be43 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 08:46:17 -0400 Subject: [PATCH 432/460] Fix syntax --- spec/doc_map_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 94a7ebf3b..d7f40f585 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -43,7 +43,7 @@ # includes these gems. In our case, it doesn't! unprovided_solargraph_rspec_requires = [ 'actionmailer', - 'actionpack' + 'actionpack', 'activerecord', 'activesupport', 'airborne', @@ -52,7 +52,7 @@ 'rspec-mocks', 'rspec-rails', 'rspec-sidekiq', - 'shoulda-matchers', + 'shoulda-matchers' ] expect(doc_map.unresolved_requires - unprovided_solargraph_rspec_requires) .to eq(['not_a_gem']) From ada4c1299de7118ca625e2ce43af86bbdbab39c8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 12:09:44 -0400 Subject: [PATCH 433/460] Use bundle information to resolve gemspecs --- lib/solargraph/rbs_map/core_map.rb | 4 - lib/solargraph/workspace/gemspecs.rb | 365 +++++++++++++----- spec/api_map_method_spec.rb | 9 +- spec/doc_map_spec.rb | 23 +- .../gemspecs_fetch_dependencies_spec.rb | 18 +- spec/workspace/gemspecs_find_gem_spec.rb | 2 - .../gemspecs_resolve_require_spec.rb | 8 +- spec/yard_map/mapper_spec.rb | 57 ++- 8 files changed, 312 insertions(+), 174 deletions(-) diff --git a/lib/solargraph/rbs_map/core_map.rb b/lib/solargraph/rbs_map/core_map.rb index 8c3d7dbdd..48b8dcdc4 100644 --- a/lib/solargraph/rbs_map/core_map.rb +++ b/lib/solargraph/rbs_map/core_map.rb @@ -34,10 +34,6 @@ def pins @pins end - def loader - @loader ||= RBS::EnvironmentLoader.new(repository: RBS::Repository.new(no_stdlib: false)) - end - private # @return [RBS::EnvironmentLoader] diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index ea21ef203..55dac6c6a 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -43,29 +43,41 @@ def resolve_require require # @todo handle different arguments to Bundler.require return auto_required_gemspecs_from_bundler if require == 'bundler/require' - # @sg-ignore Variable type could not be inferred for gemspec - # @type [Gem::Specification, nil] - gemspec = Gem::Specification.find_by_path(require) - if gemspec.nil? - gem_name_guess = require.split('/').first + # Determine gem name based on the require path + file = "lib/#{require}.rb" + spec_with_path = Gem::Specification.find_by_path(file) + + all_gemspecs = all_gemspecs_from_bundle + + gem_names_to_try = [ + spec_with_path&.name, + require.tr('/', '-'), + require.split('/').first + ].compact.uniq + gem_names_to_try.each do |gem_name| + gemspec = all_gemspecs.find { |gemspec| gemspec.name == gem_name } + return [gemspec_or_preference(gemspec)] if gemspec + begin - # this can happen when the gem is included via a local path in - # a Gemfile; Gem doesn't try to index the paths in that case. - # - # See if we can make a good guess: - potential_gemspec = Gem::Specification.find_by_name(gem_name_guess) - file = "lib/#{require}.rb" - # @sg-ignore Unresolved call to files - gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file } + gemspec = Gem::Specification.find_by_name(gem_name) + return [gemspec_or_preference(gemspec)] if gemspec rescue Gem::MissingSpecError logger.debug do - "Require path #{require} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}" + "Require path #{require} could not be resolved to a gem via find_by_path or guess of #{gem_name}" end end + + # look ourselves just in case this is hanging out somewhere + # that find_by_path doesn't index' + gemspec = all_gemspecs.find do |spec| + spec = to_gem_specification(spec) unless spec.respond_to?(:files) + + spec&.files&.any? { |gemspec_file| file == gemspec_file } + end + return [gemspec_or_preference(gemspec)] if gemspec end - return nil if gemspec.nil? - [gemspec_or_preference(gemspec)] + nil end # @param name [String] @@ -73,10 +85,14 @@ def resolve_require require # @param out [IO, nil] output stream for logging # # @return [Gem::Specification, nil] - def find_gem name, version = nil, out = nil - Gem::Specification.find_by_name(name, version) - rescue Gem::MissingSpecError - nil + def find_gem name, version = nil, out: $stderr + gemspec = all_gemspecs_from_bundle.find { |gemspec| gemspec.name == name && gemspec.version == version } + return gemspec if gemspec + + gemspec = all_gemspecs_from_bundle.find { |gemspec| gemspec.name == name } + return gemspec if gemspec + + resolve_gem_ignoring_local_bundle name, version, out: out end # @param gemspec [Gem::Specification] @@ -84,27 +100,233 @@ def find_gem name, version = nil, out = nil # # @return [Array] def fetch_dependencies gemspec, out: $stderr - # @param spec [Gem::Dependency] - only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps| - Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}" - dep = Gem.loaded_specs[spec.name] - # @todo is next line necessary? - dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement) - deps.merge fetch_dependencies(dep) if deps.add?(dep) + gemspecs = all_gemspecs_from_bundle + + # @type [Hash{String => Gem::Specification}] + deps_so_far = {} + + # @param runtime_dep [Gem::Dependency] + # @param deps [Hash{String => Gem::Specification}] + gem_dep_gemspecs = only_runtime_dependencies(gemspec).each_with_object(deps_so_far) do |runtime_dep, deps| + next if deps[runtime_dep.name] + + Solargraph.logger.info "Adding #{runtime_dep.name} dependency for #{gemspec.name}" + dep = gemspecs.find { |dep| dep.name == runtime_dep.name } + dep ||= Gem::Specification.find_by_name(runtime_dep.name, runtime_dep.requirement) rescue Gem::MissingSpecError - Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for " \ - "#{gemspec.name} not found in RubyGems." - end.to_a + dep = resolve_gem_ignoring_local_bundle runtime_dep.name, out: out + ensure + next unless dep + + fetch_dependencies(dep, out: out).each { |sub_dep| deps[sub_dep.name] ||= sub_dep } + deps[dep.name] ||= dep + end + + gem_dep_gemspecs.values.compact.uniq(&:name) + end + + # Returns all gemspecs directly depended on by this workspace's + # bundle (does not include transitive dependencies). + # + # @return [Array] + def all_gemspecs_from_bundle + return [] unless directory + + @all_gemspecs_from_bundle ||= + if in_this_bundle? + all_gemspecs_from_this_bundle + else + all_gemspecs_from_external_bundle + end + end + + # @return [Hash{Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification => Gem::Specification}] + def self.gem_specification_cache + @gem_specification_cache ||= {} end private - # True if the workspace has a root Gemfile. + # @param specish [Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification] + # @return [Gem::Specification, nil] + def to_gem_specification specish + # print time including milliseconds + self.class.gem_specification_cache[specish] ||= case specish + when Gem::Specification + @@warned_on_rubygems ||= false + if specish.respond_to?(:identifier) + specish + else + # see https://github.com/castwide/solargraph/actions/runs/17588131738/job/49961580698?pr=1006 - happened on Ruby 3.0 + unless @@warned_on_rubygems + logger.warn "Incomplete Gem::Specification encountered - recommend upgrading rubygems" + @@warned_on_rubygems = true + end + nil + end + # yay! + + when Bundler::LazySpecification + # materializing didn't work. Let's look in the local + # rubygems without bundler's help + resolve_gem_ignoring_local_bundle specish.name, + specish.version + when Bundler::StubSpecification + # turns a Bundler::StubSpecification into a + # Gem::StubSpecification into a Gem::Specification + specish = specish.stub + if specish.respond_to?(:spec) + specish.spec + else + # turn the crank again + to_gem_specification(specish) + end + else + @@warned_on_gem_type ||= false + unless @@warned_on_gem_type + logger.warn 'Unexpected type while resolving gem: ' \ + "#{specish.class}" + @@warned_on_gem_type = true + end + nil + end + end + + # @param command [String] The expression to evaluate in the external bundle + # @sg-ignore Need a JSON type + # @yield [undefined, nil] + def query_external_bundle command + Solargraph.with_clean_env do + cmd = [ + 'ruby', '-e', + "require 'bundler'; require 'json'; Dir.chdir('#{directory}') { puts begin; #{command}; end.to_json }" + ] + # @sg-ignore Unresolved call to capture3 on Module + o, e, s = Open3.capture3(*cmd) + if s.success? + Solargraph.logger.debug "External bundle: #{o}" + o && !o.empty? ? JSON.parse(o.split("\n").last) : nil + else + Solargraph.logger.warn e + raise BundleNotFoundError, "Failed to load gems from bundle at #{directory}" + end + end + end + + def in_this_bundle? + Bundler.definition&.lockfile&.to_s&.start_with?(directory) + end + + # @return [Array] + def all_gemspecs_from_this_bundle + # Find only the gems bundler is now using + specish_objects = Bundler.definition.locked_gems.specs + if specish_objects.first.respond_to?(:materialize_for_installation) + specish_objects = specish_objects.map(&:materialize_for_installation) + end + specish_objects.map do |specish| + if specish.respond_to?(:name) && specish.respond_to?(:version) && specish.respond_to?(:gem_dir) + # duck type is good enough for outside uses! + specish + else + to_gem_specification(specish) + end + end.compact + end + + # @return [Array] + def auto_required_gemspecs_from_bundler + return [] unless directory + + logger.info 'Fetching gemspecs autorequired from Bundler (bundler/require)' + @auto_required_gemspecs_from_bundler ||= + if in_this_bundle? + auto_required_gemspecs_from_this_bundle + else + auto_required_gemspecs_from_external_bundle + end + end + + # @return [Array] + def auto_required_gemspecs_from_this_bundle + # Adapted from require() in lib/bundler/runtime.rb + dep_names = Bundler.definition.dependencies.select do |dep| + dep.groups.include?(:default) && dep.should_include? + end.map(&:name) + + all_gemspecs_from_bundle.select { |gemspec| dep_names.include?(gemspec.name) } + end + + # @return [Array] + def auto_required_gemspecs_from_external_bundle + @auto_required_gemspecs_from_external_bundle ||= + begin + logger.info 'Fetching auto-required gemspecs from Bundler (bundler/require)' + command = + 'Bundler.definition.dependencies' \ + '.select { |dep| dep.groups.include?(:default) && dep.should_include? }' \ + '.map(&:name)' + # @sg-ignore + # @type [Array] + dep_names = query_external_bundle command + + all_gemspecs_from_bundle.select { |gemspec| dep_names.include?(gemspec.name) } + end + end + + # @param gemspec [Gem::Specification] + # @return [Array] + def only_runtime_dependencies gemspec + unless gemspec.respond_to?(:dependencies) && gemspec.respond_to?(:development_dependencies) + gemspec = to_gem_specification(gemspec) + end + return [] if gemspec.nil? + + gemspec.dependencies - gemspec.development_dependencies + end + + # @todo Should this be using Gem::SpecFetcher and pull them automatically? # - # @todo Handle projects with custom Bundler/Gemfile setups (see #auto_required_gemspecs_from_bundler) + # @param name [String] + # @param version [String, nil] + # @param out [IO, nil] output stream for logging # - def gemfile? - directory && File.file?(File.join(directory, 'Gemfile')) + # @return [Gem::Specification, nil] + def resolve_gem_ignoring_local_bundle name, version = nil, out: $stderr + Gem::Specification.find_by_name(name, version) + rescue Gem::MissingSpecError + begin + Gem::Specification.find_by_name(name) + rescue Gem::MissingSpecError + stdlibmap = RbsMap::StdlibMap.new(name) + unless stdlibmap.resolved? + gem_desc = name + gem_desc += ":#{version}" if version + out&.puts "Please install the gem #{gem_desc} in Solargraph's Ruby environment" + end + nil # either not here or in stdlib + end + end + + # @return [Array] + def all_gemspecs_from_external_bundle + @all_gemspecs_from_external_bundle ||= + begin + logger.info 'Fetching gemspecs required from external bundle' + + command = 'specish_objects = Bundler.definition.locked_gems&.specs; ' \ + 'if specish_objects.first.respond_to?(:materialize_for_installation);' \ + 'specish_objects = specish_objects.map(&:materialize_for_installation);' \ + 'end;' \ + 'specish_objects.map { |specish| [specish.name, specish.version] }' + query_external_bundle(command).map do |name, version| + resolve_gem_ignoring_local_bundle(name, version) + end.compact + rescue Solargraph::BundleNotFoundError => e + Solargraph.logger.info e.message + Solargraph.logger.debug e.backtrace.join("\n") + [] + end end # @return [Hash{String => Gem::Specification}] @@ -113,6 +335,7 @@ def preference_map end # @param gemspec [Gem::Specification] + # # @return [Gem::Specification] def gemspec_or_preference gemspec return gemspec unless preference_map.key?(gemspec.name) @@ -122,83 +345,15 @@ def gemspec_or_preference gemspec end # @param gemspec [Gem::Specification] - # @param version [Gem::Version] + # @param version [String] # @return [Gem::Specification] def change_gemspec_version gemspec, version Gem::Specification.find_by_name(gemspec.name, "= #{version}") rescue Gem::MissingSpecError - Solargraph.logger.info "Gem #{gemspec.name} version #{version} not found. Using #{gemspec.version} instead" + Solargraph.logger.info "Gem #{gemspec.name} version #{version.inspect} not found. " \ + "Using #{gemspec.version} instead" gemspec end - - # @param gemspec [Gem::Specification] - # @return [Array] - def only_runtime_dependencies gemspec - gemspec.dependencies - gemspec.development_dependencies - end - - # Return the gems which would be required by Bundler.require - # - # @see https://bundler.io/guides/groups.html - # @return [Array] - def auto_required_gemspecs_from_bundler - # @todo Handle projects with custom Bundler/Gemfile setups - return unless gemfile? - - if gemfile? && Bundler.definition&.lockfile&.to_s&.start_with?(directory) - # Find only the gems bundler is now using - Bundler.definition.locked_gems.specs.flat_map do |lazy_spec| - logger.info "Handling #{lazy_spec.name}:#{lazy_spec.version}" - [Gem::Specification.find_by_name(lazy_spec.name, lazy_spec.version)] - rescue Gem::MissingSpecError => e - logger.info("Could not find #{lazy_spec.name}:#{lazy_spec.version} with " \ - 'find_by_name, falling back to guess') - # can happen in local filesystem references - # TODO: should this be resolve_require or find_gem? - specs = resolve_require lazy_spec.name - logger.warn "Gem #{lazy_spec.name} #{lazy_spec.version} from bundle not found: #{e}" if specs.nil? - next specs - end.compact - else - logger.info 'Fetching gemspecs required from Bundler (bundler/require)' - gemspecs_required_from_external_bundle - end - end - - # @return [Array] - def gemspecs_required_from_external_bundle - logger.info 'Fetching gemspecs required from external bundle' - return [] unless directory - - Solargraph.with_clean_env do - cmd = [ - 'ruby', '-e', - "require 'bundler'; " \ - "require 'json'; " \ - "Dir.chdir('#{directory}') { " \ - 'puts Bundler.definition.locked_gems.specs.map { |spec| [spec.name, spec.version] }' \ - '.to_h.to_json }' - ] - # @sg-ignore Unresolved call to capture3 on Module - o, e, s = Open3.capture3(*cmd) - if s.success? - Solargraph.logger.debug "External bundle: #{o}" - hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {} - hash.flat_map do |name, version| - Gem::Specification.find_by_name(name, version) - rescue Gem::MissingSpecError => e - logger.info("Could not find #{name}:#{version} with find_by_name, falling back to guess") - # can happen in local filesystem references - # TODO: should this be resolve_require or find_gem? - specs = resolve_require name - logger.warn "Gem #{name} #{version} from bundle not found: #{e}" if specs.nil? - next specs - end.compact - else - Solargraph.logger.warn "Failed to load gems from bundle at #{directory}: #{e}" - end - end - end end end end diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index dadc6c93c..ce7c42a62 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -110,15 +110,15 @@ class B end end - describe '#get_method_stack' do + describe '#get_method_stack', time_limit_seconds: 240 do let(:out) { StringIO.new } let(:api_map) { Solargraph::ApiMap.load_with_cache(Dir.pwd, out) } - context 'with stdlib that has vital dependencies' do + context 'with stdlib that has vital dependencies', time_limit_seconds: 240 do let(:external_requires) { ['yaml'] } let(:method_stack) { api_map.get_method_stack('YAML', 'safe_load', scope: :class) } - it 'handles the YAML gem aliased to Psych' do + it 'handles the YAML gem aliased to Psych', time_limit_seconds: 240 do expect(method_stack).not_to be_empty end end @@ -146,7 +146,8 @@ class B describe '#cache_gem' do it 'can cache gem without a bench' do api_map = Solargraph::ApiMap.new - expect { api_map.cache_gem('rake', out: StringIO.new) }.not_to raise_error + gemspec = Gem::Specification.find_by_name('backport') + expect { api_map.cache_gem(gemspec, out: StringIO.new) }.not_to raise_error end end diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index d7f40f585..24854d271 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -33,6 +33,17 @@ end end + context 'understands rspec + rspec-mocks require pattern' do + let(:requires) do + ['rspec-mocks'] + end + + it 'generates pins from gems' do + ns_pin = doc_map.pins.find { |pin| pin.path == 'RSpec::Mocks' } + expect(ns_pin).to be_a(Solargraph::Pin::Namespace) + end + end + context 'with an invalid require' do let(:requires) do ['not_a_gem'] @@ -42,17 +53,13 @@ # These are auto-required by solargraph-rspec in case the bundle # includes these gems. In our case, it doesn't! unprovided_solargraph_rspec_requires = [ + 'rspec-rails', 'actionmailer', - 'actionpack', 'activerecord', - 'activesupport', - 'airborne', - 'rspec-core', - 'rspec-expectations', - 'rspec-mocks', - 'rspec-rails', + 'shoulda-matchers', 'rspec-sidekiq', - 'shoulda-matchers' + 'airborne', + 'activesupport' ] expect(doc_map.unresolved_requires - unprovided_solargraph_rspec_requires) .to eq(['not_a_gem']) diff --git a/spec/workspace/gemspecs_fetch_dependencies_spec.rb b/spec/workspace/gemspecs_fetch_dependencies_spec.rb index 6ead1060a..c1911ad42 100644 --- a/spec/workspace/gemspecs_fetch_dependencies_spec.rb +++ b/spec/workspace/gemspecs_fetch_dependencies_spec.rb @@ -17,7 +17,6 @@ end it 'finds a known dependency' do - pending('https://github.com/castwide/solargraph/pull/1006') expect(deps.map(&:name)).to include('backport') end end @@ -43,8 +42,6 @@ let(:gem_name) { 'my_fake_gem' } it 'gives a useful message' do - pending('https://github.com/castwide/solargraph/pull/1006') - output = capture_both { deps.map(&:name) } expect(output).to include('Please install the gem activerecord') end @@ -55,7 +52,7 @@ let(:dir_path) { File.realpath(Dir.mktmpdir).to_s } let(:gemspec) do - Gem::Specification.find_by_name(gem_name) + Bundler::LazySpecification.new(gem_name, nil, nil) end before do @@ -80,22 +77,15 @@ context 'with gem that exists in our bundle' do let(:gem_name) { 'undercover' } - it 'finds dependencies' do + it 'finds dependencies', time_limit_seconds: 120 do expect(deps.map(&:name)).to include('ast') end end context 'with gem does not exist in our bundle' do - let(:gemspec) do - Gem::Specification.new(fake_gem_name) - end + let(:gem_name) { 'activerecord' } - let(:gem_name) { 'undercover' } - - let(:fake_gem_name) { 'faaaaaake912' } - - it 'gives a useful message' do - pending('https://github.com/castwide/solargraph/pull/1006') + it 'gives a useful message', time_limit_seconds: 120 do dep_names = nil output = capture_both { dep_names = deps.map(&:name) } expect(output).to include('Please install the gem activerecord') diff --git a/spec/workspace/gemspecs_find_gem_spec.rb b/spec/workspace/gemspecs_find_gem_spec.rb index 76caddab3..35f5e7a15 100644 --- a/spec/workspace/gemspecs_find_gem_spec.rb +++ b/spec/workspace/gemspecs_find_gem_spec.rb @@ -62,7 +62,6 @@ end it 'complains' do - pending("implementation") gemspec expect(out.string).to include('install the gem checkoff ') @@ -92,7 +91,6 @@ end it 'complains' do - pending("implementation") gemspec expect(out.string).to include('install the gem checkoff:1.0.0') diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb index aa7b8d79d..d53638600 100644 --- a/spec/workspace/gemspecs_resolve_require_spec.rb +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -180,7 +180,7 @@ def configure_bundler_spec stub_value let(:require) { 'bundler/require' } it 'finds nothing' do - expect(specs).to be_nil + expect(specs).to be_empty end end end @@ -192,8 +192,6 @@ def configure_bundler_spec stub_value let(:require) { 'bundler/require' } it 'raises' do - pending('https://github.com/castwide/solargraph/pull/1006') - expect { specs }.to raise_error(Solargraph::BundleNotFoundError) end end @@ -217,9 +215,7 @@ def configure_bundler_spec stub_value let(:require) { 'bundler/gem_tasks' } - xit 'returns gems' do - pending('improved logic for require lookups') - + it 'returns gems' do expect(specs&.map(&:name)).to include('bundler') end end diff --git a/spec/yard_map/mapper_spec.rb b/spec/yard_map/mapper_spec.rb index 6b00e5c33..1dfe64ae7 100644 --- a/spec/yard_map/mapper_spec.rb +++ b/spec/yard_map/mapper_spec.rb @@ -1,4 +1,14 @@ describe Solargraph::YardMap::Mapper do + before :all do # rubocop:disable RSpec/BeforeAfterAll + @api_map = Solargraph::ApiMap.load('.') + end + + def pins_with require + doc_map = Solargraph::DocMap.new([require], @api_map.workspace, out: nil) + doc_map.cache_all!(nil) + doc_map.pins + end + it 'converts nil docstrings to empty strings' do dir = File.absolute_path(File.join('spec', 'fixtures', 'yard_map')) Dir.chdir dir do @@ -14,50 +24,33 @@ it 'marks explicit methods' do # Using rspec-expectations because it's a known dependency - rspec = Gem::Specification.find_by_name('rspec-expectations') - Solargraph::Yardoc.cache([], rspec) - Solargraph::Yardoc.load!(rspec) - pins = Solargraph::YardMap::Mapper.new(YARD::Registry.all).map - pin = pins.find { |pin| pin.path == 'RSpec::Matchers#be_truthy' } + pin = pins_with('rspec-expectations').find { |pin| pin.path == 'RSpec::Matchers#be_truthy' } + expect(pin).not_to be_nil expect(pin.explicit?).to be(true) end it 'marks correct return type from Logger.new' do # Using logger because it's a known dependency - logger = Gem::Specification.find_by_name('logger') - Solargraph::Yardoc.cache([], logger) - registry = Solargraph::Yardoc.load!(logger) - pins = Solargraph::YardMap::Mapper.new(registry).map - pins = pins.select { |pin| pin.path == 'Logger.new' } + pins = pins_with('logger').select { |pin| pin.path == 'Logger.new' } expect(pins.map(&:return_type).uniq.map(&:to_s)).to eq(['self']) end it 'marks correct return type from RuboCop::Options.new' do # Using rubocop because it's a known dependency - rubocop = Gem::Specification.find_by_name('rubocop') - Solargraph::Yardoc.cache([], rubocop) - Solargraph::Yardoc.load!(rubocop) - pins = Solargraph::YardMap::Mapper.new(YARD::Registry.all).map - pins = pins.select { |pin| pin.path == 'RuboCop::Options.new' } + pins = pins_with('rubocop').select { |pin| pin.path == 'RuboCop::Options.new' } expect(pins.map(&:return_type).uniq.map(&:to_s)).to eq(['self']) expect(pins.flat_map(&:signatures).map(&:return_type).uniq.map(&:to_s)).to eq(['self']) end it 'marks non-explicit methods' do # Using rspec-expectations because it's a known dependency - rspec = Gem::Specification.find_by_name('rspec-expectations') - Solargraph::Yardoc.load!(rspec) - pins = Solargraph::YardMap::Mapper.new(YARD::Registry.all).map - pin = pins.find { |pin| pin.path == 'RSpec::Matchers#expect' } + pin = pins_with('rspec-expectations').find { |pin| pin.path == 'RSpec::Matchers#expect' } expect(pin.explicit?).to be(false) end it 'adds superclass references' do # Asssuming the yard gem exists because it's a known dependency - gemspec = Gem::Specification.find_by_name('yard') - Solargraph::Yardoc.cache([], gemspec) - pins = Solargraph::YardMap::Mapper.new(Solargraph::Yardoc.load!(gemspec)).map - pin = pins.find do |pin| + pin = pins_with('yard').find do |pin| pin.is_a?(Solargraph::Pin::Reference::Superclass) && pin.name == 'YARD::CodeObjects::NamespaceObject' end expect(pin.closure.path).to eq('YARD::CodeObjects::ClassObject') @@ -65,21 +58,23 @@ it 'adds include references' do # Asssuming the ast gem exists because it's a known dependency - gemspec = Gem::Specification.find_by_name('ast') - Solargraph::Yardoc.cache([], gemspec) - pins = Solargraph::YardMap::Mapper.new(Solargraph::Yardoc.load!(gemspec)).map - inc= pins.find do |pin| + inc = pins_with('ast').find do |pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.name == 'AST::Processor::Mixin' && pin.closure.path == 'AST::Processor' end expect(inc).to be_a(Solargraph::Pin::Reference::Include) end + it 'adds corect gates' do + # Asssuming the ast gem exists because it's a known dependency + inc = pins_with('ast').find do |pin| + pin.is_a?(Solargraph::Pin::Namespace) && pin.name == 'Mixin' && pin.closure.path == 'AST::Processor' + end + expect(inc.gates).to eq(['AST::Processor::Mixin', 'AST::Processor', 'AST', '']) + end + it 'adds extend references' do # Asssuming the yard gem exists because it's a known dependency - gemspec = Gem::Specification.find_by_name('yard') - Solargraph::Yardoc.cache([], gemspec) - pins = Solargraph::YardMap::Mapper.new(Solargraph::Yardoc.load!(gemspec)).map - ext = pins.find do |pin| + ext = pins_with('yard').find do |pin| pin.is_a?(Solargraph::Pin::Reference::Extend) && pin.name == 'Enumerable' && pin.closure.path == 'YARD::Registry' end expect(ext).to be_a(Solargraph::Pin::Reference::Extend) From 2c7079c618d1fdc4eede72f35ac77d95d9119368 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 12:25:33 -0400 Subject: [PATCH 434/460] Handle LazySpecification issue --- lib/solargraph/doc_map.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index ac6b4cd79..009ca8941 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -23,7 +23,7 @@ class DocMap # @return [Array] def uncached_gemspecs uncached_yard_gemspecs.concat(uncached_rbs_collection_gemspecs) - .sort + .sort_by { |gemspec| "#{gemspec.name}:#{gemspec.version}" } .uniq { |gemspec| "#{gemspec.name}:#{gemspec.version}" } end From 5a982ad8cd644542409268c7f7c91e1c3c011efd Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 12:26:15 -0400 Subject: [PATCH 435/460] Drop @sg-ignore --- lib/solargraph/workspace/gemspecs.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 55dac6c6a..d1db9dc20 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -201,7 +201,6 @@ def query_external_bundle command 'ruby', '-e', "require 'bundler'; require 'json'; Dir.chdir('#{directory}') { puts begin; #{command}; end.to_json }" ] - # @sg-ignore Unresolved call to capture3 on Module o, e, s = Open3.capture3(*cmd) if s.success? Solargraph.logger.debug "External bundle: #{o}" From 57af0ebffe1f226c5ea25f44cbc7ac15859c8102 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 12:29:38 -0400 Subject: [PATCH 436/460] Handle LazySpecification issue --- lib/solargraph/doc_map.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 009ca8941..57706d212 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -105,7 +105,7 @@ def cache_rbs_collection_pins(gemspec, out) # @param out [IO, nil] output stream for logging # @return [void] def cache(gemspec, rebuild: false, out: nil) - build_yard = uncached_yard_gemspecs.include?(gemspec) || rebuild + build_yard = uncached_yard_gemspecs.map { |gs| "#{gs.name}:#{gs.version}" }.include?("#{gemspec.name}:#{gemspec.version}") || rebuild build_rbs_collection = uncached_rbs_collection_gemspecs.include?(gemspec) || rebuild if build_yard || build_rbs_collection type = [] From e1db9a858f5eb3cb330f1c612cae9348351acf7a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 12:38:27 -0400 Subject: [PATCH 437/460] Handle LazySpecification issue --- lib/solargraph/doc_map.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 57706d212..5c4db1cdf 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -106,7 +106,7 @@ def cache_rbs_collection_pins(gemspec, out) # @return [void] def cache(gemspec, rebuild: false, out: nil) build_yard = uncached_yard_gemspecs.map { |gs| "#{gs.name}:#{gs.version}" }.include?("#{gemspec.name}:#{gemspec.version}") || rebuild - build_rbs_collection = uncached_rbs_collection_gemspecs.include?(gemspec) || rebuild + build_rbs_collection = uncached_rbs_collection_gemspecs.map { |gs| "#{gs.name}:#{gs.version}" }.include?("#{gemspec.name}:#{gemspec.version}") || rebuild if build_yard || build_rbs_collection type = [] type << 'YARD' if build_yard From 719f44130ab9b07f06fa724eabd588df1a10b9df Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 13:11:20 -0400 Subject: [PATCH 438/460] Adjust specs --- spec/doc_map_spec.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 24854d271..40e53ee1c 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -59,7 +59,8 @@ 'shoulda-matchers', 'rspec-sidekiq', 'airborne', - 'activesupport' + 'activesupport', + 'actionpack' ] expect(doc_map.unresolved_requires - unprovided_solargraph_rspec_requires) .to eq(['not_a_gem']) @@ -125,7 +126,9 @@ let(:requires) { ['rspec'] } it 'collects dependencies' do - expect(doc_map.dependencies.map(&:name)).to include('rspec-core') + # we include doc_map.requires as solargraph-rspec will bring it + # in directly and we exclude it from dependencies + expect(doc_map.dependencies.map(&:name) + doc_map.requires).to include('rspec-core') end end From 81ae528a633f654b67414eaea87b56ab8b796203 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 17:49:55 -0400 Subject: [PATCH 439/460] CLI 'solargraph gems' improvements * Consolidate shell.rb's gem resolution behind the Workspace class so it can take advantage of improvements in other PRs (e.g., pulling info from the bundle directly to resolve gemspecs) * Add documentation on 'solargraph gems' * Allow request to cache core pins from 'solargraph cache' and 'solargraph gems' * Ensure stdlib is cached in 'solargraph gems' --- .rubocop_todo.yml | 1 - lib/solargraph/api_map.rb | 18 ++++------ lib/solargraph/doc_map.rb | 1 - lib/solargraph/shell.rb | 67 ++++++++++++++++++++++++------------- lib/solargraph/workspace.rb | 28 ++++++++++++++-- spec/api_map_method_spec.rb | 32 ++++++++++++++++++ spec/shell_spec.rb | 37 +++++++++++++++++--- spec/workspace_spec.rb | 37 ++++++++++++++++++++ 8 files changed, 177 insertions(+), 44 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 425e66c9a..e86360523 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -80,7 +80,6 @@ Layout/ElseAlignment: # Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines. Layout/EmptyLineBetweenDefs: Exclude: - - 'lib/solargraph/doc_map.rb' - 'lib/solargraph/language_server/message/initialize.rb' - 'lib/solargraph/pin/delegated_method.rb' diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 695de0230..8a45a3ebd 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -123,8 +123,10 @@ def catalog bench [self.class, @source_map_hash, conventions_environ, @doc_map, @unresolved_requires] end - # @return [DocMap, nil] - attr_reader :doc_map + # @return [DocMap] + def doc_map + @doc_map ||= DocMap.new([], [], Workspace.new('.')) + end # @return [::Array] def uncached_gemspecs @@ -189,15 +191,7 @@ def self.load directory # @param out [IO, nil] # @return [void] def cache_all_for_doc_map! out - @doc_map.cache_doc_map_gems!(out) - end - - # @param gemspec [Gem::Specification] - # @param rebuild [Boolean] - # @param out [IO, nil] - # @return [void] - def cache_gem(gemspec, rebuild: false, out: nil) - @doc_map.cache(gemspec, rebuild: rebuild, out: out) + doc_map.cache_doc_map_gems!(out) end class << self @@ -676,7 +670,7 @@ def resolve_method_aliases pins, visibility = [:public, :private, :protected] # @return [Workspace, nil] def workspace - @doc_map&.workspace + doc_map.workspace end # @param fq_reference_tag [String] A fully qualified whose method should be pulled in diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 9f119c94c..0691110f5 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -250,7 +250,6 @@ def only_runtime_dependencies gemspec gemspec.dependencies - gemspec.development_dependencies end - def inspect self.class.inspect end diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 735e6c290..240fac654 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -104,9 +104,8 @@ def clear # @param gem [String] # @param version [String, nil] def cache gem, version = nil - api_map = Solargraph::ApiMap.load(Dir.pwd) - spec = Gem::Specification.find_by_name(gem, version) - api_map.cache_gem(spec, rebuild: options[:rebuild], out: $stdout) + gems(gem + (version ? "=#{version}" : '')) + # ' end desc 'uncache GEM [...GEM]', "Delete specific cached gem documentation" @@ -132,28 +131,60 @@ def uncache *gems next end - spec = Gem::Specification.find_by_name(gem) + spec = workspace.find_gem(gem) workspace.uncache_gem(spec, out: $stdout) end end - desc 'gems [GEM[=VERSION]]', 'Cache documentation for installed gems' + desc 'gems [GEM[=VERSION]...] [STDLIB...] [core]', 'Cache documentation for + installed libraries' + long_desc %( This command will cache the + generated type documentation for the specified libraries. While + Solargraph will generate this on the fly when needed, it takes + time. This command will generate it in advance, which can be + useful for CI scenarios. + + With no arguments, it will cache all libraries in the current + workspace. If a gem or standard library name is specified, it + will cache that library's type documentation. + + An equals sign after a gem will allow a specific gem version + to be cached. + + The 'core' argument can be used to cache the type + documentation for the core Ruby libraries. + + If the library is already cached, it will be rebuilt if the + --rebuild option is set. + + Cached documentation is stored in #{PinCache.base_dir}, which + can be stored between CI runs. + ) option :rebuild, type: :boolean, desc: 'Rebuild existing documentation', default: false # @param names [Array] # @return [void] def gems *names - api_map = ApiMap.load('.') + # print time with ms + workspace = Solargraph::Workspace.new('.') + if names.empty? - Gem::Specification.to_a.each { |spec| do_cache spec, api_map } - STDERR.puts "Documentation cached for all #{Gem::Specification.count} gems." + workspace.cache_all_for_workspace!($stdout, rebuild: options[:rebuild]) else + $stderr.puts("Caching these gems: #{names}") names.each do |name| - spec = Gem::Specification.find_by_name(*name.split('=')) - do_cache spec, api_map - rescue Gem::MissingSpecError - warn "Gem '#{name}' not found" + if name == 'core' + PinCache.cache_core(out: $stdout) if !PinCache.core? || options[:rebuild] + next + end + + gemspec = workspace.find_gem(*name.split('=')) + if gemspec.nil? + warn "Gem '#{name}' not found" + else + workspace.cache_gem(gemspec, rebuild: options[:rebuild], out: $stdout) + end end - STDERR.puts "Documentation cached for #{names.count} gems." + $stderr.puts "Documentation cached for #{names.count} gems." end end @@ -194,7 +225,6 @@ def typecheck *files filecount += 1 probcount += problems.length end - # " } puts "Typecheck finished in #{time.real} seconds." puts "#{probcount} problem#{probcount != 1 ? 's' : ''} found#{files.length != 1 ? " in #{filecount} of #{files.length} files" : ''}." @@ -261,14 +291,5 @@ def pin_description pin desc += " (#{pin.location.filename} #{pin.location.range.start.line})" if pin.location desc end - - # @param gemspec [Gem::Specification] - # @param api_map [ApiMap] - # @return [void] - def do_cache gemspec, api_map - # @todo if the rebuild: option is passed as a positional arg, - # typecheck doesn't complain on the below line - api_map.cache_gem(gemspec, rebuild: options.rebuild, out: $stdout) - end end end diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 2697e90e0..5e0f07750 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -22,7 +22,8 @@ class Workspace attr_reader :gemnames alias source_gems gemnames - # @param directory [String] TODO: Document and test '' and '*' semantics + # @todo Remove '' and '*' special cases + # @param directory [String] # @param config [Config, nil] # @param server [Hash] def initialize directory = '', config = nil, server = {} @@ -66,7 +67,7 @@ def stdlib_dependencies stdlib_name def global_environ # empty docmap, since the result needs to work in any possible # context here - @global_environ ||= Convention.for_global(DocMap.new([], [], self)) + @global_environ ||= Convention.for_global(DocMap.new([], [], self, out: nil)) end # @param gemspec [Gem::Specification, Bundler::LazySpecification] @@ -195,6 +196,29 @@ def find_gem name, version = nil nil end + # @param out [IO, nil] output stream for logging + # @param rebuild [Boolean] whether to rebuild the pins even if they are cached + # @return [void] + def cache_all_for_workspace! out, rebuild: false + PinCache.cache_core(out: out) unless PinCache.core? + + # @type [Array] + gem_specs = Gem::Specification.to_a + # try any possible standard libraries, but be quiet about it + stdlib_specs = pin_cache.possible_stdlibs.map { |stdlib| find_gem(stdlib, out: nil) }.compact + specs = (gem_specs + stdlib_specs) + specs.each do |spec| + pin_cache.cache_gem(gemspec: spec, rebuild: rebuild, out: out) unless pin_cache.cached?(spec) + end + out&.puts "Documentation cached for all #{specs.length} gems." + + # do this after so that we prefer stdlib requires from gems, + # which are likely to be newer and have more pins + pin_cache.cache_all_stdlibs(out: out) + + out&.puts "Documentation cached for core, standard library and gems." + end + # Synchronize the workspace from the provided updater. # # @param updater [Source::Updater] diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index 9d4e4f553..b4202e557 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -133,6 +133,38 @@ class B end end + describe '#cache_all_for_doc_map!' do + it 'can cache gems without a bench' do + api_map = Solargraph::ApiMap.new + doc_map = instance_double(Solargraph::DocMap, cache_doc_map_gems!: true) + allow(Solargraph::DocMap).to receive(:new).and_return(doc_map) + api_map.cache_all_for_doc_map!($stderr) + expect(doc_map).to have_received(:cache_doc_map_gems!).with($stderr) + end + end + + describe '#cache_gem' do + it 'can cache gem without a bench' do + api_map = Solargraph::ApiMap.new + gemspec = Gem::Specification.find_by_name('backport') + expect { api_map.cache_gem(gemspec, out: StringIO.new) }.not_to raise_error + end + end + + describe '#workspace' do + it 'can get a default workspace without a bench' do + api_map = Solargraph::ApiMap.new + expect(api_map.workspace).not_to be_nil + end + end + + describe '#uncached_gemspecs' do + it 'can get uncached gemspecs workspace without a bench' do + api_map = Solargraph::ApiMap.new + expect(api_map.uncached_gemspecs).not_to be_nil + end + end + describe '#get_methods' do it 'recognizes mixin references from context' do source = Solargraph::Source.load_string(%( diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index 6e2a83074..f5eeb5734 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -10,12 +10,14 @@ before do File.open(File.join(temp_dir, 'Gemfile'), 'w') do |file| file.puts "source 'https://rubygems.org'" - file.puts "gem 'solargraph', path: #{File.expand_path('..', __dir__)}" + file.puts "gem 'solargraph', path: '#{File.expand_path('..', __dir__)}'" end output, status = Open3.capture2e("bundle install", chdir: temp_dir) raise "Failure installing bundle: #{output}" unless status.success? end + # @type cmd [Array] + # @return [String] def bundle_exec(*cmd) # run the command in the temporary directory with bundle exec output, status = Open3.capture2e("bundle exec #{cmd.join(' ')}", chdir: temp_dir) @@ -111,8 +113,6 @@ def bundle_exec(*cmd) end it 'caches core without erroring out' do - pending('https://github.com/castwide/solargraph/pull/1061') - capture_both do shell.uncache('core') end @@ -128,6 +128,35 @@ def bundle_exec(*cmd) expect(output).to include("Gem 'solargraph123' not found") end end + + context 'with mocked Workspace' do + let(:workspace) { instance_double(Solargraph::Workspace) } + let(:gemspec) { instance_double(Gem::Specification, name: 'backport') } + + before do + allow(Solargraph::Workspace).to receive(:new).and_return(workspace) + end + + it 'caches all without erroring out' do + allow(workspace).to receive(:cache_all_for_workspace!) + + _output = capture_both { shell.gems } + + expect(workspace).to have_received(:cache_all_for_workspace!) + end + + it 'caches single gem without erroring out' do + allow(workspace).to receive(:find_gem).with('backport').and_return(gemspec) + allow(workspace).to receive(:cache_gem) + + capture_both do + shell.options = { rebuild: false } + shell.gems('backport') + end + + expect(workspace).to have_received(:cache_gem).with(gemspec, out: an_instance_of(StringIO), rebuild: false) + end + end end describe 'cache' do @@ -139,8 +168,6 @@ def bundle_exec(*cmd) subject(:call) { shell.cache('nonexistentgem8675309') } it 'gives a good error message' do - pending('https://github.com/castwide/solargraph/pull/1061') - # capture stderr output expect { call }.to output(/not found/).to_stderr end diff --git a/spec/workspace_spec.rb b/spec/workspace_spec.rb index db8141249..8b5a67aa8 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -132,4 +132,41 @@ Solargraph::Workspace.new('./path', config) }.not_to raise_error end + + describe '#cache_all_for_workspace!' do + let(:pin_cache) { instance_double(Solargraph::PinCache) } + + before do + allow(Solargraph::PinCache).to receive(:cache_core) + allow(Solargraph::PinCache).to receive(:possible_stdlibs) + allow(Solargraph::PinCache).to receive(:new).and_return(pin_cache) + allow(pin_cache).to receive_messages(cache_gem: nil, possible_stdlibs: []) + allow(Solargraph::PinCache).to receive(:cache_all_stdlibs) + end + + it 'caches core pins' do + allow(Solargraph::PinCache).to receive_messages(core?: false) + allow(pin_cache).to receive_messages(cached?: true, + cache_all_stdlibs: nil) + + workspace.cache_all_for_workspace!(nil, rebuild: false) + + expect(Solargraph::PinCache).to have_received(:cache_core).with(out: nil) + end + + it 'caches gems' do + gemspec = instance_double(Gem::Specification, name: 'test_gem', version: '1.0.0') + allow(Gem::Specification).to receive(:to_a).and_return([gemspec]) + allow(pin_cache).to receive(:cached?).and_return(false) + allow(pin_cache).to receive(:cache_all_stdlibs).with(out: nil) + + allow(Solargraph::PinCache).to receive_messages(core?: true, + possible_stdlibs: []) + + workspace.cache_all_for_workspace!(nil, rebuild: false) + + expect(pin_cache).to have_received(:cache_gem).with(gemspec: gemspec, out: nil, + rebuild: false) + end + end end From 915c5aab23a3f048d1ae2a5f021be578f2121f9d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 18:03:37 -0400 Subject: [PATCH 440/460] Allow PR to build --- .github/workflows/linting.yml | 2 +- .github/workflows/plugins.yml | 2 +- .github/workflows/rspec.yml | 2 +- .github/workflows/typecheck.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index b4ef26bfe..6262dd494 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -8,7 +8,7 @@ name: Linting on: workflow_dispatch: {} pull_request: - branches: [ master ] + branches: ['*'] push: branches: - 'main' diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index b5984f3cb..871252fcc 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -9,7 +9,7 @@ on: push: branches: [master] pull_request: - branches: [master] + branches: ['*'] permissions: contents: read diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 6fe05a9b9..12399f50b 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -11,7 +11,7 @@ on: push: branches: [ master ] pull_request: - branches: [ master ] + branches: ['*'] permissions: contents: read diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 0ae8a3d8a..9f0020065 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -11,7 +11,7 @@ on: push: branches: [ master ] pull_request: - branches: [ master ] + branches: ['*'] permissions: contents: read From 9e29680b0e7d67f4c6165db6d028d3ea87f806c4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 18:11:47 -0400 Subject: [PATCH 441/460] Drop spec --- spec/api_map_method_spec.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index b4202e557..0ee0238cf 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -143,14 +143,6 @@ class B end end - describe '#cache_gem' do - it 'can cache gem without a bench' do - api_map = Solargraph::ApiMap.new - gemspec = Gem::Specification.find_by_name('backport') - expect { api_map.cache_gem(gemspec, out: StringIO.new) }.not_to raise_error - end - end - describe '#workspace' do it 'can get a default workspace without a bench' do api_map = Solargraph::ApiMap.new From 8827bf2b4ef7f61819bdedf2030721f994a61682 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 21:00:35 -0400 Subject: [PATCH 442/460] Fix merge --- lib/solargraph/workspace.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index c0244bec6..17531c67a 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -216,7 +216,7 @@ def cache_all_for_workspace! out, rebuild: false # @type [Array] gem_specs = Gem::Specification.to_a # try any possible standard libraries, but be quiet about it - stdlib_specs = pin_cache.possible_stdlibs.map { |stdlib| find_gem(stdlib, out: nil) }.compact + stdlib_specs = PinCache.possible_stdlibs.map { |stdlib| find_gem(stdlib, out: nil) }.compact specs = (gem_specs + stdlib_specs) specs.each do |spec| pin_cache.cache_gem(gemspec: spec, rebuild: rebuild, out: out) unless pin_cache.cached?(spec) @@ -225,7 +225,7 @@ def cache_all_for_workspace! out, rebuild: false # do this after so that we prefer stdlib requires from gems, # which are likely to be newer and have more pins - pin_cache.cache_all_stdlibs(out: out) + PinCache.cache_all_stdlibs(out: out) out&.puts "Documentation cached for core, standard library and gems." end From 18abf183f9dd136f0cf032397c18d18d03ce1801 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 21:02:30 -0400 Subject: [PATCH 443/460] Fix spec --- spec/shell_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index 2b8e91930..40ade9664 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -135,7 +135,6 @@ def bundle_exec(*cmd) before do allow(Solargraph::Workspace).to receive(:new).and_return(workspace) - allow(Solargraph::ApiMap).to receive(:load).with(Dir.pwd).and_return(api_map) allow(workspace).to receive(:cache_gem) end From b176302d25ab384c92f8666264456d9648ba8b9b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 21:11:16 -0400 Subject: [PATCH 444/460] Fix expectations in spec --- spec/workspace_spec.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/spec/workspace_spec.rb b/spec/workspace_spec.rb index cb5821a4d..1c2a4117f 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -139,16 +139,14 @@ before do allow(Solargraph::PinCache).to receive(:cache_core) - allow(Solargraph::PinCache).to receive(:possible_stdlibs) - allow(Solargraph::PinCache).to receive(:new).and_return(pin_cache) - allow(pin_cache).to receive_messages(cache_gem: nil, possible_stdlibs: []) + allow(Solargraph::PinCache).to receive_messages(new: pin_cache, possible_stdlibs: []) + allow(pin_cache).to receive_messages(cache_gem: nil) allow(Solargraph::PinCache).to receive(:cache_all_stdlibs) end it 'caches core pins' do allow(Solargraph::PinCache).to receive_messages(core?: false) - allow(pin_cache).to receive_messages(cached?: true, - cache_all_stdlibs: nil) + allow(pin_cache).to receive_messages(cached?: true) workspace.cache_all_for_workspace!(nil, rebuild: false) @@ -159,7 +157,6 @@ gemspec = instance_double(Gem::Specification, name: 'test_gem', version: '1.0.0') allow(Gem::Specification).to receive(:to_a).and_return([gemspec]) allow(pin_cache).to receive(:cached?).and_return(false) - allow(pin_cache).to receive(:cache_all_stdlibs).with(out: nil) allow(Solargraph::PinCache).to receive_messages(core?: true, possible_stdlibs: []) From 7b4982392773415b9e25bafddebeefedad81e61e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 07:29:09 -0400 Subject: [PATCH 445/460] Add space for better future merge --- lib/solargraph/workspace.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 5e0f07750..36e85efe8 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -196,14 +196,21 @@ def find_gem name, version = nil nil end + # @todo make this actually work against bundle instead of pulling + # all installed gemspecs - + # https://github.com/apiology/solargraph/pull/10 + # @return [Array] + def all_gemspecs_from_bundle + Gem::Specification.to_a + end + # @param out [IO, nil] output stream for logging # @param rebuild [Boolean] whether to rebuild the pins even if they are cached # @return [void] def cache_all_for_workspace! out, rebuild: false PinCache.cache_core(out: out) unless PinCache.core? - # @type [Array] - gem_specs = Gem::Specification.to_a + gem_specs = all_gemspecs_from_bundle # try any possible standard libraries, but be quiet about it stdlib_specs = pin_cache.possible_stdlibs.map { |stdlib| find_gem(stdlib, out: nil) }.compact specs = (gem_specs + stdlib_specs) From 71ac7b56c7fabdde2f5eb7bbdb7a1f54a3edf4fe Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 07:52:07 -0400 Subject: [PATCH 446/460] Add space for better future merge --- lib/solargraph/workspace.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 69ad12bae..c44bfb36d 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -170,6 +170,11 @@ def find_gem name, version = nil gemspecs.find_gem(name, version) end + # @return [Array] + def all_gemspecs_from_bundle + gemspecs.all_gemspecs_from_bundle + end + # Synchronize the workspace from the provided updater. # # @param updater [Source::Updater] From 9d7ed97bf0f80b6191afdc1d698c4c79934d53ae Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 08:08:35 -0400 Subject: [PATCH 447/460] Add space for better future merge --- lib/solargraph/workspace.rb | 7 +++++++ lib/solargraph/workspace/gemspecs.rb | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index c44bfb36d..50c9f4519 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -47,6 +47,13 @@ def config @config ||= Solargraph::Workspace::Config.new(directory) end + # @param stdlib_name [String] + # + # @return [Array] + def stdlib_dependencies stdlib_name + gemspecs.stdlib_dependencies(stdlib_name) + end + # @param out [IO, nil] output stream for logging # @param gemspec [Gem::Specification] # @return [Array] diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index d1db9dc20..c6ed33d58 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -80,6 +80,15 @@ def resolve_require require nil end + # @todo space for future expansion from other merges + # + # @param stdlib_name [String] + # + # @return [Array] + def stdlib_dependencies stdlib_name + [] + end + # @param name [String] # @param version [String, nil] # @param out [IO, nil] output stream for logging From 66870f09e727bce634ae0a72a88d6eebf7370b2a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 08:25:43 -0400 Subject: [PATCH 448/460] Add more space for future merges --- lib/solargraph/workspace/gemspecs.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index c6ed33d58..46c92ed4e 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -83,9 +83,10 @@ def resolve_require require # @todo space for future expansion from other merges # # @param stdlib_name [String] + # @param stdlib_version [String, nil] # # @return [Array] - def stdlib_dependencies stdlib_name + def stdlib_dependencies stdlib_name, stdlib_version = nil [] end @@ -119,6 +120,7 @@ def fetch_dependencies gemspec, out: $stderr gem_dep_gemspecs = only_runtime_dependencies(gemspec).each_with_object(deps_so_far) do |runtime_dep, deps| next if deps[runtime_dep.name] + # TODO Why doesn't this just delegate to find_gem? Add comment or change Solargraph.logger.info "Adding #{runtime_dep.name} dependency for #{gemspec.name}" dep = gemspecs.find { |dep| dep.name == runtime_dep.name } dep ||= Gem::Specification.find_by_name(runtime_dep.name, runtime_dep.requirement) @@ -131,7 +133,10 @@ def fetch_dependencies gemspec, out: $stderr deps[dep.name] ||= dep end - gem_dep_gemspecs.values.compact.uniq(&:name) + (gem_dep_gemspecs.values.compact + + # try stdlib as well + stdlib_dependencies(gemspec.name, gemspec.version)). + uniq(&:name) end # Returns all gemspecs directly depended on by this workspace's From 7365c35b986b1c07d4a59ab5e514a509092d212e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 10:37:12 -0400 Subject: [PATCH 449/460] Simplify code with find_gem, satisfy rubocop --- lib/solargraph/workspace/gemspecs.rb | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 46c92ed4e..9fe2c60fb 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -118,25 +118,18 @@ def fetch_dependencies gemspec, out: $stderr # @param runtime_dep [Gem::Dependency] # @param deps [Hash{String => Gem::Specification}] gem_dep_gemspecs = only_runtime_dependencies(gemspec).each_with_object(deps_so_far) do |runtime_dep, deps| - next if deps[runtime_dep.name] - - # TODO Why doesn't this just delegate to find_gem? Add comment or change - Solargraph.logger.info "Adding #{runtime_dep.name} dependency for #{gemspec.name}" - dep = gemspecs.find { |dep| dep.name == runtime_dep.name } - dep ||= Gem::Specification.find_by_name(runtime_dep.name, runtime_dep.requirement) - rescue Gem::MissingSpecError - dep = resolve_gem_ignoring_local_bundle runtime_dep.name, out: out - ensure + dep = find_gem(runtime_dep.name, runtime_dep.requirement) next unless dep fetch_dependencies(dep, out: out).each { |sub_dep| deps[sub_dep.name] ||= sub_dep } + deps[dep.name] ||= dep end (gem_dep_gemspecs.values.compact + # try stdlib as well - stdlib_dependencies(gemspec.name, gemspec.version)). - uniq(&:name) + stdlib_dependencies(gemspec.name, gemspec.version)) + .uniq(&:name) end # Returns all gemspecs directly depended on by this workspace's @@ -301,12 +294,12 @@ def only_runtime_dependencies gemspec # @todo Should this be using Gem::SpecFetcher and pull them automatically? # # @param name [String] - # @param version [String, nil] + # @param version_or_requirement [String, nil] # @param out [IO, nil] output stream for logging # # @return [Gem::Specification, nil] - def resolve_gem_ignoring_local_bundle name, version = nil, out: $stderr - Gem::Specification.find_by_name(name, version) + def resolve_gem_ignoring_local_bundle name, version_or_requirement = nil, out: $stderr + Gem::Specification.find_by_name(name, version_or_requirement) rescue Gem::MissingSpecError begin Gem::Specification.find_by_name(name) @@ -314,7 +307,7 @@ def resolve_gem_ignoring_local_bundle name, version = nil, out: $stderr stdlibmap = RbsMap::StdlibMap.new(name) unless stdlibmap.resolved? gem_desc = name - gem_desc += ":#{version}" if version + gem_desc += ":#{version_or_requirement}" if version_or_requirement out&.puts "Please install the gem #{gem_desc} in Solargraph's Ruby environment" end nil # either not here or in stdlib From ba1400cb3c9a0a1e45a05899e12fac62b62c6a5a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 11:01:48 -0400 Subject: [PATCH 450/460] Don't rely on gemspec being hashable --- lib/solargraph/doc_map.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 5c4db1cdf..95ee9daf3 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -288,11 +288,12 @@ def deserialize_stdlib_rbs_map path # @param rbs_version_cache_key [String] # @return [Array, nil] def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key - return if rbs_collection_pins_in_memory.key?([gemspec, rbs_version_cache_key]) + key = "#{gemspec.name}:#{gemspec.version}" + return if rbs_collection_pins_in_memory.key?([key, rbs_version_cache_key]) cached = PinCache.deserialize_rbs_collection_gem(gemspec, rbs_version_cache_key) if cached logger.info { "Loaded #{cached.length} pins from RBS collection cache for #{gemspec.name}:#{gemspec.version}" } unless cached.empty? - rbs_collection_pins_in_memory[[gemspec, rbs_version_cache_key]] = cached + rbs_collection_pins_in_memory[[key, rbs_version_cache_key]] = cached cached else logger.debug "No RBS collection pin cache for #{gemspec.name} #{gemspec.version}" From fa96091426d4e3e1badd4d48314c58ea737df41c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 13:11:00 -0400 Subject: [PATCH 451/460] Remove unused methods --- lib/solargraph/rbs_map/stdlib_map.rb | 16 ++++++++++++++++ lib/solargraph/workspace.rb | 7 ------- lib/solargraph/workspace/gemspecs.rb | 19 +++++-------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index b6804157f..9308e8041 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -33,6 +33,22 @@ def initialize library end end + # @return [RBS::Collection::Sources::Stdlib] + def self.source + @source ||= RBS::Collection::Sources::Stdlib.instance + end + + # @param name [String] + # @param version [String, nil] + # @return [Array String}>, nil] + def self.stdlib_dependencies name, version = nil + if source.has?(name, version) + source.dependencies_of(name, version) + else + [] + end + end + # @param library [String] # @return [StdlibMap] def self.load library diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 50c9f4519..c44bfb36d 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -47,13 +47,6 @@ def config @config ||= Solargraph::Workspace::Config.new(directory) end - # @param stdlib_name [String] - # - # @return [Array] - def stdlib_dependencies stdlib_name - gemspecs.stdlib_dependencies(stdlib_name) - end - # @param out [IO, nil] output stream for logging # @param gemspec [Gem::Specification] # @return [Array] diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 9fe2c60fb..4482f63e7 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -80,16 +80,6 @@ def resolve_require require nil end - # @todo space for future expansion from other merges - # - # @param stdlib_name [String] - # @param stdlib_version [String, nil] - # - # @return [Array] - def stdlib_dependencies stdlib_name, stdlib_version = nil - [] - end - # @param name [String] # @param version [String, nil] # @param out [IO, nil] output stream for logging @@ -126,10 +116,11 @@ def fetch_dependencies gemspec, out: $stderr deps[dep.name] ||= dep end - (gem_dep_gemspecs.values.compact + - # try stdlib as well - stdlib_dependencies(gemspec.name, gemspec.version)) - .uniq(&:name) + # RBS tracks implicit dependencies, like how the YAML standard + # library implies pulling in the psych library. + stdlib_deps = RbsMap::StdlibMap.stdlib_dependencies(gemspec.name, gemspec.version) || [] + stdlib_dep_gemspecs = stdlib_deps.map { |dep| find_gem(dep['name'], dep['version']) }.compact + (gem_dep_gemspecs.values.compact + stdlib_dep_gemspecs).uniq(&:name) end # Returns all gemspecs directly depended on by this workspace's From 383c93cdaac683866faa79c750dfb2659dabf3d4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 13:38:03 -0400 Subject: [PATCH 452/460] Drop @sg-ignores around YAML --- lib/solargraph/shell.rb | 1 - lib/solargraph/workspace.rb | 1 - lib/solargraph/workspace/config.rb | 1 - 3 files changed, 3 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 32bb3506a..97f05a5b1 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -81,7 +81,6 @@ def config(directory = '.') end end File.open(File.join(directory, '.solargraph.yml'), 'w') do |file| - # @sg-ignore Unresolved call to to_yaml file.puts conf.to_yaml end STDOUT.puts "Configuration file initialized." diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 29b39a8be..f30d163d6 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -306,7 +306,6 @@ def require_plugins def read_rbs_collection_path return unless rbs_collection_config_path - # @sg-ignore Unresolved call to load_file on Module path = YAML.load_file(rbs_collection_config_path)&.fetch('path') # make fully qualified File.expand_path(path, directory) diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 4ab153681..0b69cd528 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -149,7 +149,6 @@ def config_data def read_config config_path = '' return nil if config_path.empty? return nil unless File.file?(config_path) - # @sg-ignore Unresolved call to safe_load on Module YAML.safe_load(File.read(config_path)) end From 6a0e95d6d1023f66ded3136139f05cb577b08021 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 13:38:40 -0400 Subject: [PATCH 453/460] Enable YAML spec --- spec/api_map_method_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index fd3074b13..857ef6897 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -118,7 +118,7 @@ class B let(:external_requires) { ['yaml'] } let(:method_stack) { api_map.get_method_stack('YAML', 'safe_load', scope: :class) } - xit 'handles the YAML gem aliased to Psych', time_limit_seconds: 240 do + it 'handles the YAML gem aliased to Psych', time_limit_seconds: 240 do expect(method_stack).not_to be_empty end end From 7d8b5a36aecb8103c7cb353f132fad99dd49b7ff Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 13:53:08 -0400 Subject: [PATCH 454/460] Add back stdlib_dependencies() for use in future merge --- lib/solargraph/workspace.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index c44bfb36d..50c9f4519 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -47,6 +47,13 @@ def config @config ||= Solargraph::Workspace::Config.new(directory) end + # @param stdlib_name [String] + # + # @return [Array] + def stdlib_dependencies stdlib_name + gemspecs.stdlib_dependencies(stdlib_name) + end + # @param out [IO, nil] output stream for logging # @param gemspec [Gem::Specification] # @return [Array] From 149962da4c4610ee976b6d7c95cf0dcbbd98d621 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 13:53:26 -0400 Subject: [PATCH 455/460] Add back stdlib_dependencies() for use in future merge --- lib/solargraph/workspace/gemspecs.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 4482f63e7..c34120451 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -80,6 +80,14 @@ def resolve_require require nil end + # @param stdlib_name [String] + # + # @return [Array] + def stdlib_dependencies stdlib_name + deps = RbsMap::StdlibMap.stdlib_dependencies(stdlib_name, nil) || [] + deps.map { |dep| dep['name'] }.compact + end + # @param name [String] # @param version [String, nil] # @param out [IO, nil] output stream for logging From 91fa4c1bd5648f2d842f846e9e59240581049b66 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 14:14:22 -0400 Subject: [PATCH 456/460] Fix merge --- spec/workspace_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/workspace_spec.rb b/spec/workspace_spec.rb index 1c2a4117f..416966279 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -136,8 +136,10 @@ describe '#cache_all_for_workspace!' do let(:pin_cache) { instance_double(Solargraph::PinCache) } + let(:gemspecs) { instance_double(Solargraph::Workspace::Gemspecs) } before do + allow(Solargraph::Workspace::Gemspecs).to receive(:new).and_return(gemspecs) allow(Solargraph::PinCache).to receive(:cache_core) allow(Solargraph::PinCache).to receive_messages(new: pin_cache, possible_stdlibs: []) allow(pin_cache).to receive_messages(cache_gem: nil) @@ -146,7 +148,7 @@ it 'caches core pins' do allow(Solargraph::PinCache).to receive_messages(core?: false) - allow(pin_cache).to receive_messages(cached?: true) + allow(gemspecs).to receive(:all_gemspecs_from_bundle).and_return([]) workspace.cache_all_for_workspace!(nil, rebuild: false) @@ -155,7 +157,7 @@ it 'caches gems' do gemspec = instance_double(Gem::Specification, name: 'test_gem', version: '1.0.0') - allow(Gem::Specification).to receive(:to_a).and_return([gemspec]) + allow(gemspecs).to receive(:all_gemspecs_from_bundle).and_return([gemspec]) allow(pin_cache).to receive(:cached?).and_return(false) allow(Solargraph::PinCache).to receive_messages(core?: true, From 59906e6070b5d08f82b220b53376978763e49406 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 14:19:55 -0400 Subject: [PATCH 457/460] Fix merge --- lib/solargraph/workspace.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 9b1cf4794..5d09d91ad 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -207,14 +207,6 @@ def find_gem name, version = nil gemspecs.find_gem(name, version) end - # @todo make this actually work against bundle instead of pulling - # all installed gemspecs - - # https://github.com/apiology/solargraph/pull/10 - # @return [Array] - def all_gemspecs_from_bundle - Gem::Specification.to_a - end - # @param out [IO, nil] output stream for logging # @param rebuild [Boolean] whether to rebuild the pins even if they are cached # @return [void] From d9fa32f6bd89dceefb1eafc423a3dafaefa97eaf Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 15:54:51 -0400 Subject: [PATCH 458/460] Fix a Gem::Specification class limitation found on another branch --- lib/solargraph/doc_map.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 9f119c94c..79ba2c5d8 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -92,14 +92,16 @@ def unresolved_requires @unresolved_requires ||= required_gems_map.select { |_, gemspecs| gemspecs.nil? }.keys end - # @return [Set] + # @return [Array] # @param out [IO] def dependencies out: $stderr @dependencies ||= begin - all_deps = gemspecs.flat_map { |spec| fetch_dependencies(spec, out: out) } + all_deps = gemspecs. + flat_map { |spec| fetch_dependencies(spec, out: out) }. + uniq(&:name) existing_gems = gemspecs.map(&:name) - all_deps.reject { |gemspec| existing_gems.include? gemspec.name }.to_set + all_deps.reject { |gemspec| existing_gems.include? gemspec.name } end end From f4f1620db6319034202fc2c72afce5f9d9db2f0d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 16:00:34 -0400 Subject: [PATCH 459/460] Fix RuboCop issues --- lib/solargraph/doc_map.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 79ba2c5d8..cc3a4aed6 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -97,9 +97,9 @@ def unresolved_requires def dependencies out: $stderr @dependencies ||= begin - all_deps = gemspecs. - flat_map { |spec| fetch_dependencies(spec, out: out) }. - uniq(&:name) + all_deps = gemspecs + .flat_map { |spec| fetch_dependencies(spec, out: out) } + .uniq(&:name) existing_gems = gemspecs.map(&:name) all_deps.reject { |gemspec| existing_gems.include? gemspec.name } end From bb697e3b1a2d4c3c4899798706cd07690e347c2f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 16:34:21 -0400 Subject: [PATCH 460/460] Fix reference --- lib/solargraph/doc_map.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 9500f5385..63c06afd3 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -95,7 +95,7 @@ def dependencies out: $stderr @dependencies ||= begin all_deps = gemspecs - .flat_map { |spec| fetch_dependencies(spec, out: out) } + .flat_map { |spec| workspace.fetch_dependencies(spec, out: out) } .uniq(&:name) existing_gems = gemspecs.map(&:name) all_deps.reject { |gemspec| existing_gems.include? gemspec.name }