From 10ef2a80252eacd1c49c47c482a074ac918cd6d4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 20 Mar 2025 16:56:05 -0400 Subject: [PATCH 001/930] At strong level, don't allow method calls off of nilable types More specifically, require that all unique types of a complex type offer the same method before it can match. --- lib/solargraph/api_map.rb | 24 +++++++++++++++++----- lib/solargraph/pin/parameter.rb | 8 ++++++++ lib/solargraph/shell.rb | 6 ++++-- lib/solargraph/source/chain/call.rb | 20 +++++++++++-------- lib/solargraph/type_checker.rb | 18 ++++++++++------- lib/solargraph/type_checker/rules.rb | 4 ++++ spec/source/chain/call_spec.rb | 30 ++++++++++++++++++++++++++++ 7 files changed, 88 insertions(+), 22 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 53c4f1814..ee0939fbe 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -25,13 +25,27 @@ class ApiMap attr_reader :missing_docs # @param pins [Array] - def initialize pins: [] + # @param loose_unions [Boolean] if true, a potential type can be + # inferred if ANY of the UniqueTypes in the base chain's + # ComplexType match it. If false, every single UniqueTypes in + # the base must be ALL able to independently provide this + # type. The former is useful during completion, but the + # latter is best for typechecking at higher levels. + # + # Currently applies only to selecting potential methods to + # select in a Call link, but is likely to expand in the + # future to similar situations. + # + def initialize pins: [], loose_unions: true @source_map_hash = {} @cache = Cache.new @method_alias_stack = [] + @loose_unions = loose_unions index pins end + attr_reader :loose_unions + # @param pins [Array] # @return [self] def index pins @@ -124,8 +138,8 @@ def clip_at filename, position # # @param directory [String] # @return [ApiMap] - def self.load directory - api_map = new + def self.load directory, loose_unions: true + api_map = new(loose_unions: loose_unions) workspace = Solargraph::Workspace.new(directory) # api_map.catalog Bench.new(workspace: workspace) library = Library.new(workspace) @@ -139,8 +153,8 @@ def self.load directory # # @param directory [String] # @return [ApiMap] - def self.load_with_cache directory - api_map = load(directory) + def self.load_with_cache directory, loose_unions: true + api_map = load(directory, loose_unions: loose_unions) return api_map if api_map.uncached_gemspecs.empty? api_map.uncached_gemspecs.each do |gemspec| diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index b56aa388e..1ce8647c8 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -28,6 +28,14 @@ def restarg? decl == :restarg end + def mandatory_positional? + decl == :arg + end + + def positional? + !keyword? + end + def rest? decl == :restarg || decl == :kwrestarg end diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index bb4e9fef3..fd552bd8b 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -156,7 +156,9 @@ def reporters # @return [void] def typecheck *files directory = File.realpath(options[:directory]) - api_map = Solargraph::ApiMap.load_with_cache(directory) + level = options[:level].to_sym + rules = Solargraph::TypeChecker::Rules.new(level) + api_map = Solargraph::ApiMap.load_with_cache(directory, loose_unions: rules.loose_unions?) if files.empty? files = api_map.source_maps.map(&:filename) else @@ -165,7 +167,7 @@ def typecheck *files probcount = 0 filecount = 0 files.each do |file| - checker = TypeChecker.new(file, api_map: api_map, level: options[:level].to_sym) + checker = TypeChecker.new(file, api_map: api_map, rules: rules, level: level) problems = checker.problems next if problems.empty? problems.sort! { |a, b| a.location.range.start.line <=> b.location.range.start.line } diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 3c2d4ed73..e901fb957 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -36,9 +36,16 @@ def resolve api_map, name_pin, locals [] end return inferred_pins(found, api_map, name_pin.context, locals) unless found.empty? - # @param [ComplexType::UniqueType] - pins = name_pin.binder.each_unique_type.flat_map do |context| - api_map.get_method_stack(context.namespace == '' ? '' : context.tag, word, scope: context.scope) + if api_map.loose_unions + # fetch methods which ANY of the potential context types provide + pins = name_pin.binder.each_unique_type.flat_map do |context| + api_map.get_method_stack(context.namespace == '' ? '' : context.to_s, word, scope: context.scope) + end + else + # grab pins which are provided by every potential context type + pins = name_pin.binder.each_unique_type.map do |context| + api_map.get_method_stack(context.namespace == '' ? '' : context.to_s, word, scope: context.scope) + end.reduce(:&) end return [] if pins.empty? inferred_pins(pins, api_map, name_pin.context, locals) @@ -200,12 +207,9 @@ def extra_return_type docstring, context # @param signature [Pin::Signature] # @return [Boolean] def arity_matches? arguments, signature - parameters = signature.parameters - argcount = arguments.length - parcount = parameters.length - parcount -= 1 if !parameters.empty? && parameters.last.block? return false if signature.block? && !with_block? - return false if argcount < parcount && !(argcount == parcount - 1 && parameters.last.restarg?) + mandatory_positional_param_count = signature.parameters.count(&:mandatory_positional?) + return false if arguments.count < mandatory_positional_param_count true end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 03644f7f0..9f4303f87 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -24,11 +24,13 @@ class TypeChecker # @param filename [String] # @param api_map [ApiMap, nil] # @param level [Symbol] - def initialize filename, api_map: nil, level: :normal + def initialize filename, api_map: nil, level: :normal, rules: Rules.new(level) @filename = filename # @todo Smarter directory resolution - @api_map = api_map || Solargraph::ApiMap.load(File.dirname(filename)) - @rules = Rules.new(level) + @rules = rules + @api_map = api_map || Solargraph::ApiMap.load(File.dirname(filename), + loose_unions: rules.loose_unions?) + @marked_ranges = [] end @@ -55,9 +57,10 @@ class << self # @return [self] def load filename, level = :normal source = Solargraph::Source.load(filename) - api_map = Solargraph::ApiMap.new + rules = Rules.new(level) + api_map = Solargraph::ApiMap.new(loose_unions: rules.loose_unions?) api_map.map(source) - new(filename, api_map: api_map, level: level) + new(filename, api_map: api_map, level: level, rules: rules) end # @param code [String] @@ -66,9 +69,10 @@ def load filename, level = :normal # @return [self] def load_string code, filename = nil, level = :normal source = Solargraph::Source.load_string(code, filename) - api_map = Solargraph::ApiMap.new + rules = Rules.new(level) + api_map = Solargraph::ApiMap.new(loose_unions: rules.loose_unions?) api_map.map(source) - new(filename, api_map: api_map, level: level) + new(filename, api_map: api_map, level: level, rules: rules) end end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 84eb369bb..76aa7b8dd 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -49,6 +49,10 @@ def must_tag_or_infer? rank > LEVELS[:typed] end + def loose_unions? + rank < LEVELS[:strong] + end + def validate_tags? rank > LEVELS[:normal] end diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index 74b5139f6..a4dbde0fb 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -307,4 +307,34 @@ def baz # @todo It would be more accurate to return `Enumerator>` here expect(type.tag).to eq('Enumerator>') end + + it 'allows calls off of nilable objects by default' do + source = Solargraph::Source.load_string(%( + # @type [String, nil] + f = foo + a = f.upcase + a + ), 'test.rb') + api_map = Solargraph::ApiMap.new + api_map.map source + + chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(4, 6)) + type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) + expect(type.tag).to eq('String') + end + + it 'denies calls off of nilable objects when loose union mode is off' do + source = Solargraph::Source.load_string(%( + # @type [String, nil] + f = foo + a = f.upcase + a + ), 'test.rb') + api_map = Solargraph::ApiMap.new(loose_unions: false) + api_map.map source + + chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(4, 6)) + type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) + expect(type.tag).to eq('undefined') + end end From 87c7e4f6b8d55b241742d0430767036f2864f3c5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 18 Apr 2025 10:06:04 -0400 Subject: [PATCH 002/930] Allow log level to be overridden per file Useful for debug-level logging being turned on selectively at dev time --- lib/solargraph/logging.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/logging.rb b/lib/solargraph/logging.rb index 4dce90f77..e3173e78c 100644 --- a/lib/solargraph/logging.rb +++ b/lib/solargraph/logging.rb @@ -16,12 +16,29 @@ module Logging @@logger.formatter = proc do |severity, datetime, progname, msg| "[#{severity}] #{msg}\n" end + @@dev_null_logger = Logger.new('/dev/null') + module_function + # override this in your class to temporarily set a custom + # filtering log level for the class (e.g., suppress any debug + # message by setting it to :info even if it is set elsewhere, or + # show existing debug messages by setting to :debug). @return + # [Symbol] + def log_level + @@logger.level + end + # @return [Logger] def logger - @@logger + @logger ||= if log_level == @@logger.level + @@logger + else + logger = Logger.new(STDERR, log_level) + logger.formatter = @@logger.formatter + logger + end end end end From 67dab545260166f904d643f033b92ff40ab2f8db Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 20 Apr 2025 11:17:45 -0400 Subject: [PATCH 003/930] Fix numeric vs symbol logic --- lib/solargraph/logging.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/logging.rb b/lib/solargraph/logging.rb index e3173e78c..9640be39a 100644 --- a/lib/solargraph/logging.rb +++ b/lib/solargraph/logging.rb @@ -27,15 +27,16 @@ module Logging # show existing debug messages by setting to :debug). @return # [Symbol] def log_level - @@logger.level + :warn end # @return [Logger] def logger - @logger ||= if log_level == @@logger.level + @logger ||= if LOG_LEVELS[log_level.to_s] == @@logger.level @@logger else - logger = Logger.new(STDERR, log_level) + new_log_level = LOG_LEVELS[log_level.to_s] + logger = Logger.new(STDERR, level: new_log_level) logger.formatter = @@logger.formatter logger end From eb49ddbf1279a9be25c98c062491c5d78d6bfa1c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 21 Apr 2025 07:20:07 -0400 Subject: [PATCH 004/930] Avoid marshaling issues --- lib/solargraph/logging.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/logging.rb b/lib/solargraph/logging.rb index 9640be39a..7c7531aea 100644 --- a/lib/solargraph/logging.rb +++ b/lib/solargraph/logging.rb @@ -32,14 +32,14 @@ def log_level # @return [Logger] def logger - @logger ||= if LOG_LEVELS[log_level.to_s] == @@logger.level - @@logger - else - new_log_level = LOG_LEVELS[log_level.to_s] - logger = Logger.new(STDERR, level: new_log_level) - logger.formatter = @@logger.formatter - logger - end + if LOG_LEVELS[log_level.to_s] == DEFAULT_LOG_LEVEL + @@logger + else + new_log_level = LOG_LEVELS[log_level.to_s] + logger = Logger.new(STDERR, level: new_log_level) + logger.formatter = @@logger.formatter + logger + end end end end From df1334b7ff02bfaa637ba2fe180c3a8ea966f6da Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 6 May 2025 12:39:50 -0400 Subject: [PATCH 005/930] 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 16b4a0d2700d293b4dc23bd4631f80b13daa9190 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 22 May 2025 14:20:29 -0400 Subject: [PATCH 006/930] Add assertions around method aliases --- lib/solargraph.rb | 19 +++++++++++++++++++ lib/solargraph/api_map.rb | 10 ++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 352b0eaad..8834fc3fb 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -52,6 +52,25 @@ class InvalidRubocopVersionError < RuntimeError; end dir = File.dirname(__FILE__) VIEWS_PATH = File.join(dir, 'solargraph', 'views') + # @param type [Symbol] Type of assert. Not used yet, but may be + # used in the future to allow configurable asserts mixes for + # different situations. + def self.asserts_on?(type) + if ENV['SOLARGRAPH_ASSERTS'].nil? || ENV['SOLARGRAPH_ASSERTS'].empty? + false + elsif ENV['SOLARGRAPH_ASSERTS'] == 'on' + true + else + logger.warn "Unrecognized SOLARGRAPH_ASSERTS value: #{ENV['SOLARGRAPH_ASSERTS']}" + false + end + end + + def self.assert_or_log(type, msg = nil, &block) + raise (msg || block.call) if asserts_on?(type) && ![:combine_with_visibility].include?(type) + logger.info msg, &block + end + # A convenience method for Solargraph::Logging.logger. # # @return [Logger] diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 9443e8529..b0225305e 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -808,7 +808,10 @@ def prefer_non_nil_variables pins def resolve_method_aliases pins, visibility = [:public, :private, :protected] pins.map do |pin| resolved = resolve_method_alias(pin) - next pin if resolved.respond_to?(:visibility) && !visibility.include?(resolved.visibility) + if resolved.respond_to?(:visibility) && !visibility.include?(resolved.visibility) + Solargraph.assert_or_log(:alias_visibility) { "Rejecting alias - visibility of target is #{resolved.visibility}, looking for visibility #{visibility}" } + next pin + end resolved end.compact end @@ -821,7 +824,10 @@ def resolve_method_alias pin @method_alias_stack.push pin.path origin = get_method_stack(pin.full_context.tag, pin.original, scope: pin.scope).first @method_alias_stack.pop - return nil if origin.nil? + if origin.nil? + Solargraph.assert_or_log(:alias_target_missing) { "Rejecting alias - target is missing = #{pin.inspect}" } + return nil + end args = { location: pin.location, closure: pin.closure, From 5bc55d306911fb2f40e9b56699264dc76aec1d7e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 1 Jun 2025 19:31:10 -0400 Subject: [PATCH 007/930] Fix merge --- lib/solargraph/source/chain/call.rb | 2 +- spec/source/chain/call_spec.rb | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 3e975c9a1..18278e0fe 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -58,7 +58,7 @@ def resolve api_map, name_pin, locals return inferred_pins(found, api_map, name_pin, locals) unless found.empty? if api_map.loose_unions # fetch methods which ANY of the potential context types provide - pins = name_pin.binder.each_unique_type.map do |context| + pins = name_pin.binder.each_unique_type.flat_map do |context| ns_tag = context.namespace == '' ? '' : context.namespace_type.tag stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) [stack.first].compact diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index 32c5a172d..ab22eca44 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -657,6 +657,5 @@ def bl clip = api_map.clip_at('test.rb', [3, 8]) expect(clip.infer.rooted_tags).to eq('::String') ->>>>>>> origin/master end end From 5bb5d418d0aa04c4716ad6b679220d90508523b4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 9 Jul 2025 15:33:52 -0400 Subject: [PATCH 008/930] 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 65e3c0b4309271c6a962dff123f1d77632f93e36 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 9 Jul 2025 16:00:55 -0400 Subject: [PATCH 009/930] Suppress some known problematic aliases --- lib/solargraph.rb | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 2b975a66c..4a674877c 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -54,19 +54,28 @@ class InvalidRubocopVersionError < RuntimeError; end VIEWS_PATH = File.join(dir, 'solargraph', 'views') # @param type [Symbol] Type of assert. - def self.asserts_on?(type) - if ENV['SOLARGRAPH_ASSERTS'].nil? || ENV['SOLARGRAPH_ASSERTS'].empty? - false - elsif ENV['SOLARGRAPH_ASSERTS'] == 'on' - true - else - logger.warn "Unrecognized SOLARGRAPH_ASSERTS value: #{ENV['SOLARGRAPH_ASSERTS']}" - false - end + def self.asserts_on? + @asserts_on ||= if ENV['SOLARGRAPH_ASSERTS'].nil? || ENV['SOLARGRAPH_ASSERTS'].empty? + false + elsif ENV['SOLARGRAPH_ASSERTS'] == 'on' + true + else + logger.warn "Unrecognized SOLARGRAPH_ASSERTS value: #{ENV['SOLARGRAPH_ASSERTS']}" + false + end end 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? + msg ||= block.call + + # not ready for prime time + return if [:combine_with_visibility].include?(type) + # conditional aliases to handle compatibility corner cases + return if type == :alias_target_missing && msg.include?('highline/compatibility.rb') + return if type == :alias_target_missing && msg.include?('lib/json/add/date.rb') + raise msg + end logger.info msg, &block end From 336305f1d4b5eb88524dc3a4a85f445ab22ad03f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 9 Jul 2025 16:03:01 -0400 Subject: [PATCH 010/930] Drop unintended change --- lib/solargraph.rb | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 4a674877c..8295945cd 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -53,16 +53,15 @@ class InvalidRubocopVersionError < RuntimeError; end dir = File.dirname(__FILE__) VIEWS_PATH = File.join(dir, 'solargraph', 'views') - # @param type [Symbol] Type of assert. def self.asserts_on? - @asserts_on ||= if ENV['SOLARGRAPH_ASSERTS'].nil? || ENV['SOLARGRAPH_ASSERTS'].empty? - false - elsif ENV['SOLARGRAPH_ASSERTS'] == 'on' - true - else - logger.warn "Unrecognized SOLARGRAPH_ASSERTS value: #{ENV['SOLARGRAPH_ASSERTS']}" - false - end + if ENV['SOLARGRAPH_ASSERTS'].nil? || ENV['SOLARGRAPH_ASSERTS'].empty? + false + elsif ENV['SOLARGRAPH_ASSERTS'] == 'on' + true + else + logger.warn "Unrecognized SOLARGRAPH_ASSERTS value: #{ENV['SOLARGRAPH_ASSERTS']}" + false + end end def self.assert_or_log(type, msg = nil, &block) From c7079d50c3aad7b69a83496a2a25f3da1a7e6e8c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 9 Jul 2025 16:06:03 -0400 Subject: [PATCH 011/930] Update spec --- spec/pin/local_variable_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/pin/local_variable_spec.rb b/spec/pin/local_variable_spec.rb index 88075efb9..369a58bc4 100644 --- a/spec/pin/local_variable_spec.rb +++ b/spec/pin/local_variable_spec.rb @@ -46,7 +46,7 @@ class Bar # set env variable 'FOO' to 'true' in block with_env_var('SOLARGRAPH_ASSERTS', 'on') do - expect(Solargraph.asserts_on?(:combine_with_closure_name)).to be true + expect(Solargraph.asserts_on?).to be true expect { pin1.combine_with(pin2) }.to raise_error(RuntimeError, /Inconsistent :closure name/) end end From 5afc7a31c04f4d93704dc237db3d6cd5cc18726c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 10 Jul 2025 16:03:31 -0400 Subject: [PATCH 012/930] Drop unneeded code --- lib/solargraph/logging.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/solargraph/logging.rb b/lib/solargraph/logging.rb index 8ff297077..a0dbff3bb 100644 --- a/lib/solargraph/logging.rb +++ b/lib/solargraph/logging.rb @@ -17,8 +17,6 @@ module Logging @@logger.formatter = proc do |severity, datetime, progname, msg| "[#{severity}] #{msg}\n" end - @@dev_null_logger = Logger.new('/dev/null') - module_function From 404e686fb331835dfd212d4407f62d77a1578269 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 10 Jul 2025 16:21:18 -0400 Subject: [PATCH 013/930] Fix return tag --- lib/solargraph/logging.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/logging.rb b/lib/solargraph/logging.rb index a0dbff3bb..35473f6df 100644 --- a/lib/solargraph/logging.rb +++ b/lib/solargraph/logging.rb @@ -23,8 +23,9 @@ module Logging # override this in your class to temporarily set a custom # filtering log level for the class (e.g., suppress any debug # message by setting it to :info even if it is set elsewhere, or - # show existing debug messages by setting to :debug). @return - # [Symbol] + # show existing debug messages by setting to :debug). + # + # @return [Symbol] def log_level :warn end From 9a2db0c0c053b72fa4bf7da239b9d1dc20b08242 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 11 Jul 2025 17:22:07 -0400 Subject: [PATCH 014/930] Fix type issues --- lib/solargraph.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 8295945cd..f5348ded6 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -66,8 +66,11 @@ def self.asserts_on? def self.assert_or_log(type, msg = nil, &block) if asserts_on? + # @type [String, nil] msg ||= block.call + raise "No message given for #{type.inspect}" if msg.nil? + # not ready for prime time return if [:combine_with_visibility].include?(type) # conditional aliases to handle compatibility corner cases From b4e09642932a6c765531e4c2d596c748bded417c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 12 Jul 2025 10:58:39 -0400 Subject: [PATCH 015/930] 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 016/930] 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 017/930] 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 ca88ad8c43ef25ed5323f90de6a9c3b3eab71d8b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 13 Jul 2025 13:25:22 -0400 Subject: [PATCH 018/930] More logging --- 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 34ad11d1b..0c77ab06a 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -894,7 +894,7 @@ def resolve_method_alias pin origin = get_method_stack(pin.full_context.tag, pin.original, scope: pin.scope, preserve_generics: true).first @method_alias_stack.pop if origin.nil? - Solargraph.assert_or_log(:alias_target_missing) { "Rejecting alias - target is missing = #{pin.inspect}" } + Solargraph.assert_or_log(:alias_target_missing) { "Rejecting alias - target is missing while looking for #{pin.full_context.tag} #{pin.original}() in #{pin.scope} scope = #{pin.inspect}" } return nil end args = { From da58721a8d80ec78416a037bf3133d832f5f001f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 13 Jul 2025 13:25:45 -0400 Subject: [PATCH 019/930] Improve RBS definition for aliases --- lib/solargraph/pin/method_alias.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/solargraph/pin/method_alias.rb b/lib/solargraph/pin/method_alias.rb index 8636169a8..5428ec46e 100644 --- a/lib/solargraph/pin/method_alias.rb +++ b/lib/solargraph/pin/method_alias.rb @@ -23,6 +23,14 @@ def visibility :public end + def to_rbs + if scope == :class + rbs = "alias self.#{name} self.#{original}" + else + rbs = "alias #{name} #{original}" + end + end + def path @path ||= namespace + (scope == :instance ? '#' : '.') + name end From 6eccf31316c9692d8a22d7640bf14a0ac210a360 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 13 Jul 2025 13:26:01 -0400 Subject: [PATCH 020/930] Avoid unnecessary alias assertions while combining pins --- lib/solargraph/gem_pins.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index d24ace8e4..08ac99da7 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -54,9 +54,10 @@ def self.combine_method_pins(*pins) # @return [Array] def self.combine(yard_pins, rbs_map) in_yard = Set.new - rbs_api_map = Solargraph::ApiMap.new(pins: rbs_map.pins) + rbs_store = Solargraph::ApiMap::Store.new(rbs_pins) combined = yard_pins.map do |yard_pin| - next yard_pin unless yard_pin.class == Pin::Method + rbs_pin = rbs_store.get_path_pins(yard_pin.path).filter { |pin| pin.is_a? Pin::Method }.first + next yard_pin unless rbs_pin && yard_pin.class == Pin::Method in_yard.add yard_pin.path From e3ecdc93f9e5d2b03255e0c39d32ae21e846965f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 13 Jul 2025 13:28:01 -0400 Subject: [PATCH 021/930] Apply suggestion from @apiology --- lib/solargraph/gem_pins.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index 3ee6c30b3..bc4ec9666 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -52,7 +52,7 @@ def self.combine(yard_pins, rbs_pins) in_yard = Set.new rbs_store = Solargraph::ApiMap::Store.new(rbs_pins) combined = yard_pins.map do |yard_pin| - in_yard.add yard_pin.path + in_yard.add yard_pin.path rbs_pin = rbs_store.get_path_pins(yard_pin.path).filter { |pin| pin.is_a? Pin::Method }.first next yard_pin unless rbs_pin && yard_pin.class == Pin::Method From 91defa6f65cf55f9052ca434cd0b0261c48a761e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 13 Jul 2025 14:28:12 -0400 Subject: [PATCH 022/930] Fix RuboCop complaint --- lib/solargraph/pin/method_alias.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/pin/method_alias.rb b/lib/solargraph/pin/method_alias.rb index 5428ec46e..8cc656d23 100644 --- a/lib/solargraph/pin/method_alias.rb +++ b/lib/solargraph/pin/method_alias.rb @@ -25,9 +25,9 @@ def visibility def to_rbs if scope == :class - rbs = "alias self.#{name} self.#{original}" + "alias self.#{name} self.#{original}" else - rbs = "alias #{name} #{original}" + "alias #{name} #{original}" end end From 2be3a790e50c11cf812bf00d60b34bff3297a7c7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 13 Jul 2025 14:49:03 -0400 Subject: [PATCH 023/930] Clarify logic in yard/rbs merging --- lib/solargraph/gem_pins.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index bc4ec9666..9f965f6f8 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -30,6 +30,8 @@ def self.combine_method_pins_by_path(pins) by_path.values + alias_pins end + # @param pins [Pin::Method] + # @return [Pin::Base, nil] def self.combine_method_pins(*pins) out = pins.reduce(nil) do |memo, pin| next pin if memo.nil? @@ -54,13 +56,21 @@ def self.combine(yard_pins, rbs_pins) combined = yard_pins.map do |yard_pin| in_yard.add yard_pin.path rbs_pin = rbs_store.get_path_pins(yard_pin.path).filter { |pin| pin.is_a? Pin::Method }.first - next yard_pin unless rbs_pin && yard_pin.class == Pin::Method + + next yard_pin unless rbs_pin && yard_pin.is_a?(Pin::Method) unless rbs_pin logger.debug { "GemPins.combine: No rbs pin for #{yard_pin.path} - using YARD's '#{yard_pin.inspect} (return_type=#{yard_pin.return_type}; signatures=#{yard_pin.signatures})" } next yard_pin end + # at this point both yard_pins and rbs_pins are methods or + # method aliases. if not plain methods, prefer the YARD one + next yard_pin if rbs_pin.class != Pin::Method + + next rbs_pin if yard_pin.class != Pin::Method + + # both are method pins 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}" } out From bf612959ec8b43b888dac18b4ee6823af5e6ffc5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 14 Jul 2025 07:27:58 -0400 Subject: [PATCH 024/930] 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 025/930] 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 026/930] 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 027/930] 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 028/930] 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 029/930] 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 030/930] 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 031/930] 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 032/930] 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 033/930] 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 034/930] 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 035/930] 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 036/930] 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 037/930] 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 038/930] 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 039/930] 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 040/930] 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 041/930] 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 042/930] 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 043/930] 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 044/930] 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 045/930] 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 046/930] 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 047/930] 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 1d0b23d13f20646992a4b17a44c492826f433cab Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 17:53:02 -0400 Subject: [PATCH 048/930] 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 dfe2d10d0528562de45abc0c923f47fc2e2fb17f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 21:14:28 -0400 Subject: [PATCH 049/930] 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 050/930] 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 ec8eea5e19472d95824fabc6d706e469a9495405 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 22 Jul 2025 22:01:54 -0400 Subject: [PATCH 051/930] 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 dae5c53861eeac18db90b1edfe71c22183de2546 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 23 Jul 2025 07:08:22 -0400 Subject: [PATCH 052/930] 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 9d99e151aac200dbbf3c432b5f11e61009dd2870 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 23 Jul 2025 07:13:52 -0400 Subject: [PATCH 053/930] 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 054/930] 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 055/930] 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 fdd3810f34eeaa81e4bbac956d9e8372da1c5c6c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 3 Aug 2025 21:07:30 -0400 Subject: [PATCH 056/930] 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 057/930] 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 058/930] 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 059/930] 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 a30d7904818b16a1a88eafbe9977b9e8598f534d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 3 Aug 2025 21:53:24 -0400 Subject: [PATCH 060/930] 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 061/930] 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 062/930] 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 063/930] 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 064/930] 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 065/930] 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 066/930] 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 067/930] 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 068/930] 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 069/930] 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 070/930] 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 071/930] 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 072/930] 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 073/930] 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 074/930] 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 075/930] 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 076/930] 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 077/930] 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 078/930] 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 a33381bddc82dfba278193f0b31b27dc988dd89c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 11:50:00 -0400 Subject: [PATCH 079/930] Linting fixes --- lib/solargraph/api_map.rb | 14 -------------- lib/solargraph/gem_pins.rb | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index a49ac0a0d..c79c30f60 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -920,20 +920,6 @@ def prefer_non_nil_variables pins result + nil_pins end - # @param pins [Enumerable] - # @param visibility [Enumerable] - # @return [Array] - def resolve_method_aliases pins, visibility = [:public, :private, :protected] - pins.map do |pin| - resolved = resolve_method_alias(pin) - if resolved.respond_to?(:visibility) && !visibility.include?(resolved.visibility) - Solargraph.assert_or_log(:alias_visibility) { "Rejecting alias - visibility of target is #{resolved.visibility}, looking for visibility #{visibility}" } - next pin - end - resolved - end.compact - end - # @param pin [Pin::MethodAlias, Pin::Base] # @return [Pin::Method] def resolve_method_alias pin diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index 4bfc1a6d5..fc86a0d92 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -49,7 +49,7 @@ 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 From 5caa11e79c49f9847996dfa597d6b232b7250a5a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 12:17:58 -0400 Subject: [PATCH 080/930] Ratchet rubocop todo --- .rubocop_todo.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d8283c5c6..57b773d40 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -571,7 +571,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' @@ -639,7 +638,6 @@ Lint/UnusedBlockArgument: # 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' @@ -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). @@ -2653,7 +2643,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 8ad7bddc5d0b17aa128e4f9b0a70b387cbbc7d38 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 12:27:17 -0400 Subject: [PATCH 081/930] 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 082/930] 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 c2aac2b6c41411d4004538514adfebf7cb96f80f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 13:29:51 -0400 Subject: [PATCH 083/930] Fix undercover issue --- lib/solargraph/api_map.rb | 2 ++ spec/pin/method_alias_spec.rb | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 spec/pin/method_alias_spec.rb diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index c79c30f60..23f10b116 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -929,8 +929,10 @@ def resolve_method_alias pin origin = get_method_stack(pin.full_context.tag, pin.original, scope: pin.scope, preserve_generics: true).first @method_alias_stack.pop if origin.nil? + # :nocov: Solargraph.assert_or_log(:alias_target_missing) { "Rejecting alias - target is missing while looking for #{pin.full_context.tag} #{pin.original}() in #{pin.scope} scope = #{pin.inspect}" } return nil + # :nocov: end args = { location: pin.location, diff --git a/spec/pin/method_alias_spec.rb b/spec/pin/method_alias_spec.rb new file mode 100644 index 000000000..d3b408273 --- /dev/null +++ b/spec/pin/method_alias_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +describe Solargraph::Pin::MethodAlias do + describe '#to_rbs' do + it 'generates RBS from simple alias' do + method_alias = described_class.new(name: 'name', original: 'original_name') + + expect(method_alias.to_rbs).to eq('alias name original_name') + end + + it 'generates RBS from static alias' do + method_alias = described_class.new(name: 'name', original: 'original_name', scope: :class) + + expect(method_alias.to_rbs).to eq('alias self.name self.original_name') + end + end +end From a45bc0ac4dad40a7747e737d7c14ccade748b3e3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 14:05:31 -0400 Subject: [PATCH 084/930] Fix linting issues --- .rubocop.yml | 3 ++ .rubocop_todo.yml | 97 ----------------------------------------------- 2 files changed, 3 insertions(+), 97 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index a73324db2..185cd8955 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -39,6 +39,9 @@ Metrics/ParameterLists: Style/ClassVars: Enabled: false +RSpec/SpecFilePathFormat: + Enabled: false + # # Set a relaxed standard on harder-to-address item for ease of # contribution - if you are good at refactoring, we welcome PRs to diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 57b773d40..06bc41596 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1278,103 +1278,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' From 6383ad17be5fc5b485a7d97fa2dedf2c0e6425d1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 14:12:12 -0400 Subject: [PATCH 085/930] 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 f5e17c3dc4b47ac2035c8e10325302aeb5f0394b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 14:42:09 -0400 Subject: [PATCH 086/930] 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 10eca96027d7b57284620bfbf99931e7227f1b90 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 16:19:02 -0400 Subject: [PATCH 087/930] 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 964b2d8d7a2802ed57f96a987105f3bdd8ebe72f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 24 Aug 2025 15:50:58 -0400 Subject: [PATCH 088/930] Fix merge --- lib/solargraph/api_map.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 714ca20c8..54f2ab449 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -1028,11 +1028,11 @@ def resolve_method_alias(alias_pin) end if original.nil? # :nocov: - Solargraph.assert_or_log(:alias_target_missing) { "Rejecting alias - target is missing while looking for #{pin.full_context.tag} #{pin.original}() in #{pin.scope} scope = #{pin.inspect}" } + Solargraph.assert_or_log(:alias_target_missing) { "Rejecting alias - target is missing while looking for #{alias_pin.full_context.tag} #{alias_pin.original} in #{alias_pin.scope} scope = #{alias_pin.inspect}" } return nil # :nocov: end - + # @sg-ignore ignore `received nil` for original create_resolved_alias_pin(alias_pin, original) end From 9b2ddf6b38865d04266fd093b146f26f72fecf50 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 24 Aug 2025 15:52:07 -0400 Subject: [PATCH 089/930] Bring in new RuboCop version to todo file --- .rubocop_todo.yml | 132 ++++------------------------------------------ 1 file changed, 11 insertions(+), 121 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5f20e3d56..cfa4dd37e 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.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 # 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: @@ -659,13 +652,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' @@ -696,7 +688,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' @@ -1008,7 +999,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' @@ -1108,7 +1098,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' @@ -1266,109 +1255,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' @@ -1702,7 +1592,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: @@ -2233,7 +2123,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 c5acdfc8aec9677af0513598b602556758070f50 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 24 Aug 2025 16:50:57 -0400 Subject: [PATCH 090/930] Resolve aliases to .new pins --- lib/solargraph/api_map.rb | 10 +++++++--- spec/api_map/api_map_method_spec.rb | 7 +++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 54f2ab449..962d8e8cd 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -1005,13 +1005,17 @@ 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.namespace) 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}" + ancestor_method_path = if alias_pin.original == 'new' && alias_pin.scope == :class + "#{ancestor_fqns}#initialize" + else + "#{ancestor_fqns}#{alias_pin.scope == :instance ? '#' : '.'}#{alias_pin.original}" + end # Search for the original method in the ancestor original = store.get_path_pins(ancestor_method_path).find do |candidate_pin| @@ -1021,7 +1025,7 @@ def resolve_method_alias(alias_pin) break resolved if resolved end - candidate_pin.is_a?(Pin::Method) && candidate_pin.scope == alias_pin.scope + candidate_pin.is_a?(Pin::Method) end break if original diff --git a/spec/api_map/api_map_method_spec.rb b/spec/api_map/api_map_method_spec.rb index a3adc9b94..c599768d8 100644 --- a/spec/api_map/api_map_method_spec.rb +++ b/spec/api_map/api_map_method_spec.rb @@ -11,6 +11,13 @@ api_map.catalog bench end + describe '#resolve_method_alias' do + it 'resolves the IO.for_fd alias to IO.new' do + stack = api_map.get_method_stack('IO', 'for_fd', scope: :class) + expect(stack).not_to be_empty + end + end + describe '#qualify' do let(:external_requires) { ['yaml'] } From 14b794b6b7b6026d81300e9e4b85545f0d01cfd2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 24 Aug 2025 17:00:54 -0400 Subject: [PATCH 091/930] Improve spec --- spec/api_map/api_map_method_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/api_map/api_map_method_spec.rb b/spec/api_map/api_map_method_spec.rb index c599768d8..0ac32d608 100644 --- a/spec/api_map/api_map_method_spec.rb +++ b/spec/api_map/api_map_method_spec.rb @@ -14,7 +14,7 @@ describe '#resolve_method_alias' do it 'resolves the IO.for_fd alias to IO.new' do stack = api_map.get_method_stack('IO', 'for_fd', scope: :class) - expect(stack).not_to be_empty + expect(stack.map(&:class).uniq).to eq([Solargraph::Pin::Method]) end end From 5799b5213e8d7d6d2f3d680d7010367a1fc03956 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 09:00:00 -0400 Subject: [PATCH 092/930] Lint fixes --- lib/solargraph/api_map.rb | 7 ++++--- spec/source/chain/call_spec.rb | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 7ed8636e4..205ae4c33 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -62,7 +62,6 @@ def hash equality_fields.hash end - attr_reader :loose_unions def to_s @@ -192,6 +191,7 @@ def clip_at filename, position # Create an ApiMap with a workspace in the specified directory. # # @param directory [String] + # @param loose_unions [Boolean] See #initialize # @return [ApiMap] def self.load directory, loose_unions: true api_map = new(loose_unions: loose_unions) @@ -226,9 +226,10 @@ class << self # # # @param directory [String] - # @param out [IO] The output stream for messages + # @param out [IO, nil] The output stream for messages + # @param loose_unions [Boolean] See #initialize # @return [ApiMap] - def self.load_with_cache directory, out = IO::NULL, loose_unions: true + def self.load_with_cache directory, out = nil, loose_unions: true api_map = load(directory, loose_unions: loose_unions) if api_map.uncached_gemspecs.empty? logger.info { "All gems cached for #{directory}" } diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index ab22eca44..aac486605 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -407,7 +407,6 @@ def bar; end expect(type.tag).to eq('String') end - it 'denies calls off of nilable objects when loose union mode is off' do source = Solargraph::Source.load_string(%( # @type [String, nil] From e4afaada19c69de9dfc574ae3bcfc66880a44be9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 19 Aug 2025 08:21:34 -0400 Subject: [PATCH 093/930] 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 094/930] 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 095/930] 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 096/930] 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 097/930] 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 098/930] 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 099/930] 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 100/930] 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 101/930] 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 102/930] 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 103/930] 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 104/930] 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 105/930] 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 106/930] 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 107/930] 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 5517ff7a510c784a8df330f5bdd40239d49b5dd1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 09:22:20 -0400 Subject: [PATCH 108/930] Linting fixes --- lib/solargraph/shell.rb | 2 ++ lib/solargraph/type_checker.rb | 1 + 2 files changed, 3 insertions(+) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index a23e15a9b..964e21d00 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -170,6 +170,7 @@ def reporters # @return [void] def typecheck *files directory = File.realpath(options[:directory]) + # @sg-ignore Unresolved call to options level = options[:level].to_sym rules = Solargraph::TypeChecker::Rules.new(level) api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout, loose_unions: rules.loose_unions?) @@ -182,6 +183,7 @@ def typecheck *files filecount = 0 time = Benchmark.measure { files.each do |file| + # @sg-ignore Unresolved call to options checker = TypeChecker.new(file, api_map: api_map, rules: rules, level: options[:level].to_sym) problems = checker.problems next if problems.empty? diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index aee83db0e..0a5be1f14 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -23,6 +23,7 @@ class TypeChecker # @param filename [String] # @param api_map [ApiMap, nil] + # @param rules [Rules] # @param level [Symbol] def initialize filename, api_map: nil, level: :normal, rules: Rules.new(level) @filename = filename From 6c557803da5b37cee2ecdb5b14de5804e47f8fa5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 13:02:35 -0400 Subject: [PATCH 109/930] 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 d1e6b128e289ff8ef4ed8bd4c563ec297fcea486 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 27 Aug 2025 13:18:41 -0400 Subject: [PATCH 110/930] 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 111/930] 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 112/930] 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 113/930] 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 114/930] 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 115/930] 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 116/930] 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 117/930] 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 118/930] 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 119/930] 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 120/930] 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 121/930] 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 122/930] 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 123/930] 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 124/930] 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 54c6314e77aa6523f603c9fd168d36e50f8df6ac Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 28 Aug 2025 15:56:39 -0400 Subject: [PATCH 125/930] 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 706a70f120238d40c4c29d6540856970224c838b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 29 Aug 2025 11:10:23 -0400 Subject: [PATCH 126/930] 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/930] 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/930] 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 36383596fa21593bb6db0f7fffa343b37141aa75 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 29 Aug 2025 12:51:12 -0400 Subject: [PATCH 129/930] 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 130/930] 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 131/930] 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 132/930] 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 91ced058c5c81f3b1e63c51b27c08f3ca14c2339 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 30 Aug 2025 21:25:08 -0400 Subject: [PATCH 133/930] 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 134/930] 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 135/930] 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 7fc5fcd51511954f7e734c7e3ab8ba63b1d2a956 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 10:14:16 -0400 Subject: [PATCH 136/930] 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 c96d16519c33015e5a38e407333e1b086382dd10 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 31 Aug 2025 18:26:57 -0400 Subject: [PATCH 137/930] 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 138/930] 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 139/930] 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 140/930] 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 f623a73ad88f44d3ff6e6873a1682f33a07d431b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Sep 2025 07:52:39 -0400 Subject: [PATCH 141/930] 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 142/930] 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 143/930] 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 144/930] 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 145/930] 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 146/930] 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 147/930] 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 200e7e42b847bfb554de097d9ba5da6f182a7196 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 07:57:19 -0400 Subject: [PATCH 148/930] 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 83dfd6750b63227aa683e8e6cbd03346c26b3934 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 09:37:46 -0400 Subject: [PATCH 149/930] 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 150/930] 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 151/930] 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 5c90bbc91ca2894c0408c2918ed9cfd8183bdce5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 2 Sep 2025 12:12:04 -0400 Subject: [PATCH 152/930] 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 153/930] 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 154/930] 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 155/930] 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 156/930] 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 f63248177198c73cb9474367bdcdd0adce9f0936 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 18:17:14 -0400 Subject: [PATCH 157/930] 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 158/930] 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 159/930] 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 8ea210411fce0b2fcb8cf4d0b392a98454a2baad Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 3 Sep 2025 23:02:00 -0400 Subject: [PATCH 160/930] 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 161/930] 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 162/930] 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 163/930] 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 164/930] 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 165/930] 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 166/930] 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 167/930] 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 947d5b9c01b82d74c9f39613e0925ba975f0a98d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 4 Sep 2025 16:42:36 -0400 Subject: [PATCH 168/930] 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 169/930] 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 170/930] 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 171/930] 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 172/930] 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 173/930] 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 174/930] 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 175/930] 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 176/930] 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 177/930] 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 178/930] 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 179/930] 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 180/930] 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 181/930] 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 182/930] 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 183/930] 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 184/930] 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 185/930] 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 186/930] 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 187/930] 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 41f098b9245c194b5367d0e521fd595082118ee6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 6 Sep 2025 14:41:54 -0400 Subject: [PATCH 188/930] Rebuild rubocop todo file --- .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 7edc8691344bec6ca09fe1b8a4cce69df3cb0113 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 6 Sep 2025 22:04:57 -0400 Subject: [PATCH 189/930] 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 190/930] 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 191/930] 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 192/930] 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 193/930] 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 2548d6d8bcf9dd3a4de3933eaebeeef355e7a275 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 05:36:42 -0400 Subject: [PATCH 194/930] 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 195/930] 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 196/930] 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 197/930] 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 198/930] 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 199/930] 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 200/930] 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 201/930] 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 202/930] 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 203/930] 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 b2f33014d31f7d4b008845bb46006496a903df85 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 16:57:05 -0400 Subject: [PATCH 204/930] 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 8d0c4263b25c0930a09b742cd18c43fe269b8228 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:10:41 -0400 Subject: [PATCH 205/930] 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 cf54f57a9645ae0542b7ea1dede63dd8b85ff9af Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:14:22 -0400 Subject: [PATCH 206/930] 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 e2b034f0516ea84b1e95b8aa4d42fcf726f75a00 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:25:05 -0400 Subject: [PATCH 207/930] 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 28cd8d9803e983caa361e7b95cba8e547e7f4ac8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:44:39 -0400 Subject: [PATCH 208/930] 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 e2d5c0946da81ccbd0cc0388bfc02004bc7b96c2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:45:56 -0400 Subject: [PATCH 209/930] 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 4036347bd6e42351284101f114e017b9a07f28a9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 17:58:16 -0400 Subject: [PATCH 210/930] 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 2031cbb20644803d515a11948416394432aeaa7c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 18:09:04 -0400 Subject: [PATCH 211/930] 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 dbe9a3edc5291e6cf50632560893306ee074a79f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 7 Sep 2025 18:40:51 -0400 Subject: [PATCH 212/930] 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 213/930] 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 214/930] 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 215/930] 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 216/930] 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 217/930] 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 218/930] 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 219/930] 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 220/930] 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 221/930] 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 ef3fc9d700c117e0e466a16bbcc469e6ca066481 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 8 Sep 2025 15:21:31 -0400 Subject: [PATCH 222/930] 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 223/930] 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 636c55885f97df5f869e9421dc701987d82a34ab Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 07:54:56 -0400 Subject: [PATCH 224/930] 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 9b713b27c4f3ced6377b4b352fd249e2bda0ab59 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 11:20:02 -0400 Subject: [PATCH 225/930] 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 4e02576942b48d8320123c9921324db1c203ab26 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 9 Sep 2025 16:36:23 -0400 Subject: [PATCH 226/930] 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 227/930] 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 228/930] 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 82c94a675f989db005e0e29dba45dafe507c6157 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 12:16:23 -0400 Subject: [PATCH 229/930] 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 230/930] 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 c32c86cc9fbdb45f97aed4c7ead1c20fcfbcaee2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 16:55:27 -0400 Subject: [PATCH 231/930] 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 bd3ce0b0ed43efb0b1f3401e595a4e2a1dd4d6c7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 18:00:20 -0400 Subject: [PATCH 232/930] 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 adb34e55d3ca11e84191acbf4ccea18643da7f12 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 21:02:51 -0400 Subject: [PATCH 233/930] 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 234/930] 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 235/930] 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 236/930] 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 237/930] 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 3a7b7e7bc980cfc22371348362d98b0d99e78408 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 10 Sep 2025 22:51:56 -0400 Subject: [PATCH 238/930] 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 239/930] 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 240/930] 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 241/930] 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 242/930] 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 243/930] 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 244/930] 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 245/930] 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 246/930] 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 247/930] 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 248/930] 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 249/930] 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 250/930] 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 62d7d0833bd366a7b98690e157a1dd79be2db658 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 11 Sep 2025 14:29:03 -0400 Subject: [PATCH 251/930] 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 252/930] 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 14:36:53 -0400 Subject: [PATCH 253/930] 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 254/930] 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 255/930] 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 256/930] 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 257/930] 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 5a1a02dc089b7562101551845da9956c58236500 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 18:12:45 -0400 Subject: [PATCH 258/930] 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 259/930] 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 bf7642067833125b7a82319cd22a3b038e58ded2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 18:42:38 -0400 Subject: [PATCH 260/930] 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 | 4 ++-- 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, 140 insertions(+), 14 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 217e9b476..abff2180b 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -120,6 +120,7 @@ def doc_map @doc_map ||= DocMap.new([], []) end + # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [::Array] def uncached_gemspecs @doc_map&.uncached_gemspecs || [] @@ -211,6 +212,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 # @return [ApiMap] @@ -788,6 +790,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 bdfd48b90..e4841391e 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -274,6 +274,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 298b2a8e4..6cb87b13e 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -128,7 +128,7 @@ 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 + # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version def self.all_yard_gems_in_memory @yard_gems_in_memory ||= {} end @@ -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 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 9d5162431..bb42dc649 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -493,6 +493,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 @@ -539,6 +540,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 8692e9762..30128d409 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 ddc742bd8..a9fcd50f7 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -27,6 +27,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 8a6e5f9e0..c11c7a685 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -133,6 +133,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 e5b049b06..e8f1cf4c4 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] if sclass.is_a?(AST::Node) && sclass.type == :self closure = region.closure 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..e41374640 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 165567aa1..49c81fba2 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 @@ -476,12 +485,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 @@ -506,6 +517,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') @@ -575,6 +587,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 504dd4862..bae920e7c 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -75,6 +75,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 002ba974e..d26821f4b 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -135,6 +135,7 @@ def full end end + # @sg-ignore super always sets @return_type to something # @return [ComplexType] def return_type if @return_type.nil? @@ -151,12 +152,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] @@ -185,6 +187,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? @@ -205,6 +208,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] @@ -236,6 +240,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 2a0ec4639..942413f14 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -9,6 +9,7 @@ class << self # 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 @@ -192,6 +193,7 @@ def clear private # @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 c6c10bac6..e74a47e18 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -96,6 +96,9 @@ def pins # @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 3a366d9a6..371a6114f 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -82,6 +82,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| @@ -116,6 +118,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 @@ -123,6 +126,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 @@ -186,10 +190,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 a8325ea89..ee78130c0 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -39,6 +39,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 0b2d84a01..a97b5c566 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 a5ba2934c656886cf4c26214a9aedf0f08a1dc0c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 18:56:21 -0400 Subject: [PATCH 261/930] Enable CI --- .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..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' diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index b5984f3cb..2c747b5d0 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 ecc3d9771..ddb070ffd 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..cdbb17b6f 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 eb12ff0127814c8a998f4cf165fdb08d76e8b8eb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 18:58:05 -0400 Subject: [PATCH 262/930] Enable CI --- .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 4520280dc..6262dd494 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 diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index ddb070ffd..0bf9f10d1 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -11,7 +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 cdbb17b6f..9f0020065 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -11,7 +11,7 @@ on: push: branches: [ master ] pull_request: - branches: [ * ] + branches: ['*'] permissions: contents: read From 12a9e5c2b7f1dc17942c25e9d59812f513d1955c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 19:02:23 -0400 Subject: [PATCH 263/930] 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 2b35988f7..a346b9972 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -442,7 +442,7 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum 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) + 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 bf7395926ec5f0be68da0dfdc9b122d4ec02495e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 19:06:59 -0400 Subject: [PATCH 264/930] 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 277696483..d5ea9753d 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -261,7 +261,7 @@ def index # @param pinsets [Array>] # - # @return [void] + # @return [true] def catalog pinsets @pinsets = pinsets # @type [Array] From 07d567429c4b44d26a2a68a6782ba7d1dafbab9b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 19:16:58 -0400 Subject: [PATCH 265/930] 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 e6a630562..c626101f2 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -144,7 +144,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 520c8c18e964547bd9d33def9dcbcba2e3e49757 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 20:09:27 -0400 Subject: [PATCH 266/930] 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 c2d29443739d7866ce819397b72749c62bccf5d0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 20:26:08 -0400 Subject: [PATCH 267/930] 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 5dcf28552..ee4a46101 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -429,6 +429,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 57f55b911778ed7faf1ef6f7fa2fe88bcaf6eb3e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 20:45:28 -0400 Subject: [PATCH 268/930] 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 b98d84751..278ab099e 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] @@ -782,19 +799,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 730056319b1e903dbdc3e400386a81d9531d8a8e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 12 Sep 2025 21:01:59 -0400 Subject: [PATCH 269/930] 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 db725f78fdb3756c917044e35dd6d1eec1c86095 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 13 Sep 2025 16:30:39 -0400 Subject: [PATCH 270/930] 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 271/930] 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 272/930] 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 ca4f03922843102de8ac2ada050a264e095bab9f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 13 Sep 2025 19:35:01 -0400 Subject: [PATCH 273/930] Rebuild rubocop todo --- .rubocop_todo.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f7e83b95f..ecc699d97 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -584,7 +584,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' @@ -675,7 +674,6 @@ RSpec/LetBeforeExamples: # SupportedStyles: have_received, receive RSpec/MessageSpies: Exclude: - - 'spec/doc_map_spec.rb' - 'spec/language_server/host/diagnoser_spec.rb' - 'spec/language_server/host/message_worker_spec.rb' @@ -704,7 +702,11 @@ RSpec/NotToNot: - 'spec/rbs_map/core_map_spec.rb' RSpec/PendingWithoutReason: - Enabled: false + Exclude: + - '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. From df2573539463c71815ed97ee4ed1d3347a03cf64 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 20:45:04 -0400 Subject: [PATCH 274/930] 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 275/930] 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 9bfdbe7d986893ae8487377e9c44c8ad25e7a6b5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 20:49:32 -0400 Subject: [PATCH 276/930] 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 08a418235..d45ac7cef 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 1bf7b8b4bfaece7291fb841e5196109fcf424ea4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 21:14:22 -0400 Subject: [PATCH 277/930] Fix merge --- lib/solargraph/type_checker.rb | 132 ++++++++++++++------------------- 1 file changed, 56 insertions(+), 76 deletions(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 3e8f9ede8..c315a407f 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -182,35 +182,35 @@ 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 = [] - 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 + 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? 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 - # @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 + # @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 result end @@ -360,10 +360,10 @@ def argument_problems_for chain, api_map, closure_pin, locals, location 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| - params = param_details_from_stack(sig, pins) - 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 @@ -441,7 +441,6 @@ 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? && !arg_conforms_to?(argtype, ptype) errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") return errors @@ -481,10 +480,9 @@ 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? # @type [ComplexType] - argtype = argchain.infer(api_map, block_pin, locals).self_to_type(block_pin.context) + 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}") end @@ -520,26 +518,9 @@ 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) - # 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] + # @param pin [Pin::Method] # @return [Hash{String => Hash{Symbol => String, ComplexType}}] - def signature_param_details(pin) + def param_hash(pin) # @type [Hash{String => Hash{Symbol => String, ComplexType}}] result = {} pin.parameters.each do |param| @@ -550,47 +531,46 @@ def signature_param_details(pin) qualified: type } end - result - end - - # 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 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| - 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] + # 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 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) - 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) + 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}}] # 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_to_param_details param_details, param_names, signature_param_details(sig) + 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] end end - param_details + results end # @param pin [Pin::Base] From 135e50db2a27741618e9d76c39b4ec565d6df800 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 21:18:22 -0400 Subject: [PATCH 278/930] 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 108118218599b38e226d412c8b76dee8beb42dde Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 21:48:35 -0400 Subject: [PATCH 279/930] Annotation changes --- lib/solargraph/api_map.rb | 9 ++++--- lib/solargraph/api_map/cache.rb | 2 +- lib/solargraph/api_map/constants.rb | 5 +++- lib/solargraph/api_map/index.rb | 3 ++- lib/solargraph/api_map/store.rb | 7 +++++- lib/solargraph/complex_type.rb | 12 ++++++---- lib/solargraph/complex_type/unique_type.rb | 19 +++++++++++---- .../convention/active_support_concern.rb | 2 ++ .../convention/struct_definition.rb | 1 + lib/solargraph/diagnostics/rubocop_helpers.rb | 1 + lib/solargraph/diagnostics/type_check.rb | 1 + lib/solargraph/diagnostics/update_errors.rb | 1 + lib/solargraph/doc_map.rb | 10 ++++---- lib/solargraph/language_server/host.rb | 1 + .../language_server/host/message_worker.rb | 5 +++- lib/solargraph/library.rb | 6 +++++ lib/solargraph/location.rb | 1 + lib/solargraph/parser/comment_ripper.rb | 3 +++ .../parser/parser_gem/node_chainer.rb | 1 + .../parser/parser_gem/node_methods.rb | 3 +++ .../parser_gem/node_processors/block_node.rb | 2 ++ .../parser_gem/node_processors/send_node.rb | 1 + lib/solargraph/parser/region.rb | 1 + lib/solargraph/pin/base.rb | 7 ++++-- lib/solargraph/pin/base_variable.rb | 1 + lib/solargraph/pin/common.rb | 4 ++-- lib/solargraph/pin/delegated_method.rb | 3 --- lib/solargraph/pin/instance_variable.rb | 2 +- lib/solargraph/pin/method.rb | 2 ++ lib/solargraph/pin/proxy_type.rb | 2 +- lib/solargraph/pin/symbol.rb | 2 +- lib/solargraph/position.rb | 1 + lib/solargraph/rbs_map/conversions.rb | 24 +++++++++++++++---- lib/solargraph/source.rb | 1 + lib/solargraph/source/chain.rb | 1 + lib/solargraph/source/chain/call.rb | 14 +++++++---- lib/solargraph/source/change.rb | 5 ++-- lib/solargraph/source/cursor.rb | 1 + lib/solargraph/source/source_chainer.rb | 3 +++ lib/solargraph/source_map.rb | 2 +- lib/solargraph/source_map/clip.rb | 1 + lib/solargraph/type_checker.rb | 15 +++++++++--- lib/solargraph/type_checker/problem.rb | 2 +- lib/solargraph/type_checker/rules.rb | 8 ++++++- lib/solargraph/yard_map/helpers.rb | 2 +- lib/solargraph/yard_map/mapper/to_method.rb | 1 + lib/solargraph/yard_map/to_method.rb | 3 ++- 47 files changed, 153 insertions(+), 51 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 8aceaaa56..f8a4eeed0 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -192,7 +192,7 @@ def self.load directory api_map end - # @param out [IO, nil] + # @param out [StringIO, IO, nil] # @return [void] def cache_all!(out) @doc_map.cache_all!(out) @@ -214,9 +214,8 @@ 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 + # @param out [IO, StringIO] The output stream for messages # # @return [ApiMap] def self.load_with_cache directory, out = $stdout @@ -333,6 +332,7 @@ def get_instance_variable_pins(namespace, scope = :instance) result.concat store.get_instance_variables(namespace, scope) sc_fqns = namespace while (sc = store.get_superclass(sc_fqns)) + # @sg-ignore flow sensitive typing needs to handle "if foo" sc_fqns = store.constants.dereference(sc) result.concat store.get_instance_variables(sc_fqns, scope) end @@ -641,6 +641,7 @@ def super_and_sub?(sup, sub) return true if sup == sub sc_fqns = sub while (sc = store.get_superclass(sc_fqns)) + # @sg-ignore flow sensitive typing needs to handle "if foo" sc_new = store.constants.dereference(sc) # Cyclical inheritance is invalid return false if sc_new == sc_fqns @@ -784,6 +785,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? + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, visibility, true, skip, no_core) end else @@ -794,6 +796,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? + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" 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? diff --git a/lib/solargraph/api_map/cache.rb b/lib/solargraph/api_map/cache.rb index 0052d91ea..0e0be4335 100644 --- a/lib/solargraph/api_map/cache.rb +++ b/lib/solargraph/api_map/cache.rb @@ -68,7 +68,7 @@ def get_qualified_namespace name, context # @param name [String] # @param context [String] - # @param value [String] + # @param value [String, nil] # @return [void] def set_qualified_namespace name, context, value @qualified_namespaces["#{name}|#{context}"] = value diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index adbe1350c..7922fc41f 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -16,6 +16,7 @@ def initialize store # @param gates [Array, String>] # @return [String, nil] def resolve(name, *gates) + # @sg-ignore Need to figure if String#[n..m] can return nil return store.get_path_pins(name[2..]).first&.path if name.start_with?('::') flat = gates.flatten @@ -136,13 +137,14 @@ def cached_collect # will start the search in the specified context until it finds a # match for the namespace. # - # @param namespace [String, nil] The namespace to + # @param namespace [String] The namespace to # match # @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 = '' if namespace.start_with?('::') + # @sg-ignore Need to figure if String#[n..m] can return nil inner_qualify(namespace[2..], '', Set.new) else inner_qualify(namespace, context_namespace, Set.new) @@ -208,6 +210,7 @@ def inner_get_constants fqns, visibility, skip end sc_ref = store.get_superclass(fqns) if sc_ref + # @sg-ignore flow sensitive typing needs to handle "if foo" fqsc = dereference(sc_ref) result.concat inner_get_constants(fqsc, [:public], skip) unless %w[Object BasicObject].include?(fqsc) end diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 6837655b0..f27335036 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -118,7 +118,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| @@ -145,6 +145,7 @@ def map_overrides redefine_return_type pin, tag if new_pin new_pin.docstring.add_tag(tag) + # @sg-ignore flow sensitive typing needs to handle "if foo" redefine_return_type new_pin, tag end end diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index d45ac7cef..3a764333c 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -95,6 +95,7 @@ def qualify_superclass fq_sub_tag return type.simplify_literals.to_s if type.literal? ref = get_superclass(fq_sub_tag) return unless ref + # @sg-ignore flow sensitive typing needs to handle "if foo" res = constants.dereference(ref) return unless res res + type.substring @@ -202,7 +203,7 @@ def pins_by_class klass index.pins_by_class klass end - # @param fqns [String] + # @param fqns [String, nil] # @return [Array] def fqns_pins fqns return [] if fqns.nil? @@ -236,9 +237,12 @@ def get_ancestors(fqns) # Add superclass ref = get_superclass(current) + # @sg-ignore flow sensitive typing needs to handle || on nil types superclass = ref && constants.dereference(ref) if superclass && !superclass.empty? && !visited.include?(superclass) + # @sg-ignore flow sensitive typing needs to handle "if foo" ancestors << superclass + # @sg-ignore flow sensitive typing needs to handle "if foo" queue << superclass end @@ -368,6 +372,7 @@ def uncached_qualify_superclass fq_sub_tag return type.simplify_literals.to_s if type.literal? ref = get_superclass(fq_sub_tag) return unless ref + # @sg-ignore flow sensitive typing needs to handle "if foo" res = constants.dereference(ref) return unless res res + type.substring diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 6b83f54f9..54bd1d7d3 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -44,7 +44,7 @@ def qualify api_map, context = '' end # @param generics_to_resolve [Enumerable]] - # @param context_type [UniqueType, nil] + # @param context_type [ComplexType, ComplexType::UniqueType, nil] # @param resolved_generic_values [Hash{String => ComplexType}] Added to as types are encountered or resolved # @return [self] def resolve_generics_from_context generics_to_resolve, context_type, resolved_generic_values: {} @@ -65,7 +65,7 @@ def to_rbs (@items.length > 1 ? ')' : '')) end - # @param dst [ComplexType] + # @param dst [ComplexType, ComplexType::UniqueType] # @return [ComplexType] def self_to_type dst object_type_dst = dst.reduce_class_type @@ -217,14 +217,15 @@ def conforms_to?(api_map, expected, end # @param api_map [ApiMap] - # @param expected [ComplexType] - # @param inferred [ComplexType] + # @param expected [ComplexType, UniqueType] + # @param inferred [ComplexType, UniqueType] # @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..] + # @sg-ignore Need to figure if String#[n..m] can return nil return false if api_map.get_method_stack(inferred.namespace, quack, scope: inferred.scope).empty? end true @@ -367,12 +368,13 @@ class << self # :kwarg, etc) of the arguments given, instead of just having # an array of Chains as the arguments. def parse *strings, partial: false - # @type [Hash{Array => ComplexType}] + # @type [Hash{Array => ComplexType, Array}] @cache ||= {} unless partial cached = @cache[strings] return cached unless cached.nil? end + # @types [Array] types = [] key_types = nil strings.each do |type_string| diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index c24f81d4a..6436f8204 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -31,6 +31,8 @@ def self.parse name, substring = '', make_rooted: nil if name.start_with?('::') name = name[2..-1] rooted = true + # @sg-ignore out of scope of the above [2..-1], which is what + # is making solargraph think 'name' is nilable elsif !can_root_name?(name) rooted = true else @@ -45,6 +47,7 @@ def self.parse name, substring = '', make_rooted: nil parameters_type = nil unless substring.empty? subs = ComplexType.parse(substring[1..-2], partial: true) + # @sg-ignore need a non-empty-list type 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) @@ -61,6 +64,7 @@ def self.parse name, substring = '', make_rooted: nil subtypes.concat subs end end + # @sg-ignore Need to figure if String#[n..m] can return nil new(name, key_types, subtypes, rooted: rooted, parameters_type: parameters_type) end @@ -134,6 +138,7 @@ def determine_non_literal_name return 'NilClass' if name == 'nil' return 'Boolean' if ['true', 'false'].include?(name) return 'Symbol' if name[0] == ':' + # @sg-ignore need a non-empty-list type return 'String' if ['"', "'"].include?(name[0]) return 'Integer' if name.match?(/^-?\d+$/) name @@ -200,7 +205,7 @@ def erased_version_of?(other) # @param api_map [ApiMap] # @param expected [ComplexType::UniqueType, ComplexType] - # @param situation [:method_call, :assignment, :return] + # @param situation [:method_call, :assignment, :return_type] # @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 = [], @@ -322,15 +327,18 @@ def downcast_to_literal_if_possible end # @param generics_to_resolve [Enumerable] - # @param context_type [UniqueType, nil] + # @param context_type [ComplexType, UniqueType, nil] # @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 type_param = subtypes.first&.name - return self unless generics_to_resolve.include? type_param + # @sg-ignore flow sensitive typing needs to handle "if foo" + return self unless type_param && generics_to_resolve.include?(type_param) + # @sg-ignore flow sensitive typing needs to handle "if foo" unless context_type.nil? || !resolved_generic_values[type_param].nil? new_binding = true + # @sg-ignore flow sensitive typing needs to handle "if foo" resolved_generic_values[type_param] = context_type end if new_binding @@ -338,6 +346,7 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge complex_type.resolve_generics_from_context(generics_to_resolve, nil, resolved_generic_values: resolved_generic_values) end end + # @sg-ignore flow sensitive typing needs to handle "if foo" return resolved_generic_values[type_param] || self end @@ -348,7 +357,7 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge end # @param generics_to_resolve [Enumerable] - # @param context_type [UniqueType, nil] + # @param context_type [UniqueType, ComplexType, nil] # @param resolved_generic_values [Hash{String => ComplexType}] # @yieldreturn [Array] # @return [Array] @@ -399,6 +408,7 @@ def resolve_generics definitions, context_type ComplexType::UNDEFINED end else + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" context_type.all_params[idx] || definitions.generic_defaults[generic_name] || ComplexType::UNDEFINED end else @@ -432,6 +442,7 @@ def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: new_key_types ||= @key_types new_subtypes ||= @subtypes make_rooted = @rooted if make_rooted.nil? + # @sg-ignore Need to understand @foo ||= 123 will never be nil UniqueType.new(new_name, new_key_types, new_subtypes, rooted: make_rooted, parameters_type: parameters_type) end diff --git a/lib/solargraph/convention/active_support_concern.rb b/lib/solargraph/convention/active_support_concern.rb index 74c9ce765..31f846523 100644 --- a/lib/solargraph/convention/active_support_concern.rb +++ b/lib/solargraph/convention/active_support_concern.rb @@ -80,12 +80,14 @@ def process_include include_tag "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ "Handling class include include_tag=#{include_tag}" end + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" module_extends = api_map.get_extends(rooted_include_tag).map(&:parametrized_tag).map(&:to_s) logger.debug do "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ "found module extends of #{rooted_include_tag}: #{module_extends}" end return unless module_extends.include? 'ActiveSupport::Concern' + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" included_class_pins = api_map.inner_get_methods_from_reference(rooted_include_tag, namespace_pin, rooted_type, :class, visibility, deep, skip, true) logger.debug do diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index a812727d1..0556955b8 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -132,6 +132,7 @@ def parse_comments struct_comments += "\n#{comment}" end + # @sg-ignore flow sensitive typing needs to handle || on nil types Solargraph::Source.parse_docstring(struct_comments).to_docstring end diff --git a/lib/solargraph/diagnostics/rubocop_helpers.rb b/lib/solargraph/diagnostics/rubocop_helpers.rb index f6f4c82c8..549103604 100644 --- a/lib/solargraph/diagnostics/rubocop_helpers.rb +++ b/lib/solargraph/diagnostics/rubocop_helpers.rb @@ -47,6 +47,7 @@ def generate_options filename, code # @return [String] def fix_drive_letter path return path unless path.match(/^[a-z]:/) + # @sg-ignore Need to figure if String#[n..m] can return nil path[0].upcase + path[1..-1] end diff --git a/lib/solargraph/diagnostics/type_check.rb b/lib/solargraph/diagnostics/type_check.rb index 583abd077..48c0f2be8 100644 --- a/lib/solargraph/diagnostics/type_check.rb +++ b/lib/solargraph/diagnostics/type_check.rb @@ -11,6 +11,7 @@ def diagnose source, api_map # return [] unless args.include?('always') || api_map.workspaced?(source.filename) severity = Diagnostics::Severities::ERROR level = (args.reverse.find { |a| ['normal', 'typed', 'strict', 'strong'].include?(a) }) || :normal + # @sg-ignore sensitive typing needs to handle || on nil types checker = Solargraph::TypeChecker.new(source.filename, api_map: api_map, level: level.to_sym) checker.problems .sort { |a, b| a.location.range.start.line <=> b.location.range.start.line } diff --git a/lib/solargraph/diagnostics/update_errors.rb b/lib/solargraph/diagnostics/update_errors.rb index b6f9baa89..c615ece72 100644 --- a/lib/solargraph/diagnostics/update_errors.rb +++ b/lib/solargraph/diagnostics/update_errors.rb @@ -32,6 +32,7 @@ def combine_ranges code, ranges next if rng.start.line >= code.lines.length scol = code.lines[rng.start.line].index(/[^\s]/) || 0 ecol = code.lines[rng.start.line].length + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" result.push Range.from_to(rng.start.line, scol, rng.start.line, ecol) end result diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index ee4a46101..74add4a0e 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -60,7 +60,7 @@ def initialize(requires, preferences, workspace = nil) pins.concat @environ.pins end - # @param out [IO] + # @param out [StringIO, IO, nil] # @return [void] def cache_all!(out) # if we log at debug level: @@ -80,7 +80,7 @@ def cache_all!(out) end # @param gemspec [Gem::Specification] - # @param out [IO] + # @param out [IO, nil] # @return [void] def cache_yard_pins(gemspec, out) pins = GemPins.build_yard_pins(yard_plugins, gemspec) @@ -89,7 +89,7 @@ def cache_yard_pins(gemspec, out) end # @param gemspec [Gem::Specification] - # @param out [IO] + # @param out [IO, nil] # @return [void] def cache_rbs_collection_pins(gemspec, out) rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path) @@ -103,7 +103,7 @@ def cache_rbs_collection_pins(gemspec, out) # @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 + # @param out [StringIO, IO, nil] output stream for logging # @return [void] def cache(gemspec, rebuild: false, out: nil) build_yard = uncached_yard_gemspecs.include?(gemspec) || rebuild @@ -253,6 +253,7 @@ def deserialize_combined_pin_cache(gemspec) if !rbs_collection_pins.nil? && !yard_pins.nil? logger.debug { "Combining pins for #{gemspec.name}:#{gemspec.version}" } + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" 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 @@ -331,6 +332,7 @@ def resolve_path_to_gemspecs path end end return nil if gemspec.nil? + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" [gemspec_or_preference(gemspec)] end diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index 4fda6cf7b..dcf59c6da 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -728,6 +728,7 @@ def requests # @return [String] def normalize_separators path return path if File::ALT_SEPARATOR.nil? + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) end diff --git a/lib/solargraph/language_server/host/message_worker.rb b/lib/solargraph/language_server/host/message_worker.rb index 94c3d1cf8..b42718c1e 100644 --- a/lib/solargraph/language_server/host/message_worker.rb +++ b/lib/solargraph/language_server/host/message_worker.rb @@ -28,7 +28,7 @@ def initialize(host) end # pending handle messages - # @return [Array] + # @return [Array undefined}>] def messages @messages ||= [] end @@ -86,7 +86,9 @@ def cancel_message idx = messages.find_index { |msg| msg['method'] == '$/cancelRequest' } return unless idx + # @sg-ignore flow sensitive typing needs to handle "if foo" msg = messages[idx] + # @sg-ignore flow sensitive typing needs to handle "if foo" messages.delete_at idx msg end @@ -97,6 +99,7 @@ def next_priority idx = messages.find_index do |msg| UPDATE_METHODS.include?(msg['method']) || version_dependent?(msg) end + # @sg-ignore flow sensitive typing needs to handle "if foo" idx ? messages.delete_at(idx) : messages.shift end diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index bb42dc649..1de8f8d76 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -410,7 +410,9 @@ def diagnose filename name = args.shift reporter = Diagnostics.reporter(name) raise DiagnosticsError, "Diagnostics reporter #{name} does not exist" if reporter.nil? + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" repargs[reporter] ||= [] + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" repargs[reporter].concat args end end @@ -472,6 +474,7 @@ def next_map src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) } if src Logging.logger.debug "Mapping #{src.filename}" + # @sg-ignore flow sensitive typing needs to handle "if foo" source_map_hash[src.filename] = Solargraph::SourceMap.map(src) source_map_hash[src.filename] else @@ -566,9 +569,11 @@ def maybe_map source return unless source return unless @current == source || workspace.has_file?(source.filename) if source_map_hash.key?(source.filename) + # @sg-ignore flow sensitive typing needs to handle "if foo" new_map = Solargraph::SourceMap.map(source) source_map_hash[source.filename] = new_map else + # @sg-ignore flow sensitive typing needs to handle "if foo" source_map_hash[source.filename] = Solargraph::SourceMap.map(source) end end @@ -634,6 +639,7 @@ def queued_gemspec_cache # @return [void] def report_cache_progress gem_name, pending @total ||= pending + # @sg-ignore Need to understand @foo ||= 123 will never be nil @total = pending if pending > @total finished = @total - pending pct = if @total.zero? diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index b520ced79..26fa6be77 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -64,6 +64,7 @@ def to_hash # @return [Location, nil] def self.from_node(node) return nil if node.nil? || node.loc.nil? + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" range = Range.from_node(node) self.new(node.loc.expression.source_buffer.name, range) end diff --git a/lib/solargraph/parser/comment_ripper.rb b/lib/solargraph/parser/comment_ripper.rb index aa32cf498..ca6c0273e 100644 --- a/lib/solargraph/parser/comment_ripper.rb +++ b/lib/solargraph/parser/comment_ripper.rb @@ -43,6 +43,7 @@ def create_snippet(result) # @sg-ignore @override is adding, not overriding def on_embdoc_beg *args result = super + # @sg-ignore @override is adding, not overriding create_snippet(result) result end @@ -50,6 +51,7 @@ def on_embdoc_beg *args # @sg-ignore @override is adding, not overriding def on_embdoc *args result = super + # @sg-ignore @override is adding, not overriding create_snippet(result) result end @@ -57,6 +59,7 @@ def on_embdoc *args # @sg-ignore @override is adding, not overriding def on_embdoc_end *args result = super + # @sg-ignore @override is adding, not overriding create_snippet(result) result end diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index d8d46319b..a18490258 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -132,6 +132,7 @@ def generate_links n result.push Source::Chain::Array.new(chained_children, n) else lit = infer_literal_node_type(n) + # @sg-ignore flow sensitive typing needs to handle "if foo" result.push (lit ? Chain::Literal.new(lit, n) : Chain::Link.new) end result diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 5b3ebdca5..111a7f950 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -226,6 +226,7 @@ def find_recipient_node cursor if node.type == :send args = node.children[2..-1] if !args.empty? + # @sg-ignore flow sensitive typing needs to handle "if foo" return node if prev && args.include?(prev) else if source.synchronized? @@ -348,6 +349,7 @@ def from_value_position_statement node, include_explicit_returns: true if COMPOUND_STATEMENTS.include?(node.type) result.concat from_value_position_compound_statement node elsif CONDITIONAL_ALL_BUT_FIRST.include?(node.type) + # @sg-ignore Need to figure if Array#[n..m] can return nil result.concat reduce_to_value_nodes(node.children[1..-1]) # result.push NIL_NODE unless node.children[2] elsif CONDITIONAL_ALL.include?(node.type) @@ -463,6 +465,7 @@ def reduce_to_value_nodes nodes elsif COMPOUND_STATEMENTS.include?(node.type) result.concat from_value_position_compound_statement(node) elsif CONDITIONAL_ALL_BUT_FIRST.include?(node.type) + # @sg-ignore Need to figure if Array#[n..m] can return nil result.concat reduce_to_value_nodes(node.children[1..-1]) elsif node.type == :return result.concat reduce_to_value_nodes([node.children[0]]) 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 d773e8e50..9336e2e42 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/block_node.rb @@ -37,6 +37,8 @@ def process def other_class_eval? node.children[0].type == :send && node.children[0].children[1] == :class_eval && + # @sg-ignore Array#include? argument type should include + # nil for expressiveness like below [:cbase, :const].include?(node.children[0].children[0]&.type) end end 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 54009660b..dcbbd2fe1 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -202,6 +202,7 @@ def process_module_function # @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? + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" pins.delete ref mm = Solargraph::Pin::Method.new( location: ref.location, diff --git a/lib/solargraph/parser/region.rb b/lib/solargraph/parser/region.rb index a6559bc8a..aca052f3e 100644 --- a/lib/solargraph/parser/region.rb +++ b/lib/solargraph/parser/region.rb @@ -50,6 +50,7 @@ def filename # @param lvars [Array, nil] # @return [Region] def update closure: nil, scope: nil, visibility: nil, lvars: nil + # @sg-ignore flow sensitive typing needs to handle || on nil types Region.new( source: source, closure: closure || self.closure, diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index c77f962e3..732d6a8e1 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -41,7 +41,7 @@ def presence_certain? # @param type_location [Solargraph::Location, nil] # @param closure [Solargraph::Pin::Closure, nil] # @param name [String] - # @param comments [String] + # @param comments [String, nil] # @param source [Symbol, nil] # @param docstring [YARD::Docstring, nil] # @param directives [::Array, nil] @@ -375,13 +375,15 @@ def assert_source_provided Solargraph.assert_or_log(:source, "source not provided - #{@path} #{@source} #{self.class}") if source.nil? end + # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def comments + # @sg-ignore Need to understand @foo ||= 123 will never be nil @comments ||= '' end # @param generics_to_resolve [Enumerable] - # @param return_type_context [ComplexType, nil] + # @param return_type_context [ComplexType, ComplexType::UniqueType, nil] # @param context [ComplexType] # @param resolved_generic_values [Hash{String => ComplexType}] # @return [self] @@ -603,6 +605,7 @@ def type_desc rbs = return_type.rooted_tags if return_type.name == 'Class' if path if rbs + # @sg-ignore flow sensitive typing needs to handle "if foo" path + ' ' + rbs else path diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 6df623bb7..0fbf6b1c0 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -82,6 +82,7 @@ def return_types_from_node(parent_node, api_map) # @return [ComplexType] def probe api_map unless @assignment.nil? + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" types = return_types_from_node(@assignment, api_map) return ComplexType.new(types.uniq) unless types.empty? end diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 85d734fc9..c31f513de 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -28,7 +28,7 @@ def return_type @return_type ||= ComplexType::UNDEFINED end - # @return [ComplexType] + # @return [ComplexType, ComplexType::UniqueType] def context # Get the static context from the nearest namespace @context ||= find_context @@ -40,7 +40,7 @@ def namespace context.namespace.to_s end - # @return [ComplexType] + # @return [ComplexType, ComplexType::UniqueType] def binder @binder || context end diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index 3b1227bfc..ebdb020d6 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -44,7 +44,6 @@ def type_location %i[comments parameters return_type signatures].each do |method| define_method(method) do - # @sg-ignore Need to set context correctly in define_method blocks @resolved_method ? @resolved_method.send(method) : super() end end @@ -52,9 +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) end end diff --git a/lib/solargraph/pin/instance_variable.rb b/lib/solargraph/pin/instance_variable.rb index c06fdd93e..6774924fb 100644 --- a/lib/solargraph/pin/instance_variable.rb +++ b/lib/solargraph/pin/instance_variable.rb @@ -3,7 +3,7 @@ module Solargraph module Pin class InstanceVariable < BaseVariable - # @return [ComplexType] + # @return [ComplexType, ComplexType::UniqueType] def binder closure.binder end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index e2fefef3e..250dd2805 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -191,7 +191,9 @@ def generate_signature(parameters, return_type) name = p.name decl = :arg if name + # @sg-ignore flow sensitive typing needs to handle "if foo" decl = select_decl(name, false) + # @sg-ignore flow sensitive typing needs to handle "if foo" name = clean_param(name) end Pin::Parameter.new( diff --git a/lib/solargraph/pin/proxy_type.rb b/lib/solargraph/pin/proxy_type.rb index 2323489a7..495f3decc 100644 --- a/lib/solargraph/pin/proxy_type.rb +++ b/lib/solargraph/pin/proxy_type.rb @@ -3,7 +3,7 @@ module Solargraph module Pin class ProxyType < Base - # @param return_type [ComplexType] + # @param return_type [ComplexType, ComplexType::UniqueType] # @param binder [ComplexType, ComplexType::UniqueType, nil] def initialize return_type: ComplexType::UNDEFINED, binder: nil, **splat super(**splat) diff --git a/lib/solargraph/pin/symbol.rb b/lib/solargraph/pin/symbol.rb index c97aeb262..91278c24e 100644 --- a/lib/solargraph/pin/symbol.rb +++ b/lib/solargraph/pin/symbol.rb @@ -3,7 +3,7 @@ module Solargraph module Pin class Symbol < Base - # @param location [Solargraph::Location] + # @param location [Solargraph::Location, nil] # @param name [String] def initialize(location, name, **kwargs) # @sg-ignore "Unrecognized keyword argument kwargs to Solargraph::Pin::Base#initialize" diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index f0475bbe2..2bc6197ed 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -93,6 +93,7 @@ def self.from_offset text, offset end character = 0 if character.nil? and (cursor - offset).between?(0, 1) raise InvalidOffsetError if character.nil? + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" Position.new(line, character) end diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index f40824aa4..ddaf1e355 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -119,32 +119,44 @@ def convert_members_to_pins decl, closure def convert_member_to_pin member, closure, context case member when RBS::AST::Members::MethodDefinition + # @sg-ignore flow based typing needs to understand case when class pattern method_def_to_pin(member, closure, context) when RBS::AST::Members::AttrReader + # @sg-ignore flow based typing needs to understand case when class pattern attr_reader_to_pin(member, closure, context) when RBS::AST::Members::AttrWriter + # @sg-ignore flow based typing needs to understand case when class pattern attr_writer_to_pin(member, closure, context) when RBS::AST::Members::AttrAccessor + # @sg-ignore flow based typing needs to understand case when class pattern attr_accessor_to_pin(member, closure, context) when RBS::AST::Members::Include + # @sg-ignore flow based typing needs to understand case when class pattern include_to_pin(member, closure) when RBS::AST::Members::Prepend + # @sg-ignore flow based typing needs to understand case when class pattern prepend_to_pin(member, closure) when RBS::AST::Members::Extend + # @sg-ignore flow based typing needs to understand case when class pattern extend_to_pin(member, closure) when RBS::AST::Members::Alias + # @sg-ignore flow based typing needs to understand case when class pattern alias_to_pin(member, closure) when RBS::AST::Members::ClassInstanceVariable + # @sg-ignore flow based typing needs to understand case when class pattern civar_to_pin(member, closure) when RBS::AST::Members::ClassVariable + # @sg-ignore flow based typing needs to understand case when class pattern cvar_to_pin(member, closure) when RBS::AST::Members::InstanceVariable + # @sg-ignore flow based typing needs to understand case when class pattern ivar_to_pin(member, closure) when RBS::AST::Members::Public return Context.new(:public) when RBS::AST::Members::Private return Context.new(:private) when RBS::AST::Declarations::Base + # @sg-ignore flow based typing needs to understand case when class pattern convert_decl_to_pin(member, closure) else Solargraph.logger.warn "Skipping member type #{member.class}" @@ -238,7 +250,7 @@ def module_decl_to_pin decl # @param name [String] # @param tag [String] - # @param comments [String] + # @param comments [String, nil] # @param decl [RBS::AST::Declarations::ClassAlias, RBS::AST::Declarations::Constant, RBS::AST::Declarations::ModuleAlias] # @param base [String, nil] Optional conversion of tag to base # @@ -348,7 +360,7 @@ def global_decl_to_pin decl ["Rainbow::Presenter", :instance, "wrap_with_sgr"] => :private, } - # @param decl [RBS::AST::Members::MethodDefinition, RBS::AST::Members::AttrReader, RBS::AST::Members::AttrAccessor] + # @param decl [RBS::AST::Members::MethodDefinition, RBS::AST::Members::AttrReader, RBS::AST::Members::AttrWriter, RBS::AST::Members::AttrAccessor] # @param closure [Pin::Closure] # @param context [Context] # @param scope [Symbol] :instance or :class @@ -432,8 +444,10 @@ def method_def_to_sigs decl, pin type_location = location_decl_to_pin_location(overload.method_type.location) generics = overload.method_type.type_params.map(&:name).map(&:to_s) signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin) - block = if overload.method_type.block - block_parameters, block_return_type = parts_of_function(overload.method_type.block, pin) + rbs_block = overload.method_type.block + block = if rbs_block + # @sg-ignore flow sensitive typing needs to handle "if foo" + block_parameters, block_return_type = parts_of_function(rbs_block, pin) Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, type_location: type_location, closure: pin) end @@ -696,7 +710,7 @@ def alias_to_pin decl, closure 'NilClass' => 'nil' } - # @param type [RBS::MethodType] + # @param type [RBS::MethodType, RBS::Types::Block] # @return [String] def method_type_to_tag type if type_aliases.key?(type.type.return_type.to_s) diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 21282bf2a..c035fa20a 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -367,6 +367,7 @@ def string_nodes_in n # @return [void] def inner_tree_at node, position, stack return if node.nil? + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" here = Range.from_node(node) if here.contain?(position) stack.unshift node diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index c08d04878..d5d8aee60 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -71,6 +71,7 @@ def initialize links, node = nil, splat = false # @return [Chain] def base + # @sg-ignore Need to understand @foo ||= 123 will never be nil @base ||= Chain.new(links[0..-2]) end diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index cd89a5d85..458896d4d 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -53,6 +53,7 @@ def resolve api_map, name_pin, locals found = if head? api_map.visible_pins(locals, word, name_pin, location) else + # @type [Array] [] end return inferred_pins(found, api_map, name_pin, locals) unless found.empty? @@ -142,6 +143,7 @@ def inferred_pins pins, api_map, name_pin, locals end break if type.defined? end + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" p = p.with_single_signature(new_signature_pin) unless new_signature_pin.nil? next p.proxy(type) if type.defined? if !p.macros.empty? @@ -168,7 +170,7 @@ def inferred_pins pins, api_map, name_pin, locals # @param pin [Pin::Base] # @param api_map [ApiMap] - # @param context [ComplexType] + # @param context [ComplexType, ComplexType::UniqueType] # @param locals [::Array] # @return [Pin::Base] def process_macro pin, api_map, context, locals @@ -187,13 +189,14 @@ def process_macro pin, api_map, context, locals # @param pin [Pin::Method] # @param api_map [ApiMap] - # @param context [ComplexType] + # @param context [ComplexType, ComplexType::UniqueType] # @param locals [::Array] # @return [Pin::ProxyType] def process_directive pin, api_map, context, locals pin.directives.each do |dir| macro = api_map.named_macro(dir.tag.name) next if macro.nil? + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" result = inner_process_macro(pin, macro, api_map, context, locals) return result unless result.return_type.undefined? end @@ -203,7 +206,7 @@ def process_directive pin, api_map, context, locals # @param pin [Pin::Base] # @param macro [YARD::Tags::MacroDirective] # @param api_map [ApiMap] - # @param context [ComplexType] + # @param context [ComplexType, ComplexType::UniqueType] # @param locals [::Array] # @return [Pin::ProxyType] def inner_process_macro pin, macro, api_map, context, locals @@ -273,7 +276,7 @@ def yield_pins api_map, name_pin end # @param type [ComplexType] - # @param context [ComplexType] + # @param context [ComplexType, ComplexType::UniqueType] # @return [ComplexType] def with_params type, context return type unless type.to_s.include?('$') @@ -287,7 +290,7 @@ def fix_block_pass end # @param api_map [ApiMap] - # @param context [ComplexType] + # @param context [ComplexType, ComplexType::UniqueType] # @param block_parameter_types [::Array] # @param locals [::Array] # @return [ComplexType, nil] @@ -311,6 +314,7 @@ def find_block_pin(api_map) node_location = Solargraph::Location.from_node(block.node) return if node_location.nil? block_pins = api_map.get_block_pins + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" block_pins.find { |pin| pin.location.contain?(node_location) } end diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index 65c47c7e0..2de2f7120 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -7,13 +7,13 @@ class Source class Change include EncodingFixes - # @return [Range] + # @return [Range, nil] attr_reader :range # @return [String] attr_reader :new_text - # @param range [Range] The starting and ending positions of the change. + # @param range [Range, nil] The starting and ending positions of the change. # If nil, the original text will be overwritten. # @param new_text [String] The text to be changed. def initialize range, new_text @@ -61,6 +61,7 @@ def repair text off = Position.to_offset(text, range.start) match = result[0, off].match(/[.:]+\z/) if match + # @sg-ignore Need to figure if String#[n..m] can return nil result = result[0, off].sub(/#{match[0]}\z/, ' ' * match[0].length) + result[off..-1] end result diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 8d0231ea4..90f10dfee 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -112,6 +112,7 @@ def string? def recipient @recipient ||= begin node = recipient_node + # @sg-ignore flow sensitive typing needs to handle "if foo" node ? Cursor.new(source, Range.from_node(node).ending) : nil end end diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index b5bed1678..c41a61fb5 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -148,6 +148,7 @@ def get_signature_data_at index break if brackets > 0 or parens > 0 or squares > 0 char = @source.code[index, 1] break if char.nil? # @todo Is this the right way to handle this? + # @sg-ignore Need to figure if String#[n..m] can return nil if brackets.zero? and parens.zero? and squares.zero? and [' ', "\r", "\n", "\t"].include?(char) in_whitespace = true else @@ -173,7 +174,9 @@ def get_signature_data_at index squares += 1 end if brackets.zero? and parens.zero? and squares.zero? + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" break if ['"', "'", ',', ';', '%'].include?(char) + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" break if ['!', '?'].include?(char) && index < offset - 1 break if char == '$' if char == '@' diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 371a6114f..f27af22fc 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -97,7 +97,7 @@ def query_symbols query Pin::Search.new(document_symbols, query).results end - # @param position [Position] + # @param position [Position, Array(Integer, Integer)] # @return [Source::Cursor] def cursor_at position Source::Cursor.new(source, position) diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index 016d4b51f..f994b3591 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -164,6 +164,7 @@ def tag_complete full = match[1] if full.include?('::') if full.end_with?('::') + # @sg-ignore Need to figure if String#[n..m] can return nil result.concat api_map.get_constants(full[0..-3], *gates) else result.concat api_map.get_constants(full.split('::')[0..-2].join('::'), *gates) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index c315a407f..bdf25d863 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -18,7 +18,7 @@ class TypeChecker # @return [ApiMap] attr_reader :api_map - # @param filename [String] + # @param filename [String, nil] # @param api_map [ApiMap, nil] # @param level [Symbol] def initialize filename, api_map: nil, level: :normal @@ -32,6 +32,7 @@ def initialize filename, api_map: nil, level: :normal # @return [SourceMap] def source_map + # @sg-ignore Need to understand @foo ||= 123 will never be nil @source_map ||= api_map.source_map(filename) end @@ -270,6 +271,7 @@ def const_problems 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) + # @sg-ignore flow sensitive typing needs to handle "if foo" location = Location.new(filename, rng) locals = source_map.locals_at(location) pins = chain.define(api_map, block_pin, locals) @@ -286,9 +288,11 @@ def call_problems result = [] Solargraph::Parser::NodeMethods.call_nodes_from(source.node).each do |call| rng = Solargraph::Range.from_node(call) + # @sg-ignore flow sensitive typing needs to handle "if foo" next if @marked_ranges.any? { |d| d.contain?(rng.start) } chain = Solargraph::Parser.chain(call, filename) block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column) + # @sg-ignore flow sensitive typing needs to handle "if foo" location = Location.new(filename, rng) locals = source_map.locals_at(location) type = chain.infer(api_map, block_pin, locals) @@ -305,6 +309,7 @@ def call_problems end closest = found.typify(api_map) if found # @todo remove the internal_or_core? check at a higher-than-strict level + # @sg-ignore flow sensitive typing needs to handle || on nil types if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) unless closest.generic? || ignored_pins.include?(found) if closest.defined? @@ -380,7 +385,9 @@ def argument_problems_for chain, api_map, closure_pin, locals, location # @param location [Location] # @param locals [Array] # @param closure_pin [Pin::Closure] - # @param params [Hash{String => Hash{Symbol => String, Solargraph::ComplexType}}] + # @sg-ignore Unresolved type Hash{String => undefined] for params + # param on Solargraph::TypeChecker#signature_argument_problems_for + # @param params [Hash{String => undefined] # @param arguments [Array] # @param sig [Pin::Signature] # @param pin [Pin::Method] @@ -462,7 +469,7 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum # @param locals [Array] # @param location [Location] # @param pin [Pin::Method] - # @param params [Hash{String => Hash{Symbol => String, Solargraph::ComplexType}}] + # @param params [Hash{String => Hash{Symbol => undefined}}] # @param idx [Integer] # # @return [Array] @@ -597,6 +604,7 @@ def declared_externally? pin chain = Solargraph::Parser.chain(pin.assignment, filename) rng = Solargraph::Range.from_node(pin.assignment) block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column) + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" location = Location.new(filename, Range.from_node(pin.assignment)) locals = source_map.locals_at(location) type = chain.infer(api_map, block_pin, locals) @@ -612,6 +620,7 @@ def declared_externally? pin base = base.base end closest = found.typify(api_map) if found + # @sg-ignore flow sensitive typing needs to handle "if !foo" if !found || closest.defined? || internal?(found) return false end diff --git a/lib/solargraph/type_checker/problem.rb b/lib/solargraph/type_checker/problem.rb index 1ae65571a..f182e6a75 100644 --- a/lib/solargraph/type_checker/problem.rb +++ b/lib/solargraph/type_checker/problem.rb @@ -17,7 +17,7 @@ class Problem # @return [String, nil] attr_reader :suggestion - # @param location [Solargraph::Location] + # @param location [Solargraph::Location, nil] # @param message [String] # @param pin [Solargraph::Pin::Base, nil] # @param suggestion [String, nil] diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index bdb20fd66..483122140 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,13 +58,19 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end + # @todo Array#include? argument type should include + # nil for expressiveness like below + # @todo need a non-empty-list type # @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 "if foo" + # @todo flow sensitive typing needs to handle "if foo.nil?" # @todo flow sensitive typing needs to handle || on nil types # @todo Need to understand @foo ||= 123 will never be nil + # @todo Need to figure if String#[n..m] can return nil + # @todo Need to figure if Array#[n..m] can return 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 diff --git a/lib/solargraph/yard_map/helpers.rb b/lib/solargraph/yard_map/helpers.rb index 96bc454b5..ea8346f4c 100644 --- a/lib/solargraph/yard_map/helpers.rb +++ b/lib/solargraph/yard_map/helpers.rb @@ -5,7 +5,7 @@ module Helpers # @param code_object [YARD::CodeObjects::Base] # @param spec [Gem::Specification, nil] - # @return [Solargraph::Location, nil] + # @return [Solargraph::Location] def object_location code_object, spec if spec.nil? || code_object.nil? || code_object.file.nil? || code_object.line.nil? if code_object.namespace.is_a?(YARD::CodeObjects::NamespaceObject) diff --git a/lib/solargraph/yard_map/mapper/to_method.rb b/lib/solargraph/yard_map/mapper/to_method.rb index d8e3b8b43..161542ea2 100644 --- a/lib/solargraph/yard_map/mapper/to_method.rb +++ b/lib/solargraph/yard_map/mapper/to_method.rb @@ -35,6 +35,7 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = final_visibility ||= code_object.visibility if code_object.is_alias? origin_code_object = code_object.namespace.aliases[code_object] + # @sg-ignore flow sensitive typing needs to handle || on nil types pin = Pin::MethodAlias.new( name: name, location: location, diff --git a/lib/solargraph/yard_map/to_method.rb b/lib/solargraph/yard_map/to_method.rb index 3ecb7ac26..44abca361 100644 --- a/lib/solargraph/yard_map/to_method.rb +++ b/lib/solargraph/yard_map/to_method.rb @@ -62,7 +62,7 @@ def arg_type a # @param scope [Symbol, nil] # @param visibility [Symbol, nil] # @param closure [Solargraph::Pin::Base, nil] - # @param spec [Solargraph::Pin::Base, nil] + # @param spec [Gem::Specification, nil] # @return [Solargraph::Pin::Method] def make code_object, name = nil, scope = nil, visibility = nil, closure = nil, spec = nil closure ||= Solargraph::Pin::Namespace.new( @@ -71,6 +71,7 @@ def make code_object, name = nil, scope = nil, visibility = nil, closure = nil, ) location = object_location(code_object, spec) comments = code_object.docstring ? code_object.docstring.all.to_s : '' + # @sg-ignore flow sensitive typing needs to handle || on nil types Pin::Method.new( location: location, closure: closure, From 3074e2d7ec1f49eb3e3ed10447359414b16b7a4d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 21:55:07 -0400 Subject: [PATCH 280/930] Fix merge --- lib/solargraph/type_checker.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index bdf25d863..731966900 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -448,6 +448,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? && !arg_conforms_to?(argtype, ptype) errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") return errors @@ -487,9 +488,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? # @type [ComplexType] - argtype = argchain.infer(api_map, block_pin, locals) + argtype = argchain.infer(api_map, block_pin, locals).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}: #{par.name} expected #{ptype}, received #{argtype}") end From 600a82751bfd4286cde678a8fd91abaa413b36e7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 22:07:27 -0400 Subject: [PATCH 281/930] Annotation changes --- lib/solargraph/api_map.rb | 2 +- lib/solargraph/doc_map.rb | 4 ++-- lib/solargraph/pin/delegated_method.rb | 3 +++ lib/solargraph/pin_cache.rb | 10 +++++----- lib/solargraph/source/chain.rb | 3 --- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index f8a4eeed0..0a1dd347c 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -200,7 +200,7 @@ def cache_all!(out) # @param gemspec [Gem::Specification] # @param rebuild [Boolean] - # @param out [IO, nil] + # @param out [StringIO, IO, nil] # @return [void] def cache_gem(gemspec, rebuild: false, out: nil) @doc_map.cache(gemspec, rebuild: rebuild, out: out) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 74add4a0e..2adab9162 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -80,7 +80,7 @@ def cache_all!(out) end # @param gemspec [Gem::Specification] - # @param out [IO, nil] + # @param out [StringIO, IO, nil] # @return [void] def cache_yard_pins(gemspec, out) pins = GemPins.build_yard_pins(yard_plugins, gemspec) @@ -89,7 +89,7 @@ def cache_yard_pins(gemspec, out) end # @param gemspec [Gem::Specification] - # @param out [IO, nil] + # @param out [StringIO, IO, nil] # @return [void] def cache_rbs_collection_pins(gemspec, out) rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path) diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index ebdb020d6..3b1227bfc 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -44,6 +44,7 @@ def type_location %i[comments parameters return_type signatures].each do |method| define_method(method) do + # @sg-ignore Need to set context correctly in define_method blocks @resolved_method ? @resolved_method.send(method) : super() end end @@ -51,7 +52,9 @@ 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) end end diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index bd8f2492f..942413f14 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -49,7 +49,7 @@ def stdlib_require_path require end # @param require [String] - # @return [Array, nil] + # @return [Array] def deserialize_stdlib_require require load(stdlib_require_path(require)) end @@ -66,7 +66,7 @@ def core_path File.join(work_dir, 'core.ser') end - # @return [Array, nil] + # @return [Array] def deserialize_core load(core_path) end @@ -84,7 +84,7 @@ def yard_gem_path gemspec end # @param gemspec [Gem::Specification] - # @return [Array, nil] + # @return [Array] def deserialize_yard_gem(gemspec) load(yard_gem_path(gemspec)) end @@ -117,7 +117,7 @@ def rbs_collection_path_prefix(gemspec) # @param gemspec [Gem::Specification] # @param hash [String, nil] - # @return [Array, nil] + # @return [Array] def deserialize_rbs_collection_gem(gemspec, hash) load(rbs_collection_path(gemspec, hash)) end @@ -153,7 +153,7 @@ def serialize_combined_gem(gemspec, hash, pins) # @param gemspec [Gem::Specification] # @param hash [String, nil] - # @return [Array, nil] + # @return [Array] def deserialize_combined_gem gemspec, hash load(combined_path(gemspec, hash)) end diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index d5d8aee60..76bbe4ee9 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -230,9 +230,6 @@ def infer_from_definitions pins, context, api_map, locals @@inference_stack.pop if type.defined? if type.generic? - # @todo even at strong, no typechecking complaint - # happens when a [Pin::Base,nil] is passed into a method - # that accepts only [Pin::Namespace] as an argument type = type.resolve_generics(pin.closure, context.binder) end types << type From 8cace7c2dda9091d06581c389436564023148e65 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 14 Sep 2025 22:20:32 -0400 Subject: [PATCH 282/930] Annotation fixes --- lib/solargraph/pin/method.rb | 1 + lib/solargraph/pin_cache.rb | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 250dd2805..ddb082bcf 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -225,6 +225,7 @@ def signatures result = [] result.push generate_signature(parameters, top_type) if top_type.defined? result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty? + # @sg-ignore sensitive typing needs to handle || on nil types result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? result end diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 942413f14..e01a0d5ed 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -49,7 +49,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 @@ -66,7 +66,7 @@ def core_path File.join(work_dir, 'core.ser') end - # @return [Array] + # @return [Array, nil] def deserialize_core load(core_path) end @@ -84,7 +84,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 @@ -117,7 +117,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 @@ -153,7 +153,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 @@ -176,7 +176,7 @@ def uncache_stdlib end # @param gemspec [Gem::Specification] - # @param out [IO, nil] + # @param out [StringIO, IO, nil] # @return [void] def uncache_gem(gemspec, out: nil) uncache(yardoc_path(gemspec), out: out) From a9eadf960ad60cddb5268a68ac3e5beba0ff1a2c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 15 Sep 2025 07:48:14 -0400 Subject: [PATCH 283/930] Annotation fixes --- lib/solargraph/api_map.rb | 5 ++++- lib/solargraph/api_map/constants.rb | 2 +- lib/solargraph/api_map/source_to_yard.rb | 1 + lib/solargraph/api_map/store.rb | 7 +++---- lib/solargraph/complex_type/unique_type.rb | 9 +-------- .../convention/data_definition/data_assignment_node.rb | 1 - .../convention/data_definition/data_definition_node.rb | 2 -- .../struct_definition/struct_assignment_node.rb | 1 - .../struct_definition/struct_definition_node.rb | 2 -- lib/solargraph/diagnostics/rubocop_helpers.rb | 1 - lib/solargraph/doc_map.rb | 2 -- lib/solargraph/language_server/host.rb | 1 - lib/solargraph/language_server/host/message_worker.rb | 4 +--- .../message/extended/check_gem_version.rb | 1 + lib/solargraph/library.rb | 3 --- lib/solargraph/location.rb | 2 +- lib/solargraph/page.rb | 3 ++- lib/solargraph/parser/flow_sensitive_typing.rb | 4 ---- lib/solargraph/parser/parser_gem/class_methods.rb | 7 ++++--- lib/solargraph/parser/parser_gem/node_chainer.rb | 1 + lib/solargraph/parser/parser_gem/node_methods.rb | 6 ++---- .../parser/parser_gem/node_processors/block_node.rb | 2 -- .../parser/parser_gem/node_processors/masgn_node.rb | 3 --- .../parser/parser_gem/node_processors/sclass_node.rb | 1 - .../parser/parser_gem/node_processors/send_node.rb | 4 ---- lib/solargraph/pin/base.rb | 7 +++---- lib/solargraph/pin/base_variable.rb | 1 + lib/solargraph/pin/block.rb | 6 ++++-- lib/solargraph/pin/delegated_method.rb | 4 ++-- lib/solargraph/pin/documenting.rb | 1 - lib/solargraph/pin/keyword.rb | 1 - lib/solargraph/pin/local_variable.rb | 4 +--- lib/solargraph/pin/method.rb | 5 ++--- lib/solargraph/pin/parameter.rb | 1 + lib/solargraph/pin/signature.rb | 2 -- lib/solargraph/pin/symbol.rb | 1 - lib/solargraph/position.rb | 1 - lib/solargraph/range.rb | 2 +- lib/solargraph/rbs_map/conversions.rb | 2 +- lib/solargraph/source.rb | 4 ++-- lib/solargraph/source/chain.rb | 1 + lib/solargraph/source/chain/call.rb | 6 ++++++ lib/solargraph/source/change.rb | 1 - lib/solargraph/source/cursor.rb | 1 - lib/solargraph/source/source_chainer.rb | 3 --- lib/solargraph/source/updater.rb | 3 +++ lib/solargraph/source_map.rb | 3 +++ lib/solargraph/source_map/mapper.rb | 6 ++++-- lib/solargraph/type_checker.rb | 8 ++++---- lib/solargraph/type_checker/rules.rb | 4 ++++ lib/solargraph/workspace/config.rb | 6 ++++++ lib/solargraph/workspace/require_paths.rb | 1 - lib/solargraph/yard_map/to_method.rb | 2 ++ 53 files changed, 73 insertions(+), 89 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 0a1dd347c..f5512ddb5 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -142,7 +142,7 @@ def core_pins @@core_map.pins end - # @param name [String] + # @param name [String, nil] # @return [YARD::Tags::MacroDirective, nil] def named_macro name store.named_macros[name] @@ -640,6 +640,7 @@ def super_and_sub?(sup, sub) sub = sub.simplify_literals.to_s return true if sup == sub sc_fqns = sub + # @sg-ignore Need to disambiguate type of sup, sub while (sc = store.get_superclass(sc_fqns)) # @sg-ignore flow sensitive typing needs to handle "if foo" sc_new = store.constants.dereference(sc) @@ -754,6 +755,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if deep && scope == :instance store.get_prepends(fqns).reverse.each do |im| fqim = store.constants.dereference(im) + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil? end end @@ -792,6 +794,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false 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) + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" 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 7922fc41f..0859a0da8 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -190,7 +190,7 @@ def inner_qualify name, root, skip end end - # @param fqns [String] + # @param fqns [String, nil] # @param visibility [Array] # @param skip [Set] # @return [Array] diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index f2a5d156a..510ef17c9 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -68,6 +68,7 @@ def rake_yard store next end + # @sg-ignore Need to add nil check here code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope) { |obj| next if pin.location.nil? || pin.location.filename.nil? obj.add_file pin.location.filename, pin.location.range.start.line diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 3a764333c..ea52f1303 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -78,12 +78,13 @@ def get_methods fqns, scope: :instance, visibility: [:public] BOOLEAN_SUPERCLASS_PIN = Pin::Reference::Superclass.new(name: 'Boolean', closure: Pin::ROOT_PIN, source: :solargraph) OBJECT_SUPERCLASS_PIN = Pin::Reference::Superclass.new(name: 'Object', closure: Pin::ROOT_PIN, source: :solargraph) - # @param fqns [String] + # @param fqns [String, nil] # @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) + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" superclass_references[fqns].first || try_special_superclasses(fqns) end @@ -125,7 +126,7 @@ def get_path_pins path index.path_pin_hash[path] end - # @param fqns [String] + # @param fqns [String, nil] # @param scope [Symbol] :class or :instance # @return [Enumerable] def get_instance_variables(fqns, scope = :instance) @@ -240,9 +241,7 @@ def get_ancestors(fqns) # @sg-ignore flow sensitive typing needs to handle || on nil types superclass = ref && constants.dereference(ref) if superclass && !superclass.empty? && !visited.include?(superclass) - # @sg-ignore flow sensitive typing needs to handle "if foo" ancestors << superclass - # @sg-ignore flow sensitive typing needs to handle "if foo" queue << superclass end diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 6436f8204..400ba47f4 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -47,7 +47,6 @@ def self.parse name, substring = '', make_rooted: nil parameters_type = nil unless substring.empty? subs = ComplexType.parse(substring[1..-2], partial: true) - # @sg-ignore need a non-empty-list type 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) @@ -138,7 +137,6 @@ def determine_non_literal_name return 'NilClass' if name == 'nil' return 'Boolean' if ['true', 'false'].include?(name) return 'Symbol' if name[0] == ':' - # @sg-ignore need a non-empty-list type return 'String' if ['"', "'"].include?(name[0]) return 'Integer' if name.match?(/^-?\d+$/) name @@ -168,7 +166,7 @@ def ==(other) # # "[Expected] types where neither is possible are INVARIANT" # - # @param _situation [:method_call] + # @param _situation [:method_call, :return_type] # @param default [Symbol] The default variance to return if the type is not one of the special cases # # @return [:invariant, :covariant, :contravariant] @@ -333,12 +331,9 @@ def downcast_to_literal_if_possible def resolve_generics_from_context generics_to_resolve, context_type, resolved_generic_values: {} if name == ComplexType::GENERIC_TAG_NAME type_param = subtypes.first&.name - # @sg-ignore flow sensitive typing needs to handle "if foo" return self unless type_param && generics_to_resolve.include?(type_param) - # @sg-ignore flow sensitive typing needs to handle "if foo" unless context_type.nil? || !resolved_generic_values[type_param].nil? new_binding = true - # @sg-ignore flow sensitive typing needs to handle "if foo" resolved_generic_values[type_param] = context_type end if new_binding @@ -346,7 +341,6 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge complex_type.resolve_generics_from_context(generics_to_resolve, nil, resolved_generic_values: resolved_generic_values) end end - # @sg-ignore flow sensitive typing needs to handle "if foo" return resolved_generic_values[type_param] || self end @@ -408,7 +402,6 @@ def resolve_generics definitions, context_type ComplexType::UNDEFINED end else - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" context_type.all_params[idx] || definitions.generic_defaults[generic_name] || ComplexType::UNDEFINED end else 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 eacd848e0..e6ecb3453 100644 --- a/lib/solargraph/convention/data_definition/data_definition_node.rb +++ b/lib/solargraph/convention/data_definition/data_definition_node.rb @@ -66,7 +66,6 @@ def attributes end.compact end - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @return [Parser::AST::Node, nil] def body_node node.children[2] @@ -77,7 +76,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/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..679d46ec9 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,7 +87,6 @@ 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] diff --git a/lib/solargraph/diagnostics/rubocop_helpers.rb b/lib/solargraph/diagnostics/rubocop_helpers.rb index 549103604..f6f4c82c8 100644 --- a/lib/solargraph/diagnostics/rubocop_helpers.rb +++ b/lib/solargraph/diagnostics/rubocop_helpers.rb @@ -47,7 +47,6 @@ def generate_options filename, code # @return [String] def fix_drive_letter path return path unless path.match(/^[a-z]:/) - # @sg-ignore Need to figure if String#[n..m] can return nil path[0].upcase + path[1..-1] end diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 2adab9162..4f8bcd8d7 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -177,10 +177,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 support for RBS duck interfaces like _ToHash # @type [Array] 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 diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index dcf59c6da..4fda6cf7b 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -728,7 +728,6 @@ def requests # @return [String] def normalize_separators path return path if File::ALT_SEPARATOR.nil? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) end diff --git a/lib/solargraph/language_server/host/message_worker.rb b/lib/solargraph/language_server/host/message_worker.rb index b42718c1e..29637bcaf 100644 --- a/lib/solargraph/language_server/host/message_worker.rb +++ b/lib/solargraph/language_server/host/message_worker.rb @@ -66,6 +66,7 @@ def tick @resource.wait(@mutex) if messages.empty? next_message end + # @sg-ignore Need to add nil check here handler = @host.receive(message) handler&.send_response end @@ -86,9 +87,7 @@ def cancel_message idx = messages.find_index { |msg| msg['method'] == '$/cancelRequest' } return unless idx - # @sg-ignore flow sensitive typing needs to handle "if foo" msg = messages[idx] - # @sg-ignore flow sensitive typing needs to handle "if foo" messages.delete_at idx msg end @@ -99,7 +98,6 @@ def next_priority idx = messages.find_index do |msg| UPDATE_METHODS.include?(msg['method']) || version_dependent?(msg) end - # @sg-ignore flow sensitive typing needs to handle "if foo" idx ? messages.delete_at(idx) : messages.shift end 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..34e2d685c 100644 --- a/lib/solargraph/language_server/message/extended/check_gem_version.rb +++ b/lib/solargraph/language_server/message/extended/check_gem_version.rb @@ -58,6 +58,7 @@ def process end elsif fetched? Solargraph::Logging.logger.warn error + # @sg-ignore Need to add nil check here host.show_message(error, MessageTypes::ERROR) if params['verbose'] end set_result({ diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 1de8f8d76..c12f64e73 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -410,9 +410,7 @@ def diagnose filename name = args.shift reporter = Diagnostics.reporter(name) raise DiagnosticsError, "Diagnostics reporter #{name} does not exist" if reporter.nil? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" repargs[reporter] ||= [] - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" repargs[reporter].concat args end end @@ -639,7 +637,6 @@ def queued_gemspec_cache # @return [void] def report_cache_progress gem_name, pending @total ||= pending - # @sg-ignore Need to understand @foo ||= 123 will never be nil @total = pending if pending > @total finished = @total - pending pct = if @total.zero? diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index 26fa6be77..ebf7dfed4 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -64,8 +64,8 @@ def to_hash # @return [Location, nil] def self.from_node(node) return nil if node.nil? || node.loc.nil? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" range = Range.from_node(node) + # @sg-ignore Need to add nil check here self.new(node.loc.expression.source_buffer.name, range) end diff --git a/lib/solargraph/page.rb b/lib/solargraph/page.rb index 7952310c5..312c243d4 100644 --- a/lib/solargraph/page.rb +++ b/lib/solargraph/page.rb @@ -13,11 +13,11 @@ class Page # @param locals[Hash] # @param render_method [Proc] # @return [Binder] + # @sg-ignore https://github.com/castwide/solargraph/issues/1082 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) @@ -59,6 +59,7 @@ def initialize directory = VIEWS_PATH # @param layout [Boolean] # @param locals [Hash] @render_method = proc { |template, layout: false, locals: {}| + # @sg-ignore https://github.com/castwide/solargraph/issues/1082 binder = Binder.new(locals, @render_method) if layout Tilt::ERBTemplate.new(Page.select_template(directories, 'layout')).render(binder) do 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..8f9c836a7 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -39,6 +39,7 @@ def parser # @param source [Source] # @return [Array(Array, Array)] def map source + # @sg-ignore Need to add nil check here NodeProcessor.process(source.node, Region.new(source: source)) end @@ -100,7 +101,7 @@ def process_node *args Solargraph::Parser::NodeProcessor.process *args end - # @param node [Parser::AST::Node] + # @param node [Parser::AST::Node, nil] # @return [String, nil] def infer_literal_node_type node NodeMethods.infer_literal_node_type node @@ -111,7 +112,7 @@ def version parser.version end - # @param node [BasicObject] + # @param node [BasicObject, nil] # @return [Boolean] def is_ast_node? node node.is_a?(::Parser::AST::Node) @@ -125,7 +126,7 @@ def node_range node Range.new(st, en) end - # @param node [Parser::AST::Node] + # @param node [Parser::AST::Node, nil] # @return [Array] def string_ranges node return [] unless is_ast_node?(node) diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index a18490258..6858070e1 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -151,6 +151,7 @@ def hash_is_splatted? node def passed_block node return unless node == @node && @parent&.type == :block + # @sg-ignore Need to add nil check here NodeChainer.chain(@parent.children[2], @filename) end diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 111a7f950..9d57fe08b 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -37,7 +37,7 @@ def pack_name(node) parts end - # @param node [Parser::AST::Node] + # @param node [Parser::AST::Node, nil] # @return [String, nil] def infer_literal_node_type node return nil unless node.is_a?(AST::Node) @@ -105,7 +105,7 @@ def drill_signature node, signature signature end - # @param node [Parser::AST::Node] + # @param node [Parser::AST::Node, nil] # @return [Hash{Symbol => Chain}] def convert_hash node return {} unless Parser.is_ast_node?(node) @@ -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] @@ -226,7 +225,6 @@ def find_recipient_node cursor if node.type == :send args = node.children[2..-1] if !args.empty? - # @sg-ignore flow sensitive typing needs to handle "if foo" return node if prev && args.include?(prev) else if source.synchronized? 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 9336e2e42..d773e8e50 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/block_node.rb @@ -37,8 +37,6 @@ def process def other_class_eval? node.children[0].type == :send && node.children[0].children[1] == :class_eval && - # @sg-ignore Array#include? argument type should include - # nil for expressiveness like below [:cbase, :const].include?(node.children[0].children[0]&.type) end end 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..d3d2cef4f 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb @@ -7,7 +7,6 @@ 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 dcbbd2fe1..f9b0be49e 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -37,15 +37,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 @@ -202,7 +199,6 @@ def process_module_function # @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? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" pins.delete ref mm = Solargraph::Pin::Method.new( location: ref.location, diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 732d6a8e1..c312ecee8 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -315,6 +315,7 @@ def assert_same_count(other, attr) # @param other [self] # @param attr [::Symbol] # + # @sg-ignore Untyped method Solargraph::Pin::Base#assert_same could not be inferred # @return [undefined] def assert_same(other, attr) if other.nil? @@ -378,7 +379,6 @@ def assert_source_provided # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def comments - # @sg-ignore Need to understand @foo ||= 123 will never be nil @comments ||= '' end @@ -577,7 +577,7 @@ def realize api_map # the return type and the #proxied? setting, the proxy should be a clone # of the original. # - # @param return_type [ComplexType] + # @param return_type [ComplexType, nil] # @return [self] def proxy return_type result = dup @@ -605,7 +605,6 @@ def type_desc rbs = return_type.rooted_tags if return_type.name == 'Class' if path if rbs - # @sg-ignore flow sensitive typing needs to handle "if foo" path + ' ' + rbs else path @@ -657,7 +656,7 @@ def reset_generated! # @return [Boolean] attr_writer :proxied - # @return [ComplexType] + # @return [ComplexType, nil] attr_writer :return_type attr_writer :docstring diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 0fbf6b1c0..ef842beee 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -71,6 +71,7 @@ def return_types_from_node(parent_node, api_map) # Use the return node for inference. The clip might infer from the # first node in a method call instead of the entire call. chain = Parser.chain(node, nil, nil) + # @sg-ignore Need to add nil check here result = chain.infer(api_map, closure, clip.locals).self_to_type(closure.context) types.push result unless result.undefined? end diff --git a/lib/solargraph/pin/block.rb b/lib/solargraph/pin/block.rb index 5cb6a691d..fead17226 100644 --- a/lib/solargraph/pin/block.rb +++ b/lib/solargraph/pin/block.rb @@ -16,10 +16,9 @@ class Block < Callable # @param context [ComplexType, nil] # @param args [::Array] def initialize receiver: nil, args: [], context: nil, node: nil, **splat - super(**splat, parameters: args) + super(**splat, parameters: args, return_type: ComplexType.parse('::Proc')) @receiver = receiver @context = context - @return_type = ComplexType.parse('::Proc') @node = node end @@ -52,6 +51,7 @@ def typify_parameters(api_map) chain = Parser.chain(receiver, filename, node) clip = api_map.clip_at(location.filename, location.range.start) locals = clip.locals - [self] + # @sg-ignore Need to add nil check here meths = chain.define(api_map, closure, locals) # @todo Convert logic to use signatures meths.each do |meth| @@ -86,7 +86,9 @@ def maybe_rebind api_map return ComplexType::UNDEFINED unless receiver chain = Parser.chain(receiver, location.filename) + # @sg-ignore Need to add nil check here locals = api_map.source_map(location.filename).locals_at(location) + # @sg-ignore Need to add nil check here receiver_pin = chain.define(api_map, closure, locals).first return ComplexType::UNDEFINED unless receiver_pin diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index 3b1227bfc..e91379f3a 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -77,8 +77,8 @@ def resolve_method api_map resolver = @receiver_chain.define(api_map, self, []).first unless resolver - Solargraph.logger.warn \ - "Delegated receiver for #{path} was resolved to nil from `#{print_chain(@receiver_chain)}'" + # @sg-ignore Need to add nil check here + msg = Solargraph.logger.warn "Delegated receiver for #{path} was resolved to nil from `#{print_chain(@receiver_chain)}'" return end 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/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 9eae6cc6f..94b030b4d 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) @@ -39,6 +36,7 @@ def combine_with(other, attrs={}) def visible_at?(other_closure, other_loc) location.filename == other_loc.filename && presence.include?(other_loc.range.start) && + # @sg-ignore Need to add nil check here match_named_closure(other_closure, closure) end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index ddb082bcf..727434be8 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -177,7 +177,7 @@ def return_type end # @param parameters [::Array] - # @param return_type [ComplexType] + # @param return_type [ComplexType, nil] # @return [Signature] def generate_signature(parameters, return_type) block = nil @@ -225,7 +225,6 @@ def signatures result = [] result.push generate_signature(parameters, top_type) if top_type.defined? result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty? - # @sg-ignore sensitive typing needs to handle || on nil types result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? result end @@ -536,6 +535,7 @@ def see_reference api_map end match = comments.match(/^[ \t]*\(see (.*)\)/m) return nil if match.nil? + # @sg-ignore Need to add nil check here resolve_reference match[1], api_map end @@ -575,7 +575,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/parameter.rb b/lib/solargraph/pin/parameter.rb index 2bc30afda..3318be117 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -162,6 +162,7 @@ def return_type # @return [Integer] def index method_pin = closure + # @sg-ignore TODO: Unresolved call to parameter_names method_pin.parameter_names.index(name) end diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 1a36ef660..28be19f73 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -26,12 +26,10 @@ 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 91278c24e..18178e9b9 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/position.rb b/lib/solargraph/position.rb index 2bc6197ed..1958eff5d 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -57,7 +57,6 @@ 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/range.rb b/lib/solargraph/range.rb index 718617b5e..9cdc01a9b 100644 --- a/lib/solargraph/range.rb +++ b/lib/solargraph/range.rb @@ -78,7 +78,7 @@ def self.from_to l1, c1, l2, c2 # Get a range from a node. # - # @param node [AST::Node] + # @param node [AST::Node, nil] # @return [Range, nil] def self.from_node node if node&.loc && node.loc.expression diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index ddaf1e355..ae9b64a8f 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -805,7 +805,7 @@ def other_type_to_tag type end # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Module] - # @param namespace [Pin::Namespace] + # @param namespace [Pin::Namespace, nil] # @return [void] def add_mixins decl, namespace decl.each_mixin do |mixin| diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index c035fa20a..4906b7b34 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -223,6 +223,7 @@ class sclass module def defs if str dstr array while unless kwbegin hash block # @return [Array] def folding_ranges @folding_ranges ||= begin + # @type [Array] result = [] inner_folding_ranges node, result result.concat foldable_comment_block_ranges @@ -269,7 +270,7 @@ def first_not_empty_from line cursor end - # @param top [Parser::AST::Node] + # @param top [Parser::AST::Node, nil] # @param result [Array] # @param parent [Symbol, nil] # @return [void] @@ -367,7 +368,6 @@ def string_nodes_in n # @return [void] def inner_tree_at node, position, stack return if node.nil? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" here = Range.from_node(node) if here.contain?(position) stack.unshift node diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index 76bbe4ee9..ac7a1b64a 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -230,6 +230,7 @@ def infer_from_definitions pins, context, api_map, locals @@inference_stack.pop if type.defined? if type.generic? + # @sg-ignore Need to add nil check here type = type.resolve_generics(pin.closure, context.binder) end types << type diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 458896d4d..cc25f9c29 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -56,6 +56,10 @@ def resolve api_map, name_pin, locals # @type [Array] [] end + # @sg-ignore TODO: Wrong argument type for + # Solargraph::Source::Chain::Call#inferred_pins: pins + # expected Enumerable, received + # Array, Array return inferred_pins(found, api_map, name_pin, locals) unless found.empty? pins = name_pin.binder.each_unique_type.flat_map do |context| ns_tag = context.namespace == '' ? '' : context.namespace_type.tag @@ -221,6 +225,7 @@ def inner_process_macro pin, macro, api_map, context, locals txt.gsub!(/\$#{i}/, v.context.namespace) i += 1 end + # @sg-ignore Need to add support for all unique type match checking on lhs as well docstring = Solargraph::Source.parse_docstring(txt).to_docstring tag = docstring.tag(:return) unless tag.nil? || tag.types.nil? @@ -329,6 +334,7 @@ def block_call_type(api_map, name_pin, locals) block_context_pin = name_pin block_pin = find_block_pin(api_map) block_context_pin = block_pin.closure if block_pin + # @sg-ignore Need to add nil check here block.infer(api_map, block_context_pin, locals) end end diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index 2de2f7120..eef3fd32e 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -61,7 +61,6 @@ def repair text off = Position.to_offset(text, range.start) match = result[0, off].match(/[.:]+\z/) if match - # @sg-ignore Need to figure if String#[n..m] can return nil result = result[0, off].sub(/#{match[0]}\z/, ' ' * match[0].length) + result[off..-1] end result diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 90f10dfee..8d0231ea4 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -112,7 +112,6 @@ def string? def recipient @recipient ||= begin node = recipient_node - # @sg-ignore flow sensitive typing needs to handle "if foo" node ? Cursor.new(source, Range.from_node(node).ending) : nil end end diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index c41a61fb5..b5bed1678 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -148,7 +148,6 @@ def get_signature_data_at index break if brackets > 0 or parens > 0 or squares > 0 char = @source.code[index, 1] break if char.nil? # @todo Is this the right way to handle this? - # @sg-ignore Need to figure if String#[n..m] can return nil if brackets.zero? and parens.zero? and squares.zero? and [' ', "\r", "\n", "\t"].include?(char) in_whitespace = true else @@ -174,9 +173,7 @@ def get_signature_data_at index squares += 1 end if brackets.zero? and parens.zero? and squares.zero? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" break if ['"', "'", ',', ';', '%'].include?(char) - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" break if ['!', '?'].include?(char) && index < offset - 1 break if char == '$' if char == '@' diff --git a/lib/solargraph/source/updater.rb b/lib/solargraph/source/updater.rb index 72519e785..4fdb78330 100644 --- a/lib/solargraph/source/updater.rb +++ b/lib/solargraph/source/updater.rb @@ -38,6 +38,9 @@ def write text, nullable = false @output = text @did_nullify = can_nullify changes.each do |ch| + # @sg-ignore Wrong argument type for + # Solargraph::Source::Change#write: text expected String, + # received String, nil @output = ch.write(@output, can_nullify) end @output diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index f27af22fc..fc6990ec1 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -46,6 +46,7 @@ def initialize source # @generic T # @param klass [Class>] + # @sg-ignore Need better generic inference here # @return [Array>] def pins_by_class klass @pin_select_cache[klass] ||= pin_class_hash.select { |key, _| key <= klass }.values.flatten @@ -176,7 +177,9 @@ def map source # @return [Array] attr_writer :convention_pins + # @return [Hash{Class => Array}] def pin_class_hash + # @todo Need to support generic resolution in classify and transform_values @pin_class_hash ||= pins.to_set.classify(&:class).transform_values(&:to_a) end diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 18fdf1f88..9056a2207 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) @@ -104,6 +103,7 @@ def find_directive_line_number comment, tag, start # @param directive [YARD::Tags::Directive] # @return [void] def process_directive source_position, comment_position, directive + # @sg-ignore Need to add nil check here docstring = Solargraph::Source.parse_docstring(directive.tag.text).to_docstring location = Location.new(@filename, Range.new(comment_position, comment_position)) case directive.tag.tag_name @@ -187,6 +187,7 @@ def process_directive source_position, comment_position, directive when 'parse' begin ns = closure_at(source_position) + # @sg-ignore Need to add nil check here src = Solargraph::Source.load_string(directive.tag.text, @source.filename) region = Parser::Region.new(source: src, closure: ns) # @todo These pins may need to be marked not explicit @@ -209,6 +210,7 @@ def process_directive source_position, comment_position, directive namespace = closure_at(source_position) || Pin::ROOT_PIN namespace.domains.concat directive.tag.types unless directive.tag.types.nil? when 'override' + # @sg-ignore Need to add nil check here pins.push Pin::Reference::Override.new(location, directive.tag.name, docstring.tags, source: :source_map) when 'macro' @@ -245,10 +247,10 @@ 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| + # @sg-ignore flow sensitive typing needs to handle || on nil types src_pos = line ? Position.new(line, code_lines[line].to_s.chomp.index(/[^\s]/) || 0) : Position.new(code_lines.length, 0) com_pos = Position.new(line + 1 - comments.lines.length, 0) process_comment(src_pos, com_pos, comments) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 731966900..dd6bc32da 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -7,6 +7,8 @@ class TypeChecker autoload :Problem, 'solargraph/type_checker/problem' autoload :Rules, 'solargraph/type_checker/rules' + # @!parse + # include Solargraph::Parser::ParserGem::NodeMethods include Parser::NodeMethods # @return [String] @@ -32,7 +34,6 @@ def initialize filename, api_map: nil, level: :normal # @return [SourceMap] def source_map - # @sg-ignore Need to understand @foo ||= 123 will never be nil @source_map ||= api_map.source_map(filename) end @@ -288,7 +289,6 @@ def call_problems result = [] Solargraph::Parser::NodeMethods.call_nodes_from(source.node).each do |call| rng = Solargraph::Range.from_node(call) - # @sg-ignore flow sensitive typing needs to handle "if foo" next if @marked_ranges.any? { |d| d.contain?(rng.start) } chain = Solargraph::Parser.chain(call, filename) block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column) @@ -464,7 +464,7 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum end # @param sig [Pin::Signature] - # @param argchain [Source::Chain] + # @param argchain [Solargraph::Source::Chain] # @param api_map [ApiMap] # @param block_pin [Pin::Block] # @param locals [Array] @@ -478,6 +478,7 @@ def kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, result = [] kwargs = convert_hash(argchain.node) par = sig.parameters[idx] + # @type [Solargraph::Source::Chain] argchain = kwargs[par.name.to_sym] if par.decl == :kwrestarg || (par.decl == :optarg && idx == pin.parameters.length - 1 && par.asgn_code == '{}') result.concat kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs) @@ -490,7 +491,6 @@ 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? - # @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) result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 483122140..1bf9393b4 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,6 +58,7 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end + # @todo Need to add support for all unique type match checking on lhs as well # @todo Array#include? argument type should include # nil for expressiveness like below # @todo need a non-empty-list type @@ -65,6 +66,9 @@ def require_inferred_type_params? # @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 Need to add nil check here + # @todo Untyped method Solargraph::Pin::Base#assert_same could not be inferred + # @todo Need to validate config # @todo flow sensitive typing needs to handle "if foo" # @todo flow sensitive typing needs to handle "if foo.nil?" # @todo flow sensitive typing needs to handle || on nil types diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 3dafef3b0..d27434779 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -63,6 +63,7 @@ def calculated # namespace. It's typically used to identify available DSLs. # # @return [Array] + # @sg-ignore Need to validate config def domains raw_data['domains'] end @@ -70,6 +71,7 @@ def domains # An array of required paths to add to the workspace. # # @return [Array] + # @sg-ignore Need to validate config def required raw_data['require'] end @@ -83,6 +85,7 @@ def require_paths # An array of reporters to use for diagnostics. # + # @sg-ignore Need to validate 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 to validate config # @return [Hash] def formatter raw_data['formatter'] @@ -97,6 +101,7 @@ def formatter # An array of plugins to require. # + # @sg-ignore Need to validate 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 to validate config # @return [Integer] def max_files raw_data['max_files'] 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 diff --git a/lib/solargraph/yard_map/to_method.rb b/lib/solargraph/yard_map/to_method.rb index 44abca361..9384cf055 100644 --- a/lib/solargraph/yard_map/to_method.rb +++ b/lib/solargraph/yard_map/to_method.rb @@ -3,6 +3,7 @@ module Solargraph class YardMap class ToMethod + # @sg-ignore https://github.com/castwide/solargraph/issues/1082 module InnerMethods module_function @@ -79,6 +80,7 @@ def make code_object, name = nil, scope = nil, visibility = nil, closure = nil, comments: comments, scope: scope || code_object.scope, visibility: visibility || code_object.visibility, + # @sg-ignore https://github.com/castwide/solargraph/issues/1082 parameters: InnerMethods.get_parameters(code_object, location, comments), explicit: code_object.is_explicit?, source: :yard_map From 8b826c7d4a7ca99336348cf22639ba8856cd42bb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 15 Sep 2025 07:53:00 -0400 Subject: [PATCH 284/930] Update Rubocop todo --- .rubocop_todo.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 60f0269f6..acd8e0bcd 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -89,13 +89,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 @@ -1015,6 +1008,7 @@ Style/NestedTernaryOperator: # SupportedStyles: skip_modifier_ifs, always Style/Next: Exclude: + - 'lib/solargraph/api_map/index.rb' - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - 'lib/solargraph/pin/signature.rb' - 'lib/solargraph/source_map/clip.rb' From 733a5f7d50f1ec0d1ece8885968931ceef1a6c98 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 15 Sep 2025 08:10:14 -0400 Subject: [PATCH 285/930] Count @sg-ignores --- .../data_definition/data_definition_node.rb | 2 +- .../struct_definition_node.rb | 2 +- lib/solargraph/type_checker/rules.rb | 34 +++++++------------ 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/lib/solargraph/convention/data_definition/data_definition_node.rb b/lib/solargraph/convention/data_definition/data_definition_node.rb index e6ecb3453..f32bafc05 100644 --- a/lib/solargraph/convention/data_definition/data_definition_node.rb +++ b/lib/solargraph/convention/data_definition/data_definition_node.rb @@ -81,7 +81,7 @@ def data_node node.children[1] end - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 + # @sg-ignore Need to figure if String#[n..m] can return nil # @return [Array] def data_attribute_nodes data_node.children[2..-1] diff --git a/lib/solargraph/convention/struct_definition/struct_definition_node.rb b/lib/solargraph/convention/struct_definition/struct_definition_node.rb index 679d46ec9..5ea3e1991 100644 --- a/lib/solargraph/convention/struct_definition/struct_definition_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_definition_node.rb @@ -92,7 +92,7 @@ def struct_node node.children[1] end - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 + # @sg-ignore Need to figure if Array#[n..m] can return nil # @return [Array] def struct_attribute_nodes struct_node.children[2..-1] diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 1bf9393b4..28ce15953 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,28 +58,18 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo Need to add support for all unique type match checking on lhs as well - # @todo Array#include? argument type should include - # nil for expressiveness like below - # @todo need a non-empty-list type - # @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 Need to add nil check here - # @todo Untyped method Solargraph::Pin::Base#assert_same could not be inferred - # @todo Need to validate config - # @todo flow sensitive typing needs to handle "if foo" - # @todo flow sensitive typing needs to handle "if foo.nil?" - # @todo flow sensitive typing needs to handle || on nil types - # @todo Need to understand @foo ||= 123 will never be nil - # @todo Need to figure if String#[n..m] can return nil - # @todo Need to figure if Array#[n..m] can return 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. + # @todo TODO: Need to add support for all unique type match checking on lhs as well + # @todo 27: Need to understand @foo ||= 123 will never be nil + # @todo 18: Need to add nil check here + # @todo 16: flow sensitive typing needs to handle "if foo.nil?" + # @todo 15: flow sensitive typing needs to handle "if foo" + # @todo 15: flow sensitive typing needs to handle || on nil types + # @todo 7: Need to figure if String#[n..m] can return nil + # @todo 6: Need to validate config + # @todo 5: need boolish support for ? methods + # @todo 4: Need to figure if Array#[n..m] can return nil + # @todo 1: To make JSON strongly typed we'll need a record syntax + # @todo 1: Untyped method Solargraph::Pin::Base#assert_same could not be inferred def require_all_unique_types_match_declared? rank >= LEVELS[:typed] end From ea4001ee991870acf352bf8d650617d536a12b3e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 15 Sep 2025 10:19:47 -0400 Subject: [PATCH 286/930] Fix merge --- lib/solargraph/api_map.rb | 2 +- lib/solargraph/type_checker.rb | 2 +- lib/solargraph/type_checker/rules.rb | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 9cca70b00..3bc3244ae 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -234,7 +234,7 @@ class << self # @param loose_unions [Boolean] See #initialize # # @return [ApiMap] - def self.load_with_cache directory, out = $stdout + def self.load_with_cache directory, out = $stdout, loose_unions: true api_map = load(directory, loose_unions: loose_unions) if api_map.uncached_gemspecs.empty? logger.info { "All gems cached for #{directory}" } diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 9dc8d4fb8..d0005faeb 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -69,7 +69,7 @@ def assignment_conforms_to?(inferred, expected) 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_any_match unless rules.require_all_unique_types_match_expected? 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? diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index ac4ccb438..bacf43f86 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -62,7 +62,6 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo TODO: Need to add support for all unique type match checking on lhs as well # @todo 27: Need to understand @foo ||= 123 will never be nil # @todo 18: Need to add nil check here # @todo 16: flow sensitive typing needs to handle "if foo.nil?" @@ -74,7 +73,7 @@ def require_inferred_type_params? # @todo 4: Need to figure if Array#[n..m] can return nil # @todo 1: To make JSON strongly typed we'll need a record syntax # @todo 1: Untyped method Solargraph::Pin::Base#assert_same could not be inferred - def require_all_unique_types_match_declared? + def require_all_unique_types_match_expected? rank >= LEVELS[:typed] end From 9b057c6996d8eae89a2527e90a0b6f63f0352a0a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 15 Sep 2025 10:24:06 -0400 Subject: [PATCH 287/930] Unify rule predicates --- lib/solargraph/shell.rb | 2 +- lib/solargraph/type_checker.rb | 4 ++-- lib/solargraph/type_checker/rules.rb | 4 ---- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 1a8514922..ad12514f4 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -175,7 +175,7 @@ def typecheck *files # @sg-ignore Unresolved call to options level = options[:level].to_sym rules = Solargraph::TypeChecker::Rules.new(level) - api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout, loose_unions: rules.loose_unions?) + api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout, loose_unions: rules.require_all_unique_types_match_expected?) probcount = 0 if files.empty? files = api_map.source_maps.map(&:filename) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index d0005faeb..73f1e8545 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -97,7 +97,7 @@ class << self def load filename, level = :normal source = Solargraph::Source.load(filename) rules = Rules.new(level) - api_map = Solargraph::ApiMap.new(loose_unions: rules.loose_unions?) + api_map = Solargraph::ApiMap.new(loose_unions: rules.require_all_unique_types_match_expected?) api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end @@ -110,7 +110,7 @@ def load filename, level = :normal def load_string code, filename = nil, level = :normal, api_map: nil source = Solargraph::Source.load_string(code, filename) rules = Rules.new(level) - api_map ||= Solargraph::ApiMap.new(loose_unions: rules.loose_unions?) + api_map ||= Solargraph::ApiMap.new(loose_unions: rules.require_all_unique_types_match_expected?) api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index bacf43f86..da7d87921 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -50,10 +50,6 @@ def must_tag_or_infer? rank > LEVELS[:typed] end - def loose_unions? - rank < LEVELS[:strong] - end - def validate_tags? rank > LEVELS[:normal] end From 374bd9ff48210bfd3906ca806ed8634ba1b24f3f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 15 Sep 2025 10:30:40 -0400 Subject: [PATCH 288/930] Fix logic --- lib/solargraph/shell.rb | 3 ++- lib/solargraph/type_checker.rb | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index ad12514f4..5f1fdc277 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -175,7 +175,8 @@ def typecheck *files # @sg-ignore Unresolved call to options level = options[:level].to_sym rules = Solargraph::TypeChecker::Rules.new(level) - api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout, loose_unions: rules.require_all_unique_types_match_expected?) + api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout, + loose_unions: !rules.require_all_unique_types_match_expected?) probcount = 0 if files.empty? files = api_map.source_maps.map(&:filename) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 73f1e8545..12b4ba0de 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -29,7 +29,7 @@ def initialize filename, api_map: nil, level: :normal, rules: Rules.new(level) # @todo Smarter directory resolution @rules = rules @api_map = api_map || Solargraph::ApiMap.load(File.dirname(filename), - loose_unions: rules.loose_unions?) + loose_unions: !require_all_unique_types_match_expected?) # @type [Array] @marked_ranges = [] @@ -319,7 +319,7 @@ def call_problems if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) unless closest.generic? || ignored_pins.include?(found) if closest.defined? - result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest}") + result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest.rooted_tags}") else result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}") end From ca189dbec753735f6832eeb92b27aa71b2fea5c6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 15 Sep 2025 10:51:13 -0400 Subject: [PATCH 289/930] Handle assignments better in NodeChainer, fix foo ||= bar type --- lib/solargraph/api_map.rb | 4 ---- lib/solargraph/parser/parser_gem/node_chainer.rb | 11 +++++++++-- spec/type_checker/levels/strong_spec.rb | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 3bc3244ae..c30519e73 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -31,10 +31,6 @@ class ApiMap # type. The former is useful during completion, but the # latter is best for typechecking at higher levels. # - # Currently applies only to selecting potential methods to - # select in a Call link, but is likely to expand in the - # future to similar situations. - # def initialize pins: [], loose_unions: true @source_map_hash = {} @cache = Cache.new diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index 6858070e1..f9044cf41 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -90,10 +90,17 @@ def generate_links n elsif n.type == :const const = unpack_name(n) result.push Chain::Constant.new(const) - elsif [:lvar, :lvasgn].include?(n.type) + elsif n.type == :lvar result.push Chain::Call.new(n.children[0].to_s, Location.from_node(n)) - elsif [:ivar, :ivasgn].include?(n.type) + elsif n.type == :ivar result.push Chain::InstanceVariable.new(n.children[0].to_s) + elsif [:lvasgn, :ivasgn, :cvasgn].include?(n.type) + # We don't really care about the existing type of the lhs; + # we know what it will be after assignment. If it + # violates the existing type, that's something to deal + # with at type-checking time + new_node = n.children[1] + result.concat generate_links new_node elsif [:cvar, :cvasgn].include?(n.type) result.push Chain::ClassVariable.new(n.children[0].to_s) elsif [:gvar, :gvasgn].include?(n.type) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 57e43f26a..6acec79b0 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -19,6 +19,22 @@ def foo(idx, arr) expect(checker.problems.map(&:message)).to include("Wrong argument type for Array#[]: index expected Integer, received Integer, nil") end + it 'understands local evaluation with ||= removes nil from lhs type' do + checker = type_checker(%( + class Foo + def initialize + @bar = nil + end + + # @return [Integer] + def bar + @bar ||= 123 + end + end + )) + expect(checker.problems.map(&:message)).to eq([]) + end + it 'complains on bad @type assignment' do checker = type_checker(%( # @type [Integer] From 663af2aef1c6305768d90d03abde2b6bcfa78a14 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 15 Sep 2025 11:33:22 -0400 Subject: [PATCH 290/930] Add start of better ||= support --- lib/solargraph/api_map/store.rb | 1 - lib/solargraph/convention/data_definition.rb | 5 ++++- lib/solargraph/convention/struct_definition.rb | 5 ++++- lib/solargraph/library.rb | 1 - lib/solargraph/parser/parser_gem/node_chainer.rb | 4 ++++ lib/solargraph/pin/base.rb | 4 ---- lib/solargraph/pin/base_variable.rb | 1 - lib/solargraph/pin/closure.rb | 1 - lib/solargraph/pin/constant.rb | 1 - lib/solargraph/pin/conversions.rb | 2 -- lib/solargraph/pin/method.rb | 1 - lib/solargraph/pin/signature.rb | 2 -- lib/solargraph/shell.rb | 4 +--- lib/solargraph/source.rb | 1 - lib/solargraph/source/chain/call.rb | 2 ++ lib/solargraph/source/cursor.rb | 1 - lib/solargraph/source/source_chainer.rb | 1 - lib/solargraph/source_map.rb | 1 - lib/solargraph/type_checker.rb | 2 +- lib/solargraph/type_checker/rules.rb | 4 ++++ lib/solargraph/workspace.rb | 3 ++- 21 files changed, 22 insertions(+), 25 deletions(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index ea52f1303..e2e75090c 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -290,7 +290,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/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index 8efe27932..b969a81d5 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -78,7 +78,10 @@ def process private - # @return [DataDefintionNode, nil] + # @sg-ignore + # Solargraph::Convention::DataDefinition::NodeProcessors::DataNode#data_definition_node + # return type could not be inferred + # @return [DataDefinition::DataDefintionNode, DataDefinition::DataAssignmentNode, nil] def data_definition_node @data_definition_node ||= if DataDefintionNode.match?(node) DataDefintionNode.new(node) diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index 0556955b8..caa714148 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -102,7 +102,10 @@ def process private - # @return [StructDefintionNode, StructAssignmentNode, nil] + # @sg-ignore + # Solargraph::Convention::StructDefinition::NodeProcessors::StructNode#struct_definition_node + # return type could not be inferred + # @return [StructDefinition::StructDefintionNode, StructDefinition::StructAssignmentNode, nil] def struct_definition_node @struct_definition_node ||= if StructDefintionNode.match?(node) StructDefintionNode.new(node) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index c12f64e73..7343740ae 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -494,7 +494,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 diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index f9044cf41..f68e3678b 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -99,6 +99,10 @@ def generate_links n # we know what it will be after assignment. If it # violates the existing type, that's something to deal # with at type-checking time + # + # TODO: Need to implement this in a Link - currently + # definition support relies on it + # new_node = n.children[1] result.concat generate_links new_node elsif [:cvar, :cvasgn].include?(n.type) diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index c312ecee8..801f2cbca 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -376,7 +376,6 @@ def assert_source_provided Solargraph.assert_or_log(:source, "source not provided - #{@path} #{@source} #{self.class}") if source.nil? end - # @sg-ignore Need to understand @foo ||= 123 will never be nil # @return [String] def comments @comments ||= '' @@ -484,7 +483,6 @@ 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 @@ -516,7 +514,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') @@ -586,7 +583,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 ef842beee..bd3b4eb96 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/closure.rb b/lib/solargraph/pin/closure.rb index 84a7ad25c..af3a8a372 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -55,7 +55,6 @@ 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/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..1cab5f64d 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 diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 727434be8..f17aa5cd8 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -217,7 +217,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 diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 28be19f73..27c18e252 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -9,12 +9,10 @@ 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 diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 5f1fdc277..ff5298b68 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -172,11 +172,10 @@ def reporters # @return [void] def typecheck *files directory = File.realpath(options[:directory]) - # @sg-ignore Unresolved call to options level = options[:level].to_sym rules = Solargraph::TypeChecker::Rules.new(level) api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout, - loose_unions: !rules.require_all_unique_types_match_expected?) + loose_unions: !rules.require_all_unique_types_match_expected_on_lhs?) probcount = 0 if files.empty? files = api_map.source_maps.map(&:filename) @@ -186,7 +185,6 @@ def typecheck *files filecount = 0 time = Benchmark.measure { files.each do |file| - # @sg-ignore Unresolved call to options checker = TypeChecker.new(file, api_map: api_map, rules: rules, level: options[:level].to_sym) problems = checker.problems next if problems.empty? diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 4906b7b34..7f20e2630 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -464,7 +464,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/chain/call.rb b/lib/solargraph/source/chain/call.rb index f59763c79..646006841 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -70,6 +70,8 @@ def resolve api_map, name_pin, locals end else # grab pins which are provided by every potential context type + # @todo Need to understand reduce() + # @type [::Array] pins = name_pin.binder.each_unique_type.map do |context| ns_tag = context.namespace == '' ? '' : context.namespace_type.tag stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 8d0231ea4..a1750e303 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -50,7 +50,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..9b04c2f4d 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -96,7 +96,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_map.rb b/lib/solargraph/source_map.rb index fc6990ec1..9ab709db6 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -84,7 +84,6 @@ def conventions_environ # 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| diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 12b4ba0de..55f23b1c9 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -29,7 +29,7 @@ def initialize filename, api_map: nil, level: :normal, rules: Rules.new(level) # @todo Smarter directory resolution @rules = rules @api_map = api_map || Solargraph::ApiMap.load(File.dirname(filename), - loose_unions: !require_all_unique_types_match_expected?) + loose_unions: !rules.require_all_unique_types_match_expected_on_lhs?) # @type [Array] @marked_ranges = [] diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index da7d87921..3c7ccd53f 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -73,6 +73,10 @@ def require_all_unique_types_match_expected? rank >= LEVELS[:typed] end + def require_all_unique_types_match_expected_on_lhs? + rank >= LEVELS[:alpha] + end + def require_no_undefined_args? rank >= LEVELS[:alpha] end diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index ee78130c0..88fd96363 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -39,7 +39,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) @@ -137,6 +136,8 @@ def rbs_collection_path @gem_rbs_collection ||= read_rbs_collection_path end + # @sg-ignore Solargraph::Workspace#rbs_collection_config_path + # return type could not be inferred # @return [String, nil] def rbs_collection_config_path @rbs_collection_config_path ||= begin From 2b857a132b8b5fda20a4b274e51b63b14c82eea6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 15 Sep 2025 11:54:08 -0400 Subject: [PATCH 291/930] Recategorize and/or fix remaining ||= issues --- lib/solargraph/complex_type/unique_type.rb | 2 +- .../message/extended/check_gem_version.rb | 2 +- lib/solargraph/pin/conversions.rb | 3 +-- lib/solargraph/pin/parameter.rb | 2 +- lib/solargraph/source.rb | 6 +++--- lib/solargraph/source/chain.rb | 2 +- lib/solargraph/source/cursor.rb | 2 +- lib/solargraph/source/source_chainer.rb | 4 ++-- lib/solargraph/type_checker/rules.rb | 12 ++++++------ 9 files changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 400ba47f4..e1fb942b5 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -435,7 +435,7 @@ def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: new_key_types ||= @key_types new_subtypes ||= @subtypes make_rooted = @rooted if make_rooted.nil? - # @sg-ignore Need to understand @foo ||= 123 will never be nil + # @sg-ignore flow sensitive typing needs to handle || on nil types UniqueType.new(new_name, new_key_types, new_subtypes, rooted: make_rooted, parameters_type: parameters_type) end 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 34e2d685c..cfa89ff90 100644 --- a/lib/solargraph/language_server/message/extended/check_gem_version.rb +++ b/lib/solargraph/language_server/message/extended/check_gem_version.rb @@ -72,7 +72,7 @@ def process # @return [Gem::Version] attr_reader :current - # @sg-ignore Need to understand @foo ||= 123 will never be nil + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" # @return [Gem::Version] def available if !@available && !@fetched diff --git a/lib/solargraph/pin/conversions.rb b/lib/solargraph/pin/conversions.rb index 1cab5f64d..43050fb94 100644 --- a/lib/solargraph/pin/conversions.rb +++ b/lib/solargraph/pin/conversions.rb @@ -80,8 +80,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] + # @return [String, nil] def link_documentation @link_documentation ||= generate_link end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 8d77af443..b8723ea50 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -249,7 +249,7 @@ def typify_method_param api_map # @param api_map [ApiMap] # @param skip [::Array] # - # @sg-ignore Need to understand @foo ||= 123 will never be nil + # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" # @return [::Array] def see_reference heredoc, api_map, skip = [] heredoc.ref_tags.each do |ref| diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 7f20e2630..824eb7ea1 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -191,12 +191,12 @@ def code_for(node) # @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) stringified_comments[rng.start.line] ||= begin buff = associated_comments[rng.start.line] + # @sg-ignore flow sensitive typing needs to handle "if foo" buff ? stringify_comment_array(buff) : nil end end @@ -237,7 +237,7 @@ def synchronized? # Get a hash of comments grouped by the line numbers of the associated code. # - # @return [Hash{Integer => String}] + # @return [Hash{Integer => String, nil}] def associated_comments @associated_comments ||= begin # @type [Hash{Integer => String}] @@ -313,7 +313,7 @@ def stringify_comment_array comments # A hash of line numbers and their associated comments. # - # @return [Hash{Integer => Array, nil}] + # @return [Hash{Integer => String}] def stringified_comments @stringified_comments ||= {} end diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index ac7a1b64a..624b80032 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -71,7 +71,7 @@ def initialize links, node = nil, splat = false # @return [Chain] def base - # @sg-ignore Need to understand @foo ||= 123 will never be nil + # @sg-ignore Need to figure if Array#[n..m] can return nil @base ||= Chain.new(links[0..-2]) end diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index a1750e303..3dc37ad23 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -35,7 +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 + # @sg-ignore foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' # @return [String] def start_of_word @start_of_word ||= begin diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index 9b04c2f4d..37d4a0eaa 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -79,13 +79,13 @@ def chain # @return [Solargraph::Source] attr_reader :source - # @sg-ignore Need to understand @foo ||= 123 will never be nil + # @sg-ignore Need to figure if String#[n..m] can return nil # @return [String] def phrase @phrase ||= source.code[signature_data..offset-1] end - # @sg-ignore Need to understand @foo ||= 123 will never be nil + # @sg-ignore Need to figure if String#[n..m] can return nil # @return [String] def fixed_phrase @fixed_phrase ||= phrase[0..-(end_of_phrase.length+1)] diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 3c7ccd53f..aeb7df7ff 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,17 +58,17 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo 27: Need to understand @foo ||= 123 will never be nil # @todo 18: Need to add nil check here - # @todo 16: flow sensitive typing needs to handle "if foo.nil?" - # @todo 15: flow sensitive typing needs to handle "if foo" - # @todo 15: flow sensitive typing needs to handle || on nil types - # @todo 7: Need to figure if String#[n..m] can return nil + # @todo 18: flow sensitive typing needs to handle "if foo.nil?" + # @todo 16: flow sensitive typing needs to handle "if foo" + # @todo 16: flow sensitive typing needs to handle || on nil types + # @todo 8: Need to figure if String#[n..m] can return nil # @todo 6: Need to validate config # @todo 5: need boolish support for ? methods - # @todo 4: Need to figure if Array#[n..m] can return nil + # @todo 5: Need to figure if Array#[n..m] can return nil # @todo 1: To make JSON strongly typed we'll need a record syntax # @todo 1: Untyped method Solargraph::Pin::Base#assert_same could not be inferred + # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' def require_all_unique_types_match_expected? rank >= LEVELS[:typed] end From 46114f0316ac4ecd8d160ea3409c8de1c03db851 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 15 Sep 2025 15:54:28 -0400 Subject: [PATCH 292/930] Add initial 'if foo.nil?' capability in flow sensitive typing --- lib/solargraph/api_map.rb | 11 +-- lib/solargraph/api_map/cache.rb | 4 +- .../convention/active_support_concern.rb | 4 +- lib/solargraph/diagnostics/update_errors.rb | 2 +- lib/solargraph/doc_map.rb | 4 +- .../message/extended/check_gem_version.rb | 2 +- .../parser/flow_sensitive_typing.rb | 88 +++++++++++++++---- .../parser_gem/node_processors/ivasgn_node.rb | 3 - lib/solargraph/pin/base_variable.rb | 2 +- lib/solargraph/pin/common.rb | 1 - lib/solargraph/pin/parameter.rb | 3 +- lib/solargraph/pin/search.rb | 1 + lib/solargraph/position.rb | 2 +- lib/solargraph/source/chain/call.rb | 8 +- lib/solargraph/type_checker/rules.rb | 5 +- spec/parser/flow_sensitive_typing_spec.rb | 26 ++++++ spec/type_checker/levels/typed_spec.rb | 17 ++++ 17 files changed, 137 insertions(+), 46 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index c30519e73..ff8a550d3 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -767,7 +767,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if deep && scope == :instance store.get_prepends(fqns).reverse.each do |im| fqim = store.constants.dereference(im) - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil? end end @@ -782,9 +782,6 @@ 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) @@ -799,19 +796,19 @@ 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? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" 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}" } store.get_extends(fqns).reverse.each do |em| fqem = store.constants.dereference(em) - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil? end rooted_sc_tag = qualify_superclass(rooted_tag) unless rooted_sc_tag.nil? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" 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? diff --git a/lib/solargraph/api_map/cache.rb b/lib/solargraph/api_map/cache.rb index 0e0be4335..c69d223b4 100644 --- a/lib/solargraph/api_map/cache.rb +++ b/lib/solargraph/api_map/cache.rb @@ -8,7 +8,7 @@ def initialize @methods = {} # @type [Hash{String, Array => Array}] @constants = {} - # @type [Hash{String => String}] + # @type [Hash{String => String, nil}] @qualified_namespaces = {} # @type [Hash{String => Pin::Method}] @receiver_definitions = {} @@ -61,7 +61,7 @@ def set_constants namespace, contexts, value # @param name [String] # @param context [String] - # @return [String] + # @return [String, nil] def get_qualified_namespace name, context @qualified_namespaces["#{name}|#{context}"] end diff --git a/lib/solargraph/convention/active_support_concern.rb b/lib/solargraph/convention/active_support_concern.rb index 31f846523..cc6f02330 100644 --- a/lib/solargraph/convention/active_support_concern.rb +++ b/lib/solargraph/convention/active_support_concern.rb @@ -80,14 +80,14 @@ def process_include include_tag "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ "Handling class include include_tag=#{include_tag}" end - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" module_extends = api_map.get_extends(rooted_include_tag).map(&:parametrized_tag).map(&:to_s) logger.debug do "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ "found module extends of #{rooted_include_tag}: #{module_extends}" end return unless module_extends.include? 'ActiveSupport::Concern' - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" included_class_pins = api_map.inner_get_methods_from_reference(rooted_include_tag, namespace_pin, rooted_type, :class, visibility, deep, skip, true) logger.debug do diff --git a/lib/solargraph/diagnostics/update_errors.rb b/lib/solargraph/diagnostics/update_errors.rb index c615ece72..7bf6a9bde 100644 --- a/lib/solargraph/diagnostics/update_errors.rb +++ b/lib/solargraph/diagnostics/update_errors.rb @@ -32,7 +32,7 @@ def combine_ranges code, ranges next if rng.start.line >= code.lines.length scol = code.lines[rng.start.line].index(/[^\s]/) || 0 ecol = code.lines[rng.start.line].length - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" result.push Range.from_to(rng.start.line, scol, rng.start.line, ecol) end result diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 4f8bcd8d7..67be3b598 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -251,7 +251,7 @@ def deserialize_combined_pin_cache(gemspec) if !rbs_collection_pins.nil? && !yard_pins.nil? logger.debug { "Combining pins for #{gemspec.name}:#{gemspec.version}" } - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore sensitive typing needs to handle "unless foo.nil?" 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 @@ -330,7 +330,7 @@ def resolve_path_to_gemspecs path end end return nil if gemspec.nil? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" [gemspec_or_preference(gemspec)] end 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 cfa89ff90..6518affb4 100644 --- a/lib/solargraph/language_server/message/extended/check_gem_version.rb +++ b/lib/solargraph/language_server/message/extended/check_gem_version.rb @@ -72,8 +72,8 @@ def process # @return [Gem::Version] attr_reader :current - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" # @return [Gem::Version] + # @sg-ignore flow sensitive typing needs to handle "if foo ... else" def available if !@available && !@fetched @fetched = true diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index ffe046156..0f57d1ede 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -25,7 +25,16 @@ def process_and(and_node, true_ranges = []) rhs_presence = Range.new(before_rhs_pos, get_node_end_position(rhs)) - process_isa(lhs, true_ranges + [rhs_presence]) + process_calls(lhs, true_ranges + [rhs_presence]) + end + + # @param node [Parser::AST::Node] + # @param true_presences [Array] + # + # @return [void] + def process_calls(node, true_presences) + process_isa(node, true_presences) + process_nilp(node, true_presences) end # @param if_node [Parser::AST::Node] @@ -130,7 +139,8 @@ def add_downcast_local(pin, downcast_type_name, presence) location: pin.location, closure: pin.closure, name: pin.name, - assignment: pin.assignment, + # not sending along assignment as we are changing the type + # that it implies comments: pin.comments, presence: presence, return_type: ComplexType.try_parse(downcast_type_name), @@ -150,9 +160,12 @@ def process_facts(facts_by_pin, presences) # facts_by_pin.each_pair do |pin, facts| facts.each do |fact| - downcast_type_name = fact.fetch(:type) + downcast_type_name = fact.fetch(:type, nil) + nilp = fact.fetch(:nil, nil) presences.each do |presence| - add_downcast_local(pin, downcast_type_name, presence) + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + add_downcast_local(pin, downcast_type_name, presence) unless downcast_type_name.nil? + add_downcast_local(pin, 'nil', presence) if nilp == true end end end @@ -164,36 +177,47 @@ def process_facts(facts_by_pin, presences) # @return [void] def process_conditional(conditional_node, true_ranges) if conditional_node.type == :send - process_isa(conditional_node, true_ranges) + process_calls(conditional_node, true_ranges) elsif conditional_node.type == :and process_and(conditional_node, true_ranges) end end - # @param isa_node [Parser::AST::Node] - # @return [Array(String, String), nil] - def parse_isa(isa_node) - return unless isa_node&.type == :send && isa_node.children[1] == :is_a? + # @param call_node [Parser::AST::Node] + # @param method_name [Symbol] + # @return [Array(String, String), nil] Type name and variable name + def parse_call(call_node, method_name) + return unless call_node&.type == :send && call_node.children[1] == method_name # Check if conditional node follows this pattern: # s(:send, # s(:send, nil, :foo), :is_a?, # s(:const, nil, :Baz)), - isa_receiver = isa_node.children[0] - isa_type_name = type_name(isa_node.children[2]) - return unless isa_type_name + # + call_receiver = call_node.children[0] + call_arg = type_name(call_node.children[2]) - # check if isa_receiver looks like this: + # check if call_receiver looks like this: # s(:send, nil, :foo) # and set variable_name to :foo - if isa_receiver&.type == :send && isa_receiver.children[0].nil? && isa_receiver.children[1].is_a?(Symbol) - variable_name = isa_receiver.children[1].to_s + if call_receiver&.type == :send && call_receiver.children[0].nil? && call_receiver.children[1].is_a?(Symbol) + variable_name = call_receiver.children[1].to_s end # or like this: # (lvar :repr) - variable_name = isa_receiver.children[0].to_s if isa_receiver&.type == :lvar + variable_name = call_receiver.children[0].to_s if call_receiver&.type == :lvar return unless variable_name - [isa_type_name, variable_name] + [call_arg, variable_name] + end + + # @param isa_node [Parser::AST::Node] + # @return [Array(String, String), nil] + def parse_isa(isa_node) + call_type_name, variable_name = parse_call(isa_node, :is_a?) + + return unless call_type_name + + [call_type_name, variable_name] end # @param variable_name [String] @@ -202,7 +226,7 @@ def parse_isa(isa_node) # @return [Solargraph::Pin::LocalVariable, nil] def find_local(variable_name, position) pins = locals.select { |pin| pin.name == variable_name && pin.presence.include?(position) } - return unless pins.length == 1 + # return unless pins.length == 1 pins.first end @@ -224,6 +248,34 @@ def process_isa(isa_node, true_presences) process_facts(if_true, true_presences) end + # @param nilp_node [Parser::AST::Node] + # @return [Array(String, String), nil] + def parse_nilp(nilp_node) + parse_call(nilp_node, :nil?) + end + + # @param nilp_node [Parser::AST::Node] + # @param true_presences [Array] + # + # @return [void] + def process_nilp(nilp_node, true_presences) + nilp_arg, variable_name = parse_nilp(nilp_node) + return if variable_name.nil? || variable_name.empty? + # if .nil? got an argument, move on, this isn't the situation + # we're looking for and typechecking will cover any invalid + # ones + return unless nilp_arg.nil? + nilp_position = Range.from_node(nilp_node).start + + pin = find_local(variable_name, nilp_position) + return unless pin + + if_true = {} + if_true[pin] ||= [] + if_true[pin] << { nil: true } + process_facts(if_true, true_presences) + end + # @param node [Parser::AST::Node] # # @return [String, nil] 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..d05ecc41c 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb @@ -19,9 +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) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index bd3b4eb96..bab1f740e 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -82,7 +82,7 @@ def return_types_from_node(parent_node, api_map) # @return [ComplexType] def probe api_map unless @assignment.nil? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore sensitive typing needs to handle "unless foo.nil?" types = return_types_from_node(@assignment, api_map) return ComplexType.new(types.uniq) unless types.empty? end diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index c31f513de..464e0827e 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/parameter.rb b/lib/solargraph/pin/parameter.rb index b8723ea50..c24377f65 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -216,7 +216,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] @@ -249,8 +248,8 @@ def typify_method_param api_map # @param api_map [ApiMap] # @param skip [::Array] # - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" # @return [::Array] + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" def see_reference heredoc, api_map, skip = [] heredoc.ref_tags.each do |ref| next unless ref.tag_name == 'param' && ref.owner diff --git a/lib/solargraph/pin/search.rb b/lib/solargraph/pin/search.rb index f92978a35..0cfdcc166 100644 --- a/lib/solargraph/pin/search.rb +++ b/lib/solargraph/pin/search.rb @@ -48,6 +48,7 @@ def do_query # @param str1 [String] # @param str2 [String] + # # @return [Float] def fuzzy_string_match str1, str2 return 1.0 + (str2.length.to_f / str1.length.to_f) if str1.downcase.include?(str2.downcase) diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 1958eff5d..07857e20c 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -92,7 +92,7 @@ def self.from_offset text, offset end character = 0 if character.nil? and (cursor - offset).between?(0, 1) raise InvalidOffsetError if character.nil? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" Position.new(line, character) end diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 646006841..47a8db64c 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -159,7 +159,7 @@ def inferred_pins pins, api_map, name_pin, locals end break if type.defined? end - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" p = p.with_single_signature(new_signature_pin) unless new_signature_pin.nil? next p.proxy(type) if type.defined? if !p.macros.empty? @@ -212,7 +212,7 @@ def process_directive pin, api_map, context, locals pin.directives.each do |dir| macro = api_map.named_macro(dir.tag.name) next if macro.nil? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" result = inner_process_macro(pin, macro, api_map, context, locals) return result unless result.return_type.undefined? end @@ -329,9 +329,9 @@ def block_symbol_call_type(api_map, context, block_parameter_types, locals) # @return [Pin::Block, nil] def find_block_pin(api_map) node_location = Solargraph::Location.from_node(block.node) - return if node_location.nil? + return if node_location.nil? block_pins = api_map.get_block_pins - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" block_pins.find { |pin| pin.location.contain?(node_location) } end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index aeb7df7ff..ccf960133 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -59,10 +59,11 @@ def require_inferred_type_params? end # @todo 18: Need to add nil check here - # @todo 18: flow sensitive typing needs to handle "if foo.nil?" # @todo 16: flow sensitive typing needs to handle "if foo" # @todo 16: flow sensitive typing needs to handle || on nil types + # @todo 10: flow sensitive typing needs to handle "unless foo.nil?" # @todo 8: Need to figure if String#[n..m] can return nil + # @todo 7: flow sensitive typing needs to handle "if foo.nil? ... else" # @todo 6: Need to validate config # @todo 5: need boolish support for ? methods # @todo 5: Need to figure if Array#[n..m] can return nil @@ -70,9 +71,11 @@ def require_inferred_type_params? # @todo 1: Untyped method Solargraph::Pin::Base#assert_same could not be inferred # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' def require_all_unique_types_match_expected? + # TODO: Put this back up to strong rank >= LEVELS[:typed] end + # TODO: Take this back down to strong def require_all_unique_types_match_expected_on_lhs? rank >= LEVELS[:alpha] end diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index bf747fc76..8d9145df6 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -253,4 +253,30 @@ def baz; end clip = api_map.clip_at('test.rb', [3, 6]) expect { clip.infer.to_s }.not_to raise_error end + + it 'uses nil? in a simple if() to refine types on a simple class' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + def verify_repro(repr) + repr = 10 if floop + repr + if repr.nil? + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, 10, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + # the 10 here is arguably a bug + expect(clip.infer.rooted_tags).to eq('nil') + + pending('handling else case in flow senstiive typing') + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + end end diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index b4a62f369..1ae2fb5bd 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -4,6 +4,23 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :typed) end + it 'respects pin visibility' do + checker = type_checker(%( + class Foo + # Get the namespace's type (Class or Module). + # + # @param bar [Array] + # @return [Symbol, Integer] + def foo bar + baz = bar.first + return 123 if baz.nil? + baz + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'reports mismatched types for empty methods' do checker = type_checker(%( class Foo From 26341ba9988872dc36c390b350e1a29bbc3a2030 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 07:03:15 -0400 Subject: [PATCH 293/930] Cleanup --- lib/solargraph/parser/parser_gem/node_chainer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index f68e3678b..1e0978c7d 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -105,9 +105,9 @@ def generate_links n # new_node = n.children[1] result.concat generate_links new_node - elsif [:cvar, :cvasgn].include?(n.type) + elsif n.type == :cvar result.push Chain::ClassVariable.new(n.children[0].to_s) - elsif [:gvar, :gvasgn].include?(n.type) + elsif n.type == :gvar result.push Chain::GlobalVariable.new(n.children[0].to_s) elsif n.type == :or_asgn new_node = n.updated(n.children[0].type, n.children[0].children + [n.children[1]]) From 21c47db2cf2e62be40ddf88cb3795659d57621ee Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 07:06:13 -0400 Subject: [PATCH 294/930] Initial work on not-nil support --- .../parser/flow_sensitive_typing.rb | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 0f57d1ede..712a0ee8b 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -128,12 +128,28 @@ def self.visible_pins(pins, name, closure, location) private + # @param return_type [ComplexType, nil] + # + # @return [ComplexType, nil] + def remove_nil(return_type) + # TODO: This probably needs to be some kind of override + return if return_type.nil? + + types = return_type.items.reject { |t| t.name == 'nil' } + ComplexType.new(types) + end + # @param pin [Pin::LocalVariable] - # @param downcast_type_name [String] + # @param downcast_type_name [String, :not_nil] # @param presence [Range] # # @return [void] def add_downcast_local(pin, downcast_type_name, presence) + type = if downcast_type_name == :not_nil + remove_nil(pin.return_type) + else + ComplexType.try_parse(downcast_type_name) + end # @todo Create pin#update method new_pin = Solargraph::Pin::LocalVariable.new( location: pin.location, @@ -143,7 +159,7 @@ def add_downcast_local(pin, downcast_type_name, presence) # that it implies comments: pin.comments, presence: presence, - return_type: ComplexType.try_parse(downcast_type_name), + return_type: type, presence_certain: true, source: :flow_sensitive_typing ) @@ -162,10 +178,12 @@ def process_facts(facts_by_pin, presences) facts.each do |fact| downcast_type_name = fact.fetch(:type, nil) nilp = fact.fetch(:nil, nil) + not_nilp = fact.fetch(:not_nil, nil) presences.each do |presence| # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" add_downcast_local(pin, downcast_type_name, presence) unless downcast_type_name.nil? add_downcast_local(pin, 'nil', presence) if nilp == true + add_downcast_local(pin, :not_nil, presence) if not_nilp == true end end end @@ -180,6 +198,8 @@ def process_conditional(conditional_node, true_ranges) process_calls(conditional_node, true_ranges) elsif conditional_node.type == :and process_and(conditional_node, true_ranges) + elsif [:lvar, :ivar, :cvar, :gvar].include?(conditional_node.type) + process_variable(conditional_node, true_ranges) end end @@ -276,6 +296,30 @@ def process_nilp(nilp_node, true_presences) process_facts(if_true, true_presences) end + # @param var_node [Parser::AST::Node] + # + # @return [String, nil] Variable name referenced + def parse_variable(var_node) + return if var_node.children.length != 1 + + var_node.children[0]&.to_s + end + + def process_variable(node, true_presences) + variable_name = parse_variable(node) + return if variable_name.nil? + + var_position = Range.from_node(node).start + + pin = find_local(variable_name, var_position) + return unless pin + + if_true = {} + if_true[pin] ||= [] + if_true[pin] << { not_nil: true } + process_facts(if_true, true_presences) + end + # @param node [Parser::AST::Node] # # @return [String, nil] From 19ef39de7c06a36c7c127417278f5485f605cd31 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 07:33:02 -0400 Subject: [PATCH 295/930] Add specs --- spec/parser/flow_sensitive_typing_spec.rb | 50 +++++++++++++++++++++++ spec/type_checker/levels/strong_spec.rb | 34 +++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 8d9145df6..f814c914a 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -279,4 +279,54 @@ def verify_repro(repr) clip = api_map.clip_at('test.rb', [8, 10]) expect(clip.infer.rooted_tags).to eq('::Integer') end + + it 'uses variable in a simple if() to refine types on a simple class' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + def verify_repro(repr) + repr = 10 if floop + repr + if repr + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, 10, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + pending('handling else case in flow senstiive typing') + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses variable in a simple if() to refine types on a simple class using nil checks' do + source = Solargraph::Source.load_string(%( + def verify_repro(repr = nil) + repr = 10 if floop + repr + if repr + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [3, 8]) + expect(clip.infer.rooted_tags).to eq('10, nil') + + pending('deferring nil removal in flow senstive typing') + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + pending('supporting else in flow senstiive typing') + clip = api_map.clip_at('test.rb', [7, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end end diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 6acec79b0..9f1f4a08b 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -4,6 +4,40 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end + it 'respects pin visibility in if/nil? pattern' do + checker = type_checker(%( + class Foo + # Get the namespace's type (Class or Module). + # + # @param bar [Symbol, nil] + # @return [Symbol, Integer] + def foo bar + return 123 if bar.nil? + bar + end + end + )) + pending('recognizing returning branches in flow sensitive typing') + expect(checker.problems.map(&:message)).to be_empty + end + + it 'respects pin visibility in if/foo pattern' do + checker = type_checker(%( + class Foo + # Get the namespace's type (Class or Module). + # + # @param bar [Symbol, nil] + # @return [Symbol, Integer] + def foo bar + baz = bar + return baz if baz + 123 + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'does gives correct complaint on array dereference with nilable type' do checker = type_checker(%( # @param idx [Integer, nil] an index From fd33c6d3f67740a843eeb5cb7e3e3b8ade95a318 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 07:59:29 -0400 Subject: [PATCH 296/930] Fix up logic --- lib/solargraph/parser/flow_sensitive_typing.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 712a0ee8b..57d3acfb4 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -133,7 +133,7 @@ def self.visible_pins(pins, name, closure, location) # @return [ComplexType, nil] def remove_nil(return_type) # TODO: This probably needs to be some kind of override - return if return_type.nil? + return return_type if return_type.nil? || return_type.undefined? types = return_type.items.reject { |t| t.name == 'nil' } ComplexType.new(types) @@ -150,6 +150,7 @@ def add_downcast_local(pin, downcast_type_name, presence) else ComplexType.try_parse(downcast_type_name) end + return if type == pin.return_type # @todo Create pin#update method new_pin = Solargraph::Pin::LocalVariable.new( location: pin.location, @@ -305,12 +306,16 @@ def parse_variable(var_node) var_node.children[0]&.to_s end + # @return [void] + # @param node [Parser::AST::Node] + # @param true_presences [Array] def process_variable(node, true_presences) variable_name = parse_variable(node) return if variable_name.nil? var_position = Range.from_node(node).start + # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" pin = find_local(variable_name, var_position) return unless pin From e9b267aac7ee1d93283408e932f0bd4d50eccc8a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 08:36:15 -0400 Subject: [PATCH 297/930] Give name to remaining work --- lib/solargraph/api_map.rb | 4 ++-- lib/solargraph/api_map/constants.rb | 2 +- lib/solargraph/api_map/index.rb | 2 +- lib/solargraph/api_map/store.rb | 4 ++-- lib/solargraph/library.rb | 6 +++--- lib/solargraph/parser/parser_gem/node_chainer.rb | 2 +- lib/solargraph/pin/method.rb | 4 ++-- lib/solargraph/rbs_map/conversions.rb | 2 +- lib/solargraph/source.rb | 2 +- lib/solargraph/type_checker.rb | 4 ++-- lib/solargraph/type_checker/rules.rb | 2 +- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index ff8a550d3..0c2938f1d 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -344,7 +344,7 @@ def get_instance_variable_pins(namespace, scope = :instance) result.concat store.get_instance_variables(namespace, scope) sc_fqns = namespace while (sc = store.get_superclass(sc_fqns)) - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin sc_fqns = store.constants.dereference(sc) result.concat store.get_instance_variables(sc_fqns, scope) end @@ -654,7 +654,7 @@ def super_and_sub?(sup, sub) sc_fqns = sub # @sg-ignore Need to disambiguate type of sup, sub while (sc = store.get_superclass(sc_fqns)) - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin sc_new = store.constants.dereference(sc) # Cyclical inheritance is invalid return false if sc_new == sc_fqns diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 0859a0da8..c532f74de 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -210,7 +210,7 @@ def inner_get_constants fqns, visibility, skip end sc_ref = store.get_superclass(fqns) if sc_ref - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin fqsc = dereference(sc_ref) result.concat inner_get_constants(fqsc, [:public], skip) unless %w[Object BasicObject].include?(fqsc) end diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index f27335036..635d12b35 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -145,7 +145,7 @@ def map_overrides redefine_return_type pin, tag if new_pin new_pin.docstring.add_tag(tag) - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin redefine_return_type new_pin, tag end end diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index e2e75090c..37a8bbc87 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -96,7 +96,7 @@ def qualify_superclass fq_sub_tag return type.simplify_literals.to_s if type.literal? ref = get_superclass(fq_sub_tag) return unless ref - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin res = constants.dereference(ref) return unless res res + type.substring @@ -370,7 +370,7 @@ def uncached_qualify_superclass fq_sub_tag return type.simplify_literals.to_s if type.literal? ref = get_superclass(fq_sub_tag) return unless ref - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin res = constants.dereference(ref) return unless res res + type.substring diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 7343740ae..1f639831e 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -472,7 +472,7 @@ def next_map src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) } if src Logging.logger.debug "Mapping #{src.filename}" - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin source_map_hash[src.filename] = Solargraph::SourceMap.map(src) source_map_hash[src.filename] else @@ -566,11 +566,11 @@ def maybe_map source return unless source return unless @current == source || workspace.has_file?(source.filename) if source_map_hash.key?(source.filename) - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin new_map = Solargraph::SourceMap.map(source) source_map_hash[source.filename] = new_map else - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin source_map_hash[source.filename] = Solargraph::SourceMap.map(source) end end diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index 1e0978c7d..a8d9f4b51 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -143,7 +143,7 @@ def generate_links n result.push Source::Chain::Array.new(chained_children, n) else lit = infer_literal_node_type(n) - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin result.push (lit ? Chain::Literal.new(lit, n) : Chain::Link.new) end result diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index f17aa5cd8..1c29fe0d5 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -191,9 +191,9 @@ def generate_signature(parameters, return_type) name = p.name decl = :arg if name - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin decl = select_decl(name, false) - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin name = clean_param(name) end Pin::Parameter.new( diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index ae9b64a8f..4888bf744 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -446,7 +446,7 @@ def method_def_to_sigs decl, pin signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin) rbs_block = overload.method_type.block block = if rbs_block - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin block_parameters, block_return_type = parts_of_function(rbs_block, pin) Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, type_location: type_location, closure: pin) diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 824eb7ea1..5b584a948 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -196,7 +196,7 @@ def comments_for node rng = Range.from_node(node) stringified_comments[rng.start.line] ||= begin buff = associated_comments[rng.start.line] - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin buff ? stringify_comment_array(buff) : nil end end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 55f23b1c9..fdc3da0b4 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -278,7 +278,7 @@ def const_problems 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) - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin location = Location.new(filename, rng) locals = source_map.locals_at(location) pins = chain.define(api_map, block_pin, locals) @@ -298,7 +298,7 @@ def call_problems next if @marked_ranges.any? { |d| d.contain?(rng.start) } chain = Solargraph::Parser.chain(call, filename) block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column) - # @sg-ignore flow sensitive typing needs to handle "if foo" + # @sg-ignore flow sensitive typing needs a not-nil override pin location = Location.new(filename, rng) locals = source_map.locals_at(location) type = chain.infer(api_map, block_pin, locals) diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index ccf960133..18ab5703e 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -59,7 +59,7 @@ def require_inferred_type_params? end # @todo 18: Need to add nil check here - # @todo 16: flow sensitive typing needs to handle "if foo" + # @todo 16: flow sensitive typing needs a not-nil override pin # @todo 16: flow sensitive typing needs to handle || on nil types # @todo 10: flow sensitive typing needs to handle "unless foo.nil?" # @todo 8: Need to figure if String#[n..m] can return nil From da24490cb2881ea34c3efe2f25805ba7231f207d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 10:18:51 -0400 Subject: [PATCH 298/930] Understand nil removal via || --- lib/solargraph/api_map.rb | 1 - lib/solargraph/complex_type.rb | 5 + lib/solargraph/complex_type/unique_type.rb | 8 ++ .../convention/struct_definition.rb | 2 - lib/solargraph/diagnostics/type_check.rb | 1 - lib/solargraph/diagnostics/update_errors.rb | 1 - lib/solargraph/language_server/host.rb | 2 + .../language_server/host/dispatch.rb | 1 - .../language_server/host/sources.rb | 1 + .../parser/parser_gem/node_methods.rb | 2 +- lib/solargraph/parser/region.rb | 1 - lib/solargraph/pin/base.rb | 3 + lib/solargraph/pin/common.rb | 2 + lib/solargraph/pin/delegated_method.rb | 6 +- lib/solargraph/pin/namespace.rb | 2 - lib/solargraph/pin/signature.rb | 1 - lib/solargraph/pin_cache.rb | 1 - lib/solargraph/source/chain/or.rb | 7 +- lib/solargraph/source_map.rb | 6 +- lib/solargraph/source_map/data.rb | 14 ++- lib/solargraph/source_map/mapper.rb | 1 - lib/solargraph/workspace.rb | 1 + lib/solargraph/workspace/config.rb | 7 +- lib/solargraph/yard_map/mapper/to_method.rb | 3 +- lib/solargraph/yard_map/to_method.rb | 91 ------------------- spec/source/chain/or_spec.rb | 31 +++++++ spec/type_checker/levels/strict_spec.rb | 15 +++ spec/type_checker/levels/strong_spec.rb | 12 +++ 28 files changed, 111 insertions(+), 117 deletions(-) delete mode 100644 lib/solargraph/yard_map/to_method.rb create mode 100644 spec/source/chain/or_spec.rb diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 0c2938f1d..c5033fa13 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -131,7 +131,6 @@ def doc_map @doc_map ||= DocMap.new([], []) end - # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [::Array] def uncached_gemspecs @doc_map&.uncached_gemspecs || [] diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 54bd1d7d3..7d4352feb 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -289,6 +289,11 @@ def nullable? @items.any?(&:nil_type?) end + # @return [ComplexType] + def without_nil + ComplexType.new(@items.reject(&:nil_type?)) + end + # @return [Array] def all_params @items.first.all_params || [] diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index e1fb942b5..adf2b8744 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -319,6 +319,14 @@ def generic? name == GENERIC_TAG_NAME || all_params.any?(&:generic?) end + def nullable? + nil_type? + end + + def nil_type? + downcast_to_literal_if_possible == UniqueType::NIL + end + # @return [UniqueType] def downcast_to_literal_if_possible SINGLE_SUBTYPE.fetch(rooted_tag, self) diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index caa714148..a8176ba3b 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -135,13 +135,11 @@ def parse_comments struct_comments += "\n#{comment}" end - # @sg-ignore flow sensitive typing needs to handle || on nil types Solargraph::Source.parse_docstring(struct_comments).to_docstring end # @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/diagnostics/type_check.rb b/lib/solargraph/diagnostics/type_check.rb index 48c0f2be8..ea833860b 100644 --- a/lib/solargraph/diagnostics/type_check.rb +++ b/lib/solargraph/diagnostics/type_check.rb @@ -46,7 +46,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/diagnostics/update_errors.rb b/lib/solargraph/diagnostics/update_errors.rb index 7bf6a9bde..b6f9baa89 100644 --- a/lib/solargraph/diagnostics/update_errors.rb +++ b/lib/solargraph/diagnostics/update_errors.rb @@ -32,7 +32,6 @@ def combine_ranges code, ranges next if rng.start.line >= code.lines.length scol = code.lines[rng.start.line].index(/[^\s]/) || 0 ecol = code.lines[rng.start.line].length - # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" result.push Range.from_to(rng.start.line, scol, rng.start.line, ecol) end result diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index 4fda6cf7b..7067615c0 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -300,8 +300,10 @@ def prepare directory, name = nil end end + # @sg-ignore Need to validate config # @return [String] def command_path + # @type [String] options['commandPath'] || 'solargraph' end 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/host/sources.rb b/lib/solargraph/language_server/host/sources.rb index da0c63b93..01aa47ad4 100644 --- a/lib/solargraph/language_server/host/sources.rb +++ b/lib/solargraph/language_server/host/sources.rb @@ -55,6 +55,7 @@ def update uri, updater # @raise [FileNotFoundError] if the URI does not match an open source. # # @param uri [String] + # @sg-ignore Need a better type for 'raise' # @return [Solargraph::Source] def find uri open_source_hash[uri] || raise(Solargraph::FileNotFoundError, "Host could not find #{uri}") diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 9d57fe08b..16332d31b 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -351,7 +351,7 @@ def from_value_position_statement node, include_explicit_returns: true result.concat reduce_to_value_nodes(node.children[1..-1]) # result.push NIL_NODE unless node.children[2] elsif CONDITIONAL_ALL.include?(node.type) - result.concat reduce_to_value_nodes(node.children) + result.push node elsif ONLY_ONE_CHILD.include?(node.type) result.concat reduce_to_value_nodes([node.children[0]]) elsif FIRST_TWO_CHILDREN.include?(node.type) diff --git a/lib/solargraph/parser/region.rb b/lib/solargraph/parser/region.rb index aca052f3e..a6559bc8a 100644 --- a/lib/solargraph/parser/region.rb +++ b/lib/solargraph/parser/region.rb @@ -50,7 +50,6 @@ def filename # @param lvars [Array, nil] # @return [Region] def update closure: nil, scope: nil, visibility: nil, lvars: nil - # @sg-ignore flow sensitive typing needs to handle || on nil types Region.new( source: source, closure: closure || self.closure, diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 801f2cbca..fea008798 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -57,6 +57,9 @@ def initialize location: nil, type_location: nil, closure: nil, source: nil, nam @docstring = docstring @directives = directives @combine_priority = combine_priority + # @type [ComplexType, ComplexType::UniqueType, nil] + @binder = nil + assert_source_provided assert_location_provided diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 464e0827e..51b47e62f 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -7,6 +7,7 @@ module Common # @abstract # @return [Source, nil] # @type @closure [Pin::Closure, nil] + # @type @binder [ComplexType, ComplexType::UniqueType, nil] # @return [Location] attr_reader :location @@ -40,6 +41,7 @@ def namespace context.namespace.to_s end + # @sg-ignore Need to be able to do @type with a variable name # @return [ComplexType, ComplexType::UniqueType] def binder @binder || context diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index e91379f3a..7ce9bd2f9 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -13,9 +13,8 @@ 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). + # @param name [String, nil] + # @param receiver_method_name [String, nil] 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) super(name: name, **splat) @@ -95,6 +94,7 @@ def resolve_method api_map [receiver_type.to_s, :instance] end + # @sg-ignore Need to add nil check here method_stack = api_map.get_method_stack(receiver_path, @receiver_method_name, scope: method_scope) @resolved_method = method_stack.first end diff --git a/lib/solargraph/pin/namespace.rb b/lib/solargraph/pin/namespace.rb index c5e7b9117..6258db456 100644 --- a/lib/solargraph/pin/namespace.rb +++ b/lib/solargraph/pin/namespace.rb @@ -26,8 +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 end diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 27c18e252..4c25e028b 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -19,7 +19,6 @@ def identity attr_writer :closure - # @sg-ignore need boolish support for ? methods def dodgy_return_type_source? super || closure&.dodgy_return_type_source? end diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index e01a0d5ed..9cb93fbd8 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -9,7 +9,6 @@ class << self # 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 diff --git a/lib/solargraph/source/chain/or.rb b/lib/solargraph/source/chain/or.rb index 9264d4107..6f4c9e23d 100644 --- a/lib/solargraph/source/chain/or.rb +++ b/lib/solargraph/source/chain/or.rb @@ -15,7 +15,12 @@ def initialize links def resolve api_map, name_pin, locals types = @links.map { |link| link.infer(api_map, name_pin, locals) } - [Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.new(types.uniq), source: :chain)] + combined_type = Solargraph::ComplexType.new(types) + unless types.all?(&:nullable?) + combined_type = combined_type.without_nil + end + + [Solargraph::Pin::ProxyType.anonymous(combined_type, source: :chain)] end end end diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 9ab709db6..eb5aa9ffb 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -42,6 +42,9 @@ def initialize source @document_symbols = nil self.convention_pins = conventions_environ.pins @pin_select_cache = {} + + # @type [Array, nil] + @convention_pins = nil end # @generic T @@ -118,7 +121,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 @@ -126,7 +128,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 @@ -172,7 +173,6 @@ def map source private - # @return [Hash{Class => Array}] # @return [Array] attr_writer :convention_pins diff --git a/lib/solargraph/source_map/data.rb b/lib/solargraph/source_map/data.rb index 453520414..a881bbb6d 100644 --- a/lib/solargraph/source_map/data.rb +++ b/lib/solargraph/source_map/data.rb @@ -6,17 +6,27 @@ class Data # @param source [Solargraph::Source] def initialize source @source = source + # @type [Array, nil] + @pins = nil + # @return [Array, nil] + @locals = nil end + # @sg-ignore flow sensitive typing needs to handle || on nil types # @return [Array] def pins generate - @pins || [] + # @type [Array] + empty_pins = [] + @pins || empty_pins end - # @return [Array] + # @sg-ignore flow sensitive typing needs to handle || on nil types + # @return [Array] def locals generate + # @type [Array] + empty_locals = [] @locals || [] end diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 9056a2207..4f151b59e 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -250,7 +250,6 @@ def process_comment_directives return unless @code.encode('UTF-8', invalid: :replace, replace: '?') =~ DIRECTIVE_REGEXP code_lines = @code.lines @source.associated_comments.each do |line, comments| - # @sg-ignore flow sensitive typing needs to handle || on nil types src_pos = line ? Position.new(line, code_lines[line].to_s.chomp.index(/[^\s]/) || 0) : Position.new(code_lines.length, 0) com_pos = Position.new(line + 1 - comments.lines.length, 0) process_comment(src_pos, com_pos, comments) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 88fd96363..e720e08f1 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -156,6 +156,7 @@ def synchronize! updater source_hash[updater.filename] = source_hash[updater.filename].synchronize(updater) end + # @sg-ignore Need to validate config # @return [String] def command_path server['commandPath'] || 'solargraph' diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index d27434779..247f10c85 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -78,6 +78,7 @@ def required # An array of load paths for required paths. # + # @sg-ignore Need to validate config # @return [Array] def require_paths raw_data['require_paths'] || [] @@ -117,11 +118,11 @@ 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'] || - File.join(Dir.home, '.config', 'solargraph', 'config.yml') + out = ENV['SOLARGRAPH_GLOBAL_CONFIG'] || + File.join(Dir.home, '.config', 'solargraph', 'config.yml') + out end # @return [String] diff --git a/lib/solargraph/yard_map/mapper/to_method.rb b/lib/solargraph/yard_map/mapper/to_method.rb index 161542ea2..e2ba62e0a 100644 --- a/lib/solargraph/yard_map/mapper/to_method.rb +++ b/lib/solargraph/yard_map/mapper/to_method.rb @@ -6,6 +6,7 @@ class Mapper module ToMethod extend YardMap::Helpers + # @type [Hash{Array => Symbol}] VISIBILITY_OVERRIDE = { # YARD pays attention to 'private' statements prior to class methods but shouldn't ["Rails::Engine", :class, "find_root_with_flag"] => :public @@ -35,7 +36,6 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = final_visibility ||= code_object.visibility if code_object.is_alias? origin_code_object = code_object.namespace.aliases[code_object] - # @sg-ignore flow sensitive typing needs to handle || on nil types pin = Pin::MethodAlias.new( name: name, location: location, @@ -50,6 +50,7 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = source: :yardoc, ) else + # @sg-ignore flow sensitive typing needs to handle || on nil types pin = Pin::Method.new( location: location, closure: closure, diff --git a/lib/solargraph/yard_map/to_method.rb b/lib/solargraph/yard_map/to_method.rb deleted file mode 100644 index 9384cf055..000000000 --- a/lib/solargraph/yard_map/to_method.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true - -module Solargraph - class YardMap - class ToMethod - # @sg-ignore https://github.com/castwide/solargraph/issues/1082 - module InnerMethods - module_function - - # @param code_object [YARD::CodeObjects::Base] - # @param location [Solargraph::Location] - # @param comments [String] - # @return [Array] - def get_parameters code_object, location, comments - return [] unless code_object.is_a?(YARD::CodeObjects::MethodObject) - # HACK: Skip `nil` and `self` parameters that are sometimes emitted - # for methods defined in C - # See https://github.com/castwide/solargraph/issues/345 - code_object.parameters.select { |a| a[0] && a[0] != 'self' }.map do |a| - Solargraph::Pin::Parameter.new( - location: location, - closure: self, - comments: comments, - name: arg_name(a), - presence: nil, - decl: arg_type(a), - asgn_code: a[1], - source: :yard_map - ) - end - end - - # @param a [Array] - # @return [String] - def arg_name a - a[0].gsub(/[^a-z0-9_]/i, '') - end - - # @param a [Array] - # @return [::Symbol] - def arg_type a - if a[0].start_with?('**') - :kwrestarg - elsif a[0].start_with?('*') - :restarg - elsif a[0].start_with?('&') - :blockarg - elsif a[0].end_with?(':') - a[1] ? :kwoptarg : :kwarg - elsif a[1] - :optarg - else - :arg - end - end - end - private_constant :InnerMethods - - include Helpers - - # @param code_object [YARD::CodeObjects::Base] - # @param name [String, nil] - # @param scope [Symbol, nil] - # @param visibility [Symbol, nil] - # @param closure [Solargraph::Pin::Base, nil] - # @param spec [Gem::Specification, nil] - # @return [Solargraph::Pin::Method] - def make code_object, name = nil, scope = nil, visibility = nil, closure = nil, spec = nil - closure ||= Solargraph::Pin::Namespace.new( - name: code_object.namespace.to_s, - gates: [code_object.namespace.to_s] - ) - location = object_location(code_object, spec) - comments = code_object.docstring ? code_object.docstring.all.to_s : '' - # @sg-ignore flow sensitive typing needs to handle || on nil types - Pin::Method.new( - location: location, - closure: closure, - name: name || code_object.name.to_s, - comments: comments, - scope: scope || code_object.scope, - visibility: visibility || code_object.visibility, - # @sg-ignore https://github.com/castwide/solargraph/issues/1082 - parameters: InnerMethods.get_parameters(code_object, location, comments), - explicit: code_object.is_explicit?, - source: :yard_map - ) - end - end - end -end diff --git a/spec/source/chain/or_spec.rb b/spec/source/chain/or_spec.rb new file mode 100644 index 000000000..4c4803576 --- /dev/null +++ b/spec/source/chain/or_spec.rb @@ -0,0 +1,31 @@ +describe Solargraph::Source::Chain::Or do + it 'handles simple nil-removal' do + source = Solargraph::Source.load_string(%( + # @param a [Integer, nil] + def foo a + b = a || 10 + b + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + + clip = api_map.clip_at('test.rb', [3, 8]) + expect(clip.infer.simplify_literals.rooted_tags).to eq('::Integer') + end + + it '' do + source = Solargraph::Source.load_string(%( + def foo + out = ENV['BAR'] || + File.join(Dir.home, '.config', 'solargraph', 'config.yml') + out + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + + clip = api_map.clip_at('test.rb', [3, 8]) + expect(clip.infer.simplify_literals.rooted_tags).to eq('::String') + end +end diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 278ab099e..4cc582d67 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -5,6 +5,21 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strict) end + it 'can derive return types' 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 'ignores nilable type issues' do pending("moving nilable handling back to strong") diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 9f1f4a08b..08945e5ef 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -21,6 +21,18 @@ def foo bar expect(checker.problems.map(&:message)).to be_empty end + it 'respects || overriding nilable types' do + checker = type_checker(%( + # @return [String] + def global_config_path + out = ENV['SOLARGRAPH_GLOBAL_CONFIG'] || + File.join(Dir.home, '.config', 'solargraph', 'config.yml') + out + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'respects pin visibility in if/foo pattern' do checker = type_checker(%( class Foo From 27303362234ee6a2c8c3a350a8f850e89d4e96b3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 11:11:28 -0400 Subject: [PATCH 299/930] Update @sg-ignore accounting --- lib/solargraph/api_map.rb | 8 ++++---- lib/solargraph/api_map/constants.rb | 4 ++-- lib/solargraph/api_map/store.rb | 2 +- lib/solargraph/complex_type.rb | 2 +- lib/solargraph/complex_type/unique_type.rb | 6 +++--- lib/solargraph/convention/active_support_concern.rb | 4 ++-- .../data_definition/data_definition_node.rb | 2 +- .../struct_definition/struct_definition_node.rb | 2 +- lib/solargraph/doc_map.rb | 2 +- lib/solargraph/parser/flow_sensitive_typing.rb | 4 ++-- lib/solargraph/parser/parser_gem/node_methods.rb | 4 ++-- lib/solargraph/pin/base.rb | 2 +- lib/solargraph/pin/callable.rb | 2 +- lib/solargraph/pin/method.rb | 2 +- lib/solargraph/pin/parameter.rb | 4 ++-- lib/solargraph/position.rb | 2 +- lib/solargraph/source.rb | 2 +- lib/solargraph/source/chain.rb | 2 +- lib/solargraph/source/chain/call.rb | 6 +++--- lib/solargraph/source/source_chainer.rb | 4 ++-- lib/solargraph/source_map/clip.rb | 2 +- lib/solargraph/source_map/data.rb | 4 ++-- lib/solargraph/type_checker.rb | 2 +- lib/solargraph/type_checker/rules.rb | 12 +++++------- lib/solargraph/yard_map/mapper/to_method.rb | 2 +- 25 files changed, 43 insertions(+), 45 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index c5033fa13..c25a90c77 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -766,7 +766,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if deep && scope == :instance store.get_prepends(fqns).reverse.each do |im| fqim = store.constants.dereference(im) - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs a not-nil override pin result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil? end end @@ -795,19 +795,19 @@ 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? - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs a not-nil override pin 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}" } store.get_extends(fqns).reverse.each do |em| fqem = store.constants.dereference(em) - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs a not-nil override pin result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil? end rooted_sc_tag = qualify_superclass(rooted_tag) unless rooted_sc_tag.nil? - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs a not-nil override pin 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? diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index c532f74de..d026beafb 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -16,7 +16,7 @@ def initialize store # @param gates [Array, String>] # @return [String, nil] def resolve(name, *gates) - # @sg-ignore Need to figure if String#[n..m] can return nil + # @sg-ignore Need to add nil check here return store.get_path_pins(name[2..]).first&.path if name.start_with?('::') flat = gates.flatten @@ -144,7 +144,7 @@ def cached_collect # @return [String, nil] fully qualified namespace def qualify_namespace namespace, context_namespace = '' if namespace.start_with?('::') - # @sg-ignore Need to figure if String#[n..m] can return nil + # @sg-ignore Need to add nil check here inner_qualify(namespace[2..], '', Set.new) else inner_qualify(namespace, context_namespace, Set.new) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 37a8bbc87..caed02e7b 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -238,7 +238,7 @@ def get_ancestors(fqns) # Add superclass ref = get_superclass(current) - # @sg-ignore flow sensitive typing needs to handle || on nil types + # @sg-ignore flow sensitive typing needs to handle && with variables superclass = ref && constants.dereference(ref) if superclass && !superclass.empty? && !visited.include?(superclass) ancestors << superclass diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 7d4352feb..4c06d1cb4 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -225,7 +225,7 @@ def duck_types_match? api_map, expected, inferred expected.each do |exp| next unless exp.duck_type? quack = exp.to_s[1..] - # @sg-ignore Need to figure if String#[n..m] can return nil + # @sg-ignore Need to add nil check here return false if api_map.get_method_stack(inferred.namespace, quack, scope: inferred.scope).empty? end true diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index adf2b8744..4c0b371de 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -63,7 +63,7 @@ def self.parse name, substring = '', make_rooted: nil subtypes.concat subs end end - # @sg-ignore Need to figure if String#[n..m] can return nil + # @sg-ignore Need to add nil check here new(name, key_types, subtypes, rooted: rooted, parameters_type: parameters_type) end @@ -257,7 +257,7 @@ def desc rooted_tags end - # @sg-ignore flow sensitive typing needs to handle || on nil types + # @sg-ignore Need better if/elseanalysis # @return [String] def to_rbs if duck_type? @@ -443,7 +443,7 @@ def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: new_key_types ||= @key_types new_subtypes ||= @subtypes make_rooted = @rooted if make_rooted.nil? - # @sg-ignore flow sensitive typing needs to handle || on nil types + # @sg-ignore Need better ||= handling on locals UniqueType.new(new_name, new_key_types, new_subtypes, rooted: make_rooted, parameters_type: parameters_type) end diff --git a/lib/solargraph/convention/active_support_concern.rb b/lib/solargraph/convention/active_support_concern.rb index cc6f02330..fb27286cd 100644 --- a/lib/solargraph/convention/active_support_concern.rb +++ b/lib/solargraph/convention/active_support_concern.rb @@ -80,14 +80,14 @@ def process_include include_tag "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ "Handling class include include_tag=#{include_tag}" end - # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" + # @sg-ignore flow sensitive typing needs a not-nil override pin module_extends = api_map.get_extends(rooted_include_tag).map(&:parametrized_tag).map(&:to_s) logger.debug do "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ "found module extends of #{rooted_include_tag}: #{module_extends}" end return unless module_extends.include? 'ActiveSupport::Concern' - # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" + # @sg-ignore flow sensitive typing needs a not-nil override pin included_class_pins = api_map.inner_get_methods_from_reference(rooted_include_tag, namespace_pin, rooted_type, :class, visibility, deep, skip, true) logger.debug do diff --git a/lib/solargraph/convention/data_definition/data_definition_node.rb b/lib/solargraph/convention/data_definition/data_definition_node.rb index f32bafc05..901ce48d9 100644 --- a/lib/solargraph/convention/data_definition/data_definition_node.rb +++ b/lib/solargraph/convention/data_definition/data_definition_node.rb @@ -81,7 +81,7 @@ def data_node node.children[1] end - # @sg-ignore Need to figure if String#[n..m] can return nil + # @sg-ignore Need to add nil check here # @return [Array] def data_attribute_nodes data_node.children[2..-1] diff --git a/lib/solargraph/convention/struct_definition/struct_definition_node.rb b/lib/solargraph/convention/struct_definition/struct_definition_node.rb index 5ea3e1991..8f03f1fcb 100644 --- a/lib/solargraph/convention/struct_definition/struct_definition_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_definition_node.rb @@ -92,7 +92,7 @@ def struct_node node.children[1] end - # @sg-ignore Need to figure if Array#[n..m] can return nil + # @sg-ignore Need to add nil check here # @return [Array] def struct_attribute_nodes struct_node.children[2..-1] diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 67be3b598..a493f0169 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -330,7 +330,7 @@ def resolve_path_to_gemspecs path end end return nil if gemspec.nil? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" + # @sg-ignore flow sensitive typing needs a not-nil override pin [gemspec_or_preference(gemspec)] end diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 57d3acfb4..aa48e6321 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -181,7 +181,7 @@ def process_facts(facts_by_pin, presences) nilp = fact.fetch(:nil, nil) not_nilp = fact.fetch(:not_nil, nil) presences.each do |presence| - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs a not-nil override pin add_downcast_local(pin, downcast_type_name, presence) unless downcast_type_name.nil? add_downcast_local(pin, 'nil', presence) if nilp == true add_downcast_local(pin, :not_nil, presence) if not_nilp == true @@ -315,7 +315,7 @@ def process_variable(node, true_presences) var_position = Range.from_node(node).start - # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" + # @sg-ignore flow sensitive typing needs a not-nil override pin pin = find_local(variable_name, var_position) return unless pin diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 16332d31b..50f04277b 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -347,7 +347,7 @@ def from_value_position_statement node, include_explicit_returns: true if COMPOUND_STATEMENTS.include?(node.type) result.concat from_value_position_compound_statement node elsif CONDITIONAL_ALL_BUT_FIRST.include?(node.type) - # @sg-ignore Need to figure if Array#[n..m] can return nil + # @sg-ignore Need to add nil check here result.concat reduce_to_value_nodes(node.children[1..-1]) # result.push NIL_NODE unless node.children[2] elsif CONDITIONAL_ALL.include?(node.type) @@ -463,7 +463,7 @@ def reduce_to_value_nodes nodes elsif COMPOUND_STATEMENTS.include?(node.type) result.concat from_value_position_compound_statement(node) elsif CONDITIONAL_ALL_BUT_FIRST.include?(node.type) - # @sg-ignore Need to figure if Array#[n..m] can return nil + # @sg-ignore Need to add nil check here result.concat reduce_to_value_nodes(node.children[1..-1]) elsif node.type == :return result.concat reduce_to_value_nodes([node.children[0]]) diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index fea008798..22eda8131 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -81,7 +81,7 @@ def closure # @param other [self] # @param attrs [Hash{::Symbol => Object}] # - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs a not-nil override pin # @return [self] def combine_with(other, attrs={}) raise "tried to combine #{other.class} with #{self.class}" unless other.class == self.class diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 8735ec953..c9146f0d0 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -78,7 +78,7 @@ def choose_parameters(other) end end - # @sg-ignore Need to figure if Array#[n..m] can return nil + # @sg-ignore Need to add nil check here # @return [Array] def blockless_parameters if parameters.last&.block? diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 1c29fe0d5..bde3d8f77 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -76,7 +76,7 @@ def combine_signatures(other) end end - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs a not-nil override pin def combine_with(other, attrs = {}) priority_choice = choose_priority(other) return priority_choice unless priority_choice.nil? diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index c24377f65..4a71fb23d 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -195,7 +195,7 @@ def compatible_arg?(atype, api_map) ptype.generic? end - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs a not-nil override pin def documentation tag = param_tag return '' if tag.nil? || tag.text.nil? @@ -249,7 +249,7 @@ def typify_method_param api_map # @param skip [::Array] # # @return [::Array] - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs a not-nil override pin def see_reference heredoc, api_map, skip = [] heredoc.ref_tags.each do |ref| next unless ref.tag_name == 'param' && ref.owner diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 07857e20c..0b4985de1 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -92,7 +92,7 @@ def self.from_offset text, offset end character = 0 if character.nil? and (cursor - offset).between?(0, 1) raise InvalidOffsetError if character.nil? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" + # @sg-ignore flow sensitive typing needs a not-nil override pin Position.new(line, character) end diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 5b584a948..19614016c 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -61,7 +61,7 @@ def at range # @param l2 [Integer] # @param c2 [Integer] # - # @sg-ignore Need to figure if String#[n..m] can return nil + # @sg-ignore Need to add nil check here # @return [String] def from_to l1, c1, l2, c2 b = Solargraph::Position.line_char_to_offset(code, l1, c1) diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index 624b80032..97f9acc41 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -71,7 +71,7 @@ def initialize links, node = nil, splat = false # @return [Chain] def base - # @sg-ignore Need to figure if Array#[n..m] can return nil + # @sg-ignore Need to add nil check here @base ||= Chain.new(links[0..-2]) end diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 47a8db64c..3bdbdfaf0 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -159,7 +159,7 @@ def inferred_pins pins, api_map, name_pin, locals end break if type.defined? end - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs a not-nil override pin p = p.with_single_signature(new_signature_pin) unless new_signature_pin.nil? next p.proxy(type) if type.defined? if !p.macros.empty? @@ -212,7 +212,7 @@ def process_directive pin, api_map, context, locals pin.directives.each do |dir| macro = api_map.named_macro(dir.tag.name) next if macro.nil? - # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" + # @sg-ignore flow sensitive typing needs a not-nil override pin result = inner_process_macro(pin, macro, api_map, context, locals) return result unless result.return_type.undefined? end @@ -331,7 +331,7 @@ def find_block_pin(api_map) node_location = Solargraph::Location.from_node(block.node) return if node_location.nil? block_pins = api_map.get_block_pins - # @sg-ignore flow sensitive typing needs to handle "if foo.nil? ... else" + # @sg-ignore flow sensitive typing needs a not-nil override pin block_pins.find { |pin| pin.location.contain?(node_location) } end diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index 37d4a0eaa..354db4957 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -79,13 +79,13 @@ def chain # @return [Solargraph::Source] attr_reader :source - # @sg-ignore Need to figure if String#[n..m] can return nil + # @sg-ignore Need to add nil check here # @return [String] def phrase @phrase ||= source.code[signature_data..offset-1] end - # @sg-ignore Need to figure if String#[n..m] can return nil + # @sg-ignore Need to add nil check here # @return [String] def fixed_phrase @fixed_phrase ||= phrase[0..-(end_of_phrase.length+1)] diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index f994b3591..681542915 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -164,7 +164,7 @@ def tag_complete full = match[1] if full.include?('::') if full.end_with?('::') - # @sg-ignore Need to figure if String#[n..m] can return nil + # @sg-ignore Need to add nil check here result.concat api_map.get_constants(full[0..-3], *gates) else result.concat api_map.get_constants(full.split('::')[0..-2].join('::'), *gates) diff --git a/lib/solargraph/source_map/data.rb b/lib/solargraph/source_map/data.rb index a881bbb6d..93ed147d0 100644 --- a/lib/solargraph/source_map/data.rb +++ b/lib/solargraph/source_map/data.rb @@ -12,7 +12,7 @@ def initialize source @locals = nil end - # @sg-ignore flow sensitive typing needs to handle || on nil types + # @sg-ignore Need better || handling on ivars # @return [Array] def pins generate @@ -21,7 +21,7 @@ def pins @pins || empty_pins end - # @sg-ignore flow sensitive typing needs to handle || on nil types + # @sg-ignore Need better || handling on ivars # @return [Array] def locals generate diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index fdc3da0b4..a5838c6d2 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -315,7 +315,7 @@ def call_problems end closest = found.typify(api_map) if found # @todo remove the internal_or_core? check at a higher-than-strict level - # @sg-ignore flow sensitive typing needs to handle || on nil types + # @sg-ignore flow sensitive typing needs a not-nil override pin if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) unless closest.generic? || ignored_pins.include?(found) if closest.defined? diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 18ab5703e..e05d91971 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,18 +58,16 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo 18: Need to add nil check here - # @todo 16: flow sensitive typing needs a not-nil override pin - # @todo 16: flow sensitive typing needs to handle || on nil types - # @todo 10: flow sensitive typing needs to handle "unless foo.nil?" - # @todo 8: Need to figure if String#[n..m] can return nil - # @todo 7: flow sensitive typing needs to handle "if foo.nil? ... else" + # @todo 34: flow sensitive typing needs a not-nil override pin + # @todo 33: Need to add nil check here # @todo 6: Need to validate config # @todo 5: need boolish support for ? methods - # @todo 5: Need to figure if Array#[n..m] can return nil + # @todo 2: Need better || handling on ivars + # @todo 1: flow sensitive typing needs to handle && with variables # @todo 1: To make JSON strongly typed we'll need a record syntax # @todo 1: Untyped method Solargraph::Pin::Base#assert_same could not be inferred # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' + # @todo 1: Need better ||= handling on locals def require_all_unique_types_match_expected? # TODO: Put this back up to strong rank >= LEVELS[:typed] diff --git a/lib/solargraph/yard_map/mapper/to_method.rb b/lib/solargraph/yard_map/mapper/to_method.rb index e2ba62e0a..d4547a233 100644 --- a/lib/solargraph/yard_map/mapper/to_method.rb +++ b/lib/solargraph/yard_map/mapper/to_method.rb @@ -50,7 +50,7 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = source: :yardoc, ) else - # @sg-ignore flow sensitive typing needs to handle || on nil types + # @sg-ignore Need to add nil check here pin = Pin::Method.new( location: location, closure: closure, From 2aaacd7f097301e0dbf32fca8c80823af7fd1b43 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 11:15:31 -0400 Subject: [PATCH 300/930] Linting fixes --- lib/solargraph/parser/parser_gem/node_methods.rb | 3 --- spec/source/chain/or_spec.rb | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 50f04277b..8321acd21 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -302,7 +302,6 @@ def repaired_find_recipient_node cursor module DeepInference class << self CONDITIONAL_ALL_BUT_FIRST = [:if, :unless] - CONDITIONAL_ALL = [:or] ONLY_ONE_CHILD = [:return] FIRST_TWO_CHILDREN = [:rescue] COMPOUND_STATEMENTS = [:begin, :kwbegin] @@ -350,8 +349,6 @@ def from_value_position_statement node, include_explicit_returns: true # @sg-ignore Need to add nil check here result.concat reduce_to_value_nodes(node.children[1..-1]) # result.push NIL_NODE unless node.children[2] - elsif CONDITIONAL_ALL.include?(node.type) - result.push node elsif ONLY_ONE_CHILD.include?(node.type) result.concat reduce_to_value_nodes([node.children[0]]) elsif FIRST_TWO_CHILDREN.include?(node.type) diff --git a/spec/source/chain/or_spec.rb b/spec/source/chain/or_spec.rb index 4c4803576..547dcaec9 100644 --- a/spec/source/chain/or_spec.rb +++ b/spec/source/chain/or_spec.rb @@ -14,7 +14,7 @@ def foo a expect(clip.infer.simplify_literals.rooted_tags).to eq('::Integer') end - it '' do + it 'removes nil from more complex cases' do source = Solargraph::Source.load_string(%( def foo out = ENV['BAR'] || From 55972e2b8a1f66493f1df0f2516245419e21d85c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 11:22:12 -0400 Subject: [PATCH 301/930] Remove duplicate method --- lib/solargraph/complex_type/unique_type.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 4c0b371de..dc544593b 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -323,10 +323,6 @@ def nullable? nil_type? end - def nil_type? - downcast_to_literal_if_possible == UniqueType::NIL - end - # @return [UniqueType] def downcast_to_literal_if_possible SINGLE_SUBTYPE.fetch(rooted_tag, self) From 10261b7d903db239d1e123a7803a8ce5124ee8ee Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 11:45:40 -0400 Subject: [PATCH 302/930] spec updates --- spec/parser/node_methods_spec.rb | 6 ++---- spec/type_checker/levels/strict_spec.rb | 22 +++++++++++++++++++++- spec/type_checker/levels/strong_spec.rb | 1 + 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/spec/parser/node_methods_spec.rb b/spec/parser/node_methods_spec.rb index f9504b584..675f15ce4 100644 --- a/spec/parser/node_methods_spec.rb +++ b/spec/parser/node_methods_spec.rb @@ -187,9 +187,7 @@ it "handles top 'or' nodes" do node = Solargraph::Parser.parse('1 || "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.length).to eq(2) - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(rets[0])).to eq('::Integer') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(rets[1])).to eq('::String') + expect(rets.length).to eq(1) end it "handles nested 'and' nodes" do @@ -338,7 +336,7 @@ it "handles top 'or' nodes" do node = Solargraph::Parser.parse('1 || "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:int, :str]) + expect(rets.map(&:type)).to eq([:or]) end it "handles nested 'and' nodes from return" do diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 4cc582d67..6f3992fed 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -7,7 +7,7 @@ def type_checker(code) it 'can derive return types' do checker = type_checker(%( - # @param a [String] + # @param a [String, nil] # @return [void] def foo(a); end @@ -1009,6 +1009,25 @@ 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 + else + nil + end + end + end + )) + expect(checker.problems.map(&:message)).to eq([]) + end + it 'does not complain on defaulted reader with detailed expression' do checker = type_checker(%( class Foo @@ -1023,6 +1042,7 @@ def bar end end )) + pending('implicit nil being understood from implicit else') expect(checker.problems.map(&:message)).to eq([]) end end diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 08945e5ef..551290e4c 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -47,6 +47,7 @@ def foo bar end end )) + pending 'not-nil override pin available and used by flow sensitive typing' expect(checker.problems.map(&:message)).to be_empty end From 5f0101aaf9f98ed023e60f96ea52a7e9903e8250 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 12:56:40 -0400 Subject: [PATCH 303/930] Fix flubbed initialization --- lib/solargraph/source_map.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index eb5aa9ffb..4ddcf2a03 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -34,6 +34,8 @@ def locals # @param source [Source] def initialize source @source = source + # @type [Array, nil] + @convention_pins = nil conventions_environ.merge Convention.for_local(self) unless filename.nil? # FIXME: unmemoizing the document_symbols in case it was called and memoized from any of conventions above @@ -43,8 +45,6 @@ def initialize source self.convention_pins = conventions_environ.pins @pin_select_cache = {} - # @type [Array, nil] - @convention_pins = nil end # @generic T From f483e738aaa33f82b0d03261287e2f197b089b14 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 13:36:58 -0400 Subject: [PATCH 304/930] Fixes --- lib/solargraph/source_map.rb | 1 - spec/type_checker/levels/alpha_spec.rb | 2 -- 2 files changed, 3 deletions(-) diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 4ddcf2a03..c235c0b94 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -44,7 +44,6 @@ def initialize source @document_symbols = nil self.convention_pins = conventions_environ.pins @pin_select_cache = {} - end # @generic T diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 7d3833469..b0505e091 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -6,8 +6,6 @@ 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 From a7b11e6a8e58689b3568264672762fe1d9280474 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 20:02:24 -0400 Subject: [PATCH 305/930] Revert vasgn node changes --- .../parser/parser_gem/node_chainer.rb | 21 +++++-------------- .../source/chain/instance_variable.rb | 6 ++++++ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index a8d9f4b51..4f7428ac8 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -90,24 +90,13 @@ def generate_links n elsif n.type == :const const = unpack_name(n) result.push Chain::Constant.new(const) - elsif n.type == :lvar + elsif [:lvar, :lvasgn].include?(n.type) result.push Chain::Call.new(n.children[0].to_s, Location.from_node(n)) - elsif n.type == :ivar - result.push Chain::InstanceVariable.new(n.children[0].to_s) - elsif [:lvasgn, :ivasgn, :cvasgn].include?(n.type) - # We don't really care about the existing type of the lhs; - # we know what it will be after assignment. If it - # violates the existing type, that's something to deal - # with at type-checking time - # - # TODO: Need to implement this in a Link - currently - # definition support relies on it - # - new_node = n.children[1] - result.concat generate_links new_node - elsif n.type == :cvar + elsif [:ivar, :ivasgn].include?(n.type) + result.push Chain::InstanceVariable.new(n.children[0].to_s, n) + elsif [:cvar, :cvasgn].include?(n.type) result.push Chain::ClassVariable.new(n.children[0].to_s) - elsif n.type == :gvar + elsif [:gvar, :gvasgn].include?(n.type) result.push Chain::GlobalVariable.new(n.children[0].to_s) elsif n.type == :or_asgn new_node = n.updated(n.children[0].type, n.children[0].children + [n.children[1]]) diff --git a/lib/solargraph/source/chain/instance_variable.rb b/lib/solargraph/source/chain/instance_variable.rb index ea09f5578..94676733f 100644 --- a/lib/solargraph/source/chain/instance_variable.rb +++ b/lib/solargraph/source/chain/instance_variable.rb @@ -4,6 +4,12 @@ module Solargraph class Source class Chain class InstanceVariable < Link + def initialize word, node + super(word) + @node = node + end + + # @sg-ignore flow sensitive typing needs a not-nil override pin def resolve api_map, name_pin, locals api_map.get_instance_variable_pins(name_pin.binder.namespace, name_pin.binder.scope).select{|p| p.name == word} end From 9768c87bbac6a5a8a4346bad2953b307b883ea3a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 20:04:32 -0400 Subject: [PATCH 306/930] Linting fixes --- lib/solargraph/convention/data_definition.rb | 3 --- lib/solargraph/convention/struct_definition.rb | 3 --- lib/solargraph/source/chain/instance_variable.rb | 2 ++ 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index b969a81d5..647f24962 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -78,9 +78,6 @@ def process private - # @sg-ignore - # Solargraph::Convention::DataDefinition::NodeProcessors::DataNode#data_definition_node - # return type could not be inferred # @return [DataDefinition::DataDefintionNode, DataDefinition::DataAssignmentNode, nil] def data_definition_node @data_definition_node ||= if DataDefintionNode.match?(node) diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index a8176ba3b..13741eb2a 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -102,9 +102,6 @@ def process private - # @sg-ignore - # Solargraph::Convention::StructDefinition::NodeProcessors::StructNode#struct_definition_node - # return type could not be inferred # @return [StructDefinition::StructDefintionNode, StructDefinition::StructAssignmentNode, nil] def struct_definition_node @struct_definition_node ||= if StructDefintionNode.match?(node) diff --git a/lib/solargraph/source/chain/instance_variable.rb b/lib/solargraph/source/chain/instance_variable.rb index 94676733f..5b0b5e5b2 100644 --- a/lib/solargraph/source/chain/instance_variable.rb +++ b/lib/solargraph/source/chain/instance_variable.rb @@ -4,6 +4,8 @@ module Solargraph class Source class Chain class InstanceVariable < Link + # @param word [String] + # @param node [Parser::AST::Node, nil] The node representing the variable def initialize word, node super(word) @node = node From 5d19a9eea722672be82691c74dc4f76841f8acbe Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 20:07:51 -0400 Subject: [PATCH 307/930] Spec fixes --- spec/source/chain/instance_variable_spec.rb | 2 +- spec/source/chain/or_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/source/chain/instance_variable_spec.rb b/spec/source/chain/instance_variable_spec.rb index 8326a66d2..d24fbb235 100644 --- a/spec/source/chain/instance_variable_spec.rb +++ b/spec/source/chain/instance_variable_spec.rb @@ -6,7 +6,7 @@ bar_pin = Solargraph::Pin::InstanceVariable.new(closure: closure, name: '@foo') api_map = Solargraph::ApiMap.new api_map.index [closure, methpin, foo_pin, bar_pin] - link = Solargraph::Source::Chain::InstanceVariable.new('@foo') + link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil) pins = link.resolve(api_map, methpin, []) expect(pins.length).to eq(1) expect(pins.first.name).to eq('@foo') diff --git a/spec/source/chain/or_spec.rb b/spec/source/chain/or_spec.rb index 547dcaec9..084738fe3 100644 --- a/spec/source/chain/or_spec.rb +++ b/spec/source/chain/or_spec.rb @@ -10,7 +10,7 @@ def foo a api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [3, 8]) + clip = api_map.clip_at('test.rb', [4, 8]) expect(clip.infer.simplify_literals.rooted_tags).to eq('::Integer') end From ed01ab8c272bae89389f05752dbab299d022df7a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 20:09:13 -0400 Subject: [PATCH 308/930] Spec fixes --- spec/type_checker/levels/strict_spec.rb | 2 +- spec/type_checker/levels/strong_spec.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 6f3992fed..722d59bdd 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -1042,7 +1042,7 @@ def bar end end )) - pending('implicit nil being understood from implicit else') + expect(checker.problems.map(&:message)).to eq([]) end end diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 551290e4c..83498296e 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -79,6 +79,8 @@ def bar end end )) + pending('support for @@var ||= expr pattern') + expect(checker.problems.map(&:message)).to eq([]) end From 4c5a4ea62cd17d90777a98ddfe97851f59208ec7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 20:56:11 -0400 Subject: [PATCH 309/930] Add some @sg-ignores for now --- lib/solargraph/api_map/store.rb | 1 + lib/solargraph/library.rb | 1 + lib/solargraph/pin/base.rb | 4 ++++ lib/solargraph/pin/base_variable.rb | 1 + lib/solargraph/pin/closure.rb | 1 + lib/solargraph/pin/constant.rb | 1 + lib/solargraph/pin/conversions.rb | 2 ++ lib/solargraph/pin/method.rb | 1 + lib/solargraph/pin/signature.rb | 2 ++ lib/solargraph/source.rb | 1 + lib/solargraph/source/cursor.rb | 1 + lib/solargraph/source/source_chainer.rb | 1 + lib/solargraph/source_map.rb | 1 + lib/solargraph/workspace.rb | 1 + 14 files changed, 19 insertions(+) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index caed02e7b..e8a55841b 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -290,6 +290,7 @@ def catalog pinsets true end + # @sg-ignore Need better ||= handling on ivars # @return [Hash{::Array(String, String) => ::Array}] def fqns_pins_map @fqns_pins_map ||= Hash.new do |h, (base, name)| diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 1f639831e..9470a24d4 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -494,6 +494,7 @@ def pins @pins ||= [] end + # @sg-ignore Need better ||= handling on ivars # @return [Set] def external_requires @external_requires ||= source_map_external_require_hash.values.flatten.to_set diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 22eda8131..89cb0fbf1 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -379,6 +379,7 @@ def assert_source_provided Solargraph.assert_or_log(:source, "source not provided - #{@path} #{@source} #{self.class}") if source.nil? end + # @sg-ignore Need better ||= handling on ivars # @return [String] def comments @comments ||= '' @@ -486,6 +487,7 @@ def return_type @return_type ||= ComplexType::UNDEFINED end + # @sg-ignore Need better ||= handling on ivars # @return [YARD::Docstring] def docstring parse_comments unless @docstring @@ -517,6 +519,7 @@ def maybe_directives? @maybe_directives ||= comments.include?('@!') end + # @sg-ignore Need better ||= handling on ivars # @return [Boolean] def deprecated? @deprecated ||= docstring.has_tag?('deprecated') @@ -586,6 +589,7 @@ def proxy return_type result end + # @sg-ignore Need better ||= handling on ivars # @deprecated # @return [String] def identity diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index bab1f740e..7d0b7f5a1 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 better ||= handling on ivars def return_type @return_type ||= generate_complex_type end diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index af3a8a372..3230603db 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 better ||= handling on ivars # @return [::Array] def generics @generics ||= docstring.tags(:generic).map(&:name) diff --git a/lib/solargraph/pin/constant.rb b/lib/solargraph/pin/constant.rb index 94a968e7e..881072641 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 better ||= handling on ivars def return_type @return_type ||= generate_complex_type end diff --git a/lib/solargraph/pin/conversions.rb b/lib/solargraph/pin/conversions.rb index 43050fb94..e46e959fe 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 better ||= handling on ivars # @return [Hash] def completion_item @completion_item ||= { @@ -49,6 +50,7 @@ def completion_item } end + # @sg-ignore Need better ||= handling on ivars # @return [Hash] def resolve_completion_item @resolve_completion_item ||= begin diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index bde3d8f77..3a392ec28 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -217,6 +217,7 @@ def generate_signature(parameters, return_type) signature end + # @sg-ignore Need better ||= handling on ivars # @return [::Array] def signatures @signatures ||= begin diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 4c25e028b..23cc09256 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -9,10 +9,12 @@ def initialize **splat super(**splat) end + # @sg-ignore Need better ||= handling on ivars def generics @generics ||= [].freeze end + # @sg-ignore Need better ||= handling on ivars def identity @identity ||= "signature#{object_id}" end diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 19614016c..3c9cbd001 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -464,6 +464,7 @@ def repaired private + # @sg-ignore Need better ||= handling on ivars # @return [Array] def code_lines @code_lines ||= code.lines diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 3dc37ad23..8f33fdf25 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -50,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 better ||= handling on ivars # @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 354db4957..5d5c5237c 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -96,6 +96,7 @@ def fixed_position @fixed_position ||= Position.from_offset(source.code, offset - end_of_phrase.length) end + # @sg-ignore Need better ||= handling on ivars # @return [String] def end_of_phrase @end_of_phrase ||= begin diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index c235c0b94..9d5373a3b 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -86,6 +86,7 @@ def conventions_environ # all pins except Solargraph::Pin::Reference::Reference # + # @sg-ignore Need better ||= handling on ivars # @return [Array] def document_symbols @document_symbols ||= (pins + convention_pins).select do |pin| diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index e720e08f1..f1c848983 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -39,6 +39,7 @@ def require_paths @require_paths ||= RequirePaths.new(directory_or_nil, config).generate end + # @sg-ignore Need better ||= handling on ivars # @return [Solargraph::Workspace::Config] def config @config ||= Solargraph::Workspace::Config.new(directory) From 066f5b03c17c929562db001a06fef562f67003c2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 20:56:48 -0400 Subject: [PATCH 310/930] Revert pointless change --- lib/solargraph/pin/block.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/pin/block.rb b/lib/solargraph/pin/block.rb index fead17226..2e99ad2df 100644 --- a/lib/solargraph/pin/block.rb +++ b/lib/solargraph/pin/block.rb @@ -16,10 +16,11 @@ class Block < Callable # @param context [ComplexType, nil] # @param args [::Array] def initialize receiver: nil, args: [], context: nil, node: nil, **splat - super(**splat, parameters: args, return_type: ComplexType.parse('::Proc')) + super(**splat, parameters: args) @receiver = receiver @context = context @node = node + @return_type = ComplexType.parse('::Proc') end # @param api_map [ApiMap] From bdd081f310d190e4bb90e45c4e479cabc757fbc5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 21:00:39 -0400 Subject: [PATCH 311/930] Drop dead sg-ignores --- lib/solargraph/source/chain/instance_variable.rb | 1 - lib/solargraph/workspace.rb | 2 -- 2 files changed, 3 deletions(-) diff --git a/lib/solargraph/source/chain/instance_variable.rb b/lib/solargraph/source/chain/instance_variable.rb index 5b0b5e5b2..75c31f2ce 100644 --- a/lib/solargraph/source/chain/instance_variable.rb +++ b/lib/solargraph/source/chain/instance_variable.rb @@ -11,7 +11,6 @@ def initialize word, node @node = node end - # @sg-ignore flow sensitive typing needs a not-nil override pin def resolve api_map, name_pin, locals api_map.get_instance_variable_pins(name_pin.binder.namespace, name_pin.binder.scope).select{|p| p.name == word} end diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index f1c848983..1cebb1efb 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -137,8 +137,6 @@ def rbs_collection_path @gem_rbs_collection ||= read_rbs_collection_path end - # @sg-ignore Solargraph::Workspace#rbs_collection_config_path - # return type could not be inferred # @return [String, nil] def rbs_collection_config_path @rbs_collection_config_path ||= begin From 8c39012bc8768d4898249933769f847072317d76 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 21:05:05 -0400 Subject: [PATCH 312/930] Update counts --- lib/solargraph/type_checker/rules.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index e05d91971..ec30c169d 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,9 +58,10 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo 34: flow sensitive typing needs a not-nil override pin - # @todo 33: Need to add nil check here - # @todo 6: Need to validate config + # @todo 33: flow sensitive typing needs a not-nil override pin + # @todo 32: Need to add nil check here + # @todo 18: Need better ||= handling on ivars + # @todo 9: Need to validate config # @todo 5: need boolish support for ? methods # @todo 2: Need better || handling on ivars # @todo 1: flow sensitive typing needs to handle && with variables From 2ffa7bf03e4f46df00dfce88e5b4dd7b492b9848 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 21:55:52 -0400 Subject: [PATCH 313/930] Revert spec exectations --- spec/type_checker/levels/strong_spec.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 83498296e..0bde28a66 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -51,7 +51,7 @@ def foo bar expect(checker.problems.map(&:message)).to be_empty end - it 'does gives correct complaint on array dereference with nilable type' do + it 'does not complain on array dereference' do checker = type_checker(%( # @param idx [Integer, nil] an index # @param arr [Array] an array of integers @@ -61,9 +61,7 @@ def foo(idx, arr) arr[idx] end )) - # 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") + expect(checker.problems.map(&:message)).to be_empty end it 'understands local evaluation with ||= removes nil from lhs type' do From c9cef4e73ece05381d96d45601a1422789497c55 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 21:57:28 -0400 Subject: [PATCH 314/930] Revert spec exectations --- spec/convention/gemfile_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/convention/gemfile_spec.rb b/spec/convention/gemfile_spec.rb index 827da7993..f104c261d 100644 --- a/spec/convention/gemfile_spec.rb +++ b/spec/convention/gemfile_spec.rb @@ -33,6 +33,7 @@ def type_checker code instance_eval File.read local_gemfile if File.exist? local_gemfile )) + pending('should warn on source') 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) From 6242161668da01896b0e4601fb7c9c435c385bdb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 16 Sep 2025 22:14:35 -0400 Subject: [PATCH 315/930] Drop @sg-ignore --- lib/solargraph/yardoc.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index ed638a7ce..eb0aa0bbd 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -24,8 +24,6 @@ def cache(yard_plugins, gemspec) 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(current_bundle_env_tweaks, cmd, chdir: gemspec.gem_dir) unless status.success? Solargraph.logger.warn { "YARD failed running #{cmd.inspect} in #{gemspec.gem_dir}" } From 67da9b12d2ebfdca0af49b2c5ab77ae9df8a4bf6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 17 Sep 2025 15:18:22 -0400 Subject: [PATCH 316/930] Fix method intersection logic --- lib/solargraph/source/chain/call.rb | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index c9d2c96e5..d2e4b8757 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -56,21 +56,19 @@ def resolve api_map, name_pin, locals [] end return inferred_pins(found, api_map, name_pin, locals) unless found.empty? - if api_map.loose_unions - # fetch methods which ANY of the potential context types provide - pins = name_pin.binder.each_unique_type.flat_map do |context| - ns_tag = context.namespace == '' ? '' : context.namespace_type.tag - stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) - [stack.first].compact - end - else - # grab pins which are provided by every potential context type - pins = name_pin.binder.each_unique_type.map do |context| - ns_tag = context.namespace == '' ? '' : context.namespace_type.tag - stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) - [stack.first].compact - end.reduce(:&) + # @sg-ignore Unresolved call to map on void, ::Enumerator<::Solargraph::ComplexType::UniqueType> + pin_groups = name_pin.binder.each_unique_type.map do |context| + ns_tag = context.namespace == '' ? '' : context.namespace_type.tag + stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) + [stack.first].compact + end + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array + if !api_map.loose_unions && pin_groups.any? { |pins| pins.empty? } + pin_groups = [] end + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array + pins = pin_groups.flatten.uniq(&:path) + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array return [] if pins.empty? inferred_pins(pins, api_map, name_pin, locals) end From be70960eb3d3def9804516510904eda9943fee47 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 17 Sep 2025 16:02:23 -0400 Subject: [PATCH 317/930] Move strict unions back to strong level --- lib/solargraph/type_checker/rules.rb | 6 ++---- spec/type_checker/levels/strict_spec.rb | 2 -- spec/type_checker/levels/typed_spec.rb | 2 -- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index ec30c169d..b86fe0f01 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -70,13 +70,11 @@ def require_inferred_type_params? # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' # @todo 1: Need better ||= handling on locals def require_all_unique_types_match_expected? - # TODO: Put this back up to strong - rank >= LEVELS[:typed] + rank >= LEVELS[:strong] end - # TODO: Take this back down to strong def require_all_unique_types_match_expected_on_lhs? - rank >= LEVELS[:alpha] + rank >= LEVELS[:strong] end def require_no_undefined_args? diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 722d59bdd..e271d2c89 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -21,8 +21,6 @@ def bar(b) end it 'ignores nilable type issues' do - pending("moving nilable handling back to strong") - checker = type_checker(%( # @param a [String] # @return [void] diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index 1ae2fb5bd..23c64c8e3 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -270,8 +270,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 abcd8d309670df02a4c185ebeff34054c1a5f0c0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 17 Sep 2025 16:33:44 -0400 Subject: [PATCH 318/930] Test CI build --- lib/solargraph.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 038e7bccf..67d2e10e4 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -57,6 +57,7 @@ class InvalidRubocopVersionError < RuntimeError; end # @param type [Symbol] Type of assert. def self.asserts_on?(type) + # @sg-ignore flow sensitive typing needs a not-nil override pin if ENV['SOLARGRAPH_ASSERTS'].nil? || ENV['SOLARGRAPH_ASSERTS'].empty? false elsif ENV['SOLARGRAPH_ASSERTS'] == 'on' From 7cf5b142a44345ed3e9208ba1b7d3f0056ccc26a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 17 Sep 2025 16:42:52 -0400 Subject: [PATCH 319/930] Fix merge --- 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 c25a90c77..e7d25cbd0 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -237,7 +237,7 @@ def self.load_with_cache directory, out = $stdout, loose_unions: true end api_map.cache_all!(out) - load(directory) + load(directory, loose_unions: loose_unions) end # @return [Array] From d0272e2b4e91d116739a8cddb4204dab109183cc Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 17 Sep 2025 17:01:56 -0400 Subject: [PATCH 320/930] Add @sg-ignores to work down --- Rakefile | 1 + lib/solargraph/api_map.rb | 4 ++- lib/solargraph/api_map/constants.rb | 3 +++ lib/solargraph/api_map/index.rb | 2 ++ lib/solargraph/api_map/source_to_yard.rb | 12 +++++++++ lib/solargraph/api_map/store.rb | 6 +++++ lib/solargraph/complex_type.rb | 2 +- lib/solargraph/convention/data_definition.rb | 5 ++++ .../data_definition/data_definition_node.rb | 1 + .../convention/struct_definition.rb | 5 ++++ .../struct_definition_node.rb | 1 + lib/solargraph/diagnostics/rubocop_helpers.rb | 2 ++ lib/solargraph/doc_map.rb | 6 +++++ lib/solargraph/language_server/host.rb | 1 + .../message/extended/document.rb | 1 + .../message/text_document/completion.rb | 2 ++ .../message/text_document/definition.rb | 4 +++ .../message/text_document/document_symbol.rb | 2 ++ .../message/text_document/formatting.rb | 1 + .../message/text_document/hover.rb | 2 ++ .../message/text_document/signature_help.rb | 1 + .../message/text_document/type_definition.rb | 2 ++ .../message/workspace/workspace_symbol.rb | 2 ++ lib/solargraph/library.rb | 24 ++++++++++++++++++ lib/solargraph/parser/comment_ripper.rb | 1 + .../parser/flow_sensitive_typing.rb | 5 +++- lib/solargraph/parser/node_processor/base.rb | 3 +++ .../parser/parser_gem/class_methods.rb | 5 ++++ .../parser/parser_gem/node_chainer.rb | 4 +++ .../parser/parser_gem/node_methods.rb | 9 ++++++- .../parser_gem/node_processors/args_node.rb | 3 +++ .../parser_gem/node_processors/lvasgn_node.rb | 1 + .../node_processors/resbody_node.rb | 1 + .../parser_gem/node_processors/send_node.rb | 6 +++++ lib/solargraph/pin/base.rb | 3 +++ lib/solargraph/pin/base_variable.rb | 3 +++ lib/solargraph/pin/block.rb | 4 +++ lib/solargraph/pin/callable.rb | 7 +++++- lib/solargraph/pin/closure.rb | 2 ++ lib/solargraph/pin/common.rb | 1 + lib/solargraph/pin/delegated_method.rb | 7 ++++++ lib/solargraph/pin/documenting.rb | 1 + lib/solargraph/pin/instance_variable.rb | 5 ++++ lib/solargraph/pin/local_variable.rb | 2 ++ lib/solargraph/pin/method.rb | 19 ++++++++++++++ lib/solargraph/pin/namespace.rb | 1 + lib/solargraph/pin/parameter.rb | 19 +++++++++++--- lib/solargraph/pin/proxy_type.rb | 1 + lib/solargraph/pin/reference.rb | 2 ++ lib/solargraph/pin/reference/superclass.rb | 2 ++ lib/solargraph/pin/signature.rb | 9 +++++++ lib/solargraph/range.rb | 1 + lib/solargraph/rbs_map.rb | 1 + lib/solargraph/rbs_map/conversions.rb | 1 + lib/solargraph/rbs_map/stdlib_map.rb | 1 + lib/solargraph/shell.rb | 5 ++++ lib/solargraph/source.rb | 20 +++++++++++++++ lib/solargraph/source/chain.rb | 1 + lib/solargraph/source/chain/call.rb | 24 ++++++++++++++++-- lib/solargraph/source/chain/constant.rb | 4 +++ lib/solargraph/source/chain/hash.rb | 1 + lib/solargraph/source/chain/if.rb | 1 + lib/solargraph/source/chain/literal.rb | 2 ++ lib/solargraph/source/change.rb | 7 ++++++ lib/solargraph/source/cursor.rb | 8 ++++-- lib/solargraph/source/source_chainer.rb | 7 +++++- lib/solargraph/source_map.rb | 2 ++ lib/solargraph/source_map/clip.rb | 9 +++++++ lib/solargraph/source_map/mapper.rb | 12 +++++++++ lib/solargraph/type_checker.rb | 25 ++++++++++++++++--- lib/solargraph/workspace/require_paths.rb | 1 + lib/solargraph/yard_map/mapper.rb | 1 + lib/solargraph/yard_map/mapper/to_method.rb | 3 +++ lib/solargraph/yardoc.rb | 1 + solargraph.gemspec | 1 + spec/type_checker/levels/alpha_spec.rb | 15 +++++++++++ 76 files changed, 356 insertions(+), 16 deletions(-) diff --git a/Rakefile b/Rakefile index c83d9ab6b..f27abfeb6 100755 --- a/Rakefile +++ b/Rakefile @@ -63,6 +63,7 @@ def undercover status rescue StandardError => e warn "hit error: #{e.message}" + # @sg-ignore Need to add nil check here warn "Backtrace:\n#{e.backtrace.join("\n")}" warn "output: #{output}" puts "Flushing" diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index e7d25cbd0..f5ba0eb37 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -651,13 +651,13 @@ def super_and_sub?(sup, sub) sub = sub.simplify_literals.to_s return true if sup == sub sc_fqns = sub - # @sg-ignore Need to disambiguate type of sup, sub while (sc = store.get_superclass(sc_fqns)) # @sg-ignore flow sensitive typing needs a not-nil override pin sc_new = store.constants.dereference(sc) # Cyclical inheritance is invalid return false if sc_new == sc_fqns sc_fqns = sc_new + # @sg-ignore need to be able to resolve same method signature on two different types return true if sc_fqns == sup end false @@ -681,6 +681,7 @@ 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) + # @sg-ignore Need to add nil check here next nil if resolved.respond_to?(:visibility) && !visibility.include?(resolved.visibility) resolved end.compact @@ -844,6 +845,7 @@ def get_namespace_type fqns # @type [Pin::Namespace, nil] pin = store.get_path_pins(fqns).select{|p| p.is_a?(Pin::Namespace)}.first return nil if pin.nil? + # @sg-ignore flow sensitive typing needs a not-nil override pin pin.type end diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index d026beafb..dc50bf34b 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -69,6 +69,7 @@ def qualify tag, context_tag = '' fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace) return unless fqns + # @sg-ignore flow sensitive typing needs a not-nil override pin fqns + type.substring end @@ -200,11 +201,13 @@ def inner_get_constants fqns, visibility, skip result = [] store.get_prepends(fqns).each do |pre| + # @sg-ignore Need to add nil check here pre_fqns = resolve(pre.name, pre.closure.gates - skip.to_a) result.concat inner_get_constants(pre_fqns, [:public], skip) end result.concat(store.get_constants(fqns, visibility).sort { |a, b| a.name <=> b.name }) store.get_includes(fqns).each do |pin| + # @sg-ignore Need to add nil check here inc_fqns = resolve(pin.name, pin.closure.gates - skip.to_a) result.concat inner_get_constants(inc_fqns, [:public], skip) end diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 635d12b35..0d4431e70 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -138,12 +138,14 @@ def map_overrides end (ovr.tags.map(&:tag_name) + ovr.delete).uniq.each do |tag| pin.docstring.delete_tags tag + # @sg-ignore flow sensitive typing needs a not-nil override pin new_pin.docstring.delete_tags tag if new_pin end ovr.tags.each do |tag| pin.docstring.add_tag(tag) redefine_return_type pin, tag if new_pin + # @sg-ignore flow sensitive typing needs a not-nil override pin new_pin.docstring.add_tag(tag) # @sg-ignore flow sensitive typing needs a not-nil override pin redefine_return_type new_pin, tag diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index 510ef17c9..d0f1224a0 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -36,12 +36,16 @@ def rake_yard store end if pin.type == :class code_object_map[pin.path] ||= YARD::CodeObjects::ClassObject.new(root_code_object, pin.path) { |obj| + # @sg-ignore flow sensitive typing needs a not-nil override pin next if pin.location.nil? || pin.location.filename.nil? + # @sg-ignore flow sensitive typing needs a not-nil override pin obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) } else code_object_map[pin.path] ||= YARD::CodeObjects::ModuleObject.new(root_code_object, pin.path) { |obj| + # @sg-ignore flow sensitive typing needs a not-nil override pin next if pin.location.nil? || pin.location.filename.nil? + # @sg-ignore flow sensitive typing needs a not-nil override pin obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) } end @@ -49,6 +53,7 @@ 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? + # @sg-ignore flow sensitive typing needs a not-nil override pin include_object.instance_mixins.push code_object_map[ref.parametrized_tag.to_s] end end @@ -57,8 +62,10 @@ def rake_yard store next unless extend_object code_object = code_object_map[ref.parametrized_tag.to_s] next unless code_object + # @sg-ignore flow sensitive typing needs a not-nil override pin extend_object.class_mixins.push code_object # @todo add spec showing why this next line is necessary + # @sg-ignore need to be able to resolve same method signature on two different types extend_object.instance_mixins.push code_object end end @@ -70,12 +77,17 @@ def rake_yard store # @sg-ignore Need to add nil check here code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope) { |obj| + # @sg-ignore flow sensitive typing needs a not-nil override pin next if pin.location.nil? || pin.location.filename.nil? + # @sg-ignore flow sensitive typing needs a not-nil override pin obj.add_file pin.location.filename, pin.location.range.start.line } method_object = code_object_at(pin.path, YARD::CodeObjects::MethodObject) + # @sg-ignore Need to add nil check here method_object.docstring = pin.docstring + # @sg-ignore Need to add nil check here method_object.visibility = pin.visibility || :public + # @sg-ignore Need to add nil check here method_object.parameters = pin.parameters.map do |p| [p.full_name, p.asgn_code] end diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index e8a55841b..34d9f9205 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -34,6 +34,7 @@ def update *pinsets @fqns_pins_map = nil return catalog(pinsets) if changed == 0 + # @sg-ignore Need to add nil check here pinsets[changed..].each_with_index do |pins, idx| @pinsets[changed + idx] = pins @indexes[changed + idx] = if pins.empty? @@ -99,6 +100,7 @@ def qualify_superclass fq_sub_tag # @sg-ignore flow sensitive typing needs a not-nil override pin res = constants.dereference(ref) return unless res + # @sg-ignore flow sensitive typing needs a not-nil override pin res + type.substring end @@ -210,7 +212,9 @@ def fqns_pins fqns return [] if fqns.nil? if fqns.include?('::') parts = fqns.split('::') + # @sg-ignore flow sensitive typing needs a not-nil override pin name = parts.pop + # @sg-ignore flow sensitive typing needs a not-nil override pin base = parts.join('::') else base = '' @@ -240,6 +244,7 @@ def get_ancestors(fqns) ref = get_superclass(current) # @sg-ignore flow sensitive typing needs to handle && with variables superclass = ref && constants.dereference(ref) + # @sg-ignore flow sensitive typing needs a not-nil override pin if superclass && !superclass.empty? && !visited.include?(superclass) ancestors << superclass queue << superclass @@ -374,6 +379,7 @@ def uncached_qualify_superclass fq_sub_tag # @sg-ignore flow sensitive typing needs a not-nil override pin res = constants.dereference(ref) return unless res + # @sg-ignore flow sensitive typing needs a not-nil override pin res + type.substring end end diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 4c06d1cb4..1075dd82c 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -225,7 +225,6 @@ def duck_types_match? api_map, expected, inferred expected.each do |exp| next unless exp.duck_type? quack = exp.to_s[1..] - # @sg-ignore Need to add nil check here return false if api_map.get_method_stack(inferred.namespace, quack, scope: inferred.scope).empty? end true @@ -399,6 +398,7 @@ def parse *strings, partial: false elsif base.end_with?('=') raise ComplexTypeError, "Invalid hash thing" unless key_types.nil? # types.push ComplexType.new([UniqueType.new(base[0..-2].strip)]) + # @sg-ignore Need to add nil check here types.push UniqueType.parse(base[0..-2].strip, subtype_string) # @todo this should either expand key_type's type # automatically or complain about not being diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index 647f24962..1db23451c 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -17,6 +17,7 @@ def process type: :class, location: loc, closure: region.closure, + # @sg-ignore need to be able to resolve same method signature on two different types name: data_definition_node.class_name, comments: comments_for(node), visibility: :public, @@ -39,6 +40,7 @@ def process # Solargraph::SourceMap::Clip#complete_keyword_parameters does not seem to currently take into account [Pin::Method#signatures] hence we only one for :kwarg pins.push initialize_method_pin + # @sg-ignore need to be able to resolve same method signature on two different types data_definition_node.attributes.map do |attribute_node, attribute_name| initialize_method_pin.parameters.push( Pin::Parameter.new( @@ -51,6 +53,7 @@ def process end # define attribute readers and instance variables + # @sg-ignore need to be able to resolve same method signature on two different types data_definition_node.attributes.each do |attribute_node, attribute_name| name = attribute_name.to_s method_pin = Pin::Method.new( @@ -92,8 +95,10 @@ def data_definition_node # @return [String, nil] def attribute_comments(attribute_node, attribute_name) data_comments = comments_for(attribute_node) + # @sg-ignore flow sensitive typing needs a not-nil override pin return if data_comments.nil? || data_comments.empty? + # @sg-ignore flow sensitive typing needs a not-nil override pin data_comments.split("\n").find do |row| row.include?(attribute_name) end&.gsub('@param', '@return')&.gsub(attribute_name, '') diff --git a/lib/solargraph/convention/data_definition/data_definition_node.rb b/lib/solargraph/convention/data_definition/data_definition_node.rb index 901ce48d9..49cf210a7 100644 --- a/lib/solargraph/convention/data_definition/data_definition_node.rb +++ b/lib/solargraph/convention/data_definition/data_definition_node.rb @@ -84,6 +84,7 @@ def data_node # @sg-ignore Need to add nil check here # @return [Array] def data_attribute_nodes + # @sg-ignore Need to add nil check here data_node.children[2..-1] end end diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index 13741eb2a..a2a1022b0 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -17,6 +17,7 @@ def process type: :class, location: loc, closure: region.closure, + # @sg-ignore need to be able to resolve same method signature on two different types name: struct_definition_node.class_name, docstring: docstring, visibility: :public, @@ -39,6 +40,7 @@ def process pins.push initialize_method_pin + # @sg-ignore need to be able to resolve same method signature on two different types struct_definition_node.attributes.map do |attribute_node, attribute_name| initialize_method_pin.parameters.push( Pin::Parameter.new( @@ -52,6 +54,7 @@ def process end # define attribute accessors and instance variables + # @sg-ignore need to be able to resolve same method signature on two different types struct_definition_node.attributes.each do |attribute_node, attribute_name| [attribute_name, "#{attribute_name}="].each do |name| docs = docstring.tags.find { |t| t.tag_name == 'param' && t.name == attribute_name } @@ -121,6 +124,7 @@ def docstring # @return [YARD::Docstring] def parse_comments struct_comments = comments_for(node) || '' + # @sg-ignore need to be able to resolve same method signature on two different types struct_definition_node.attributes.each do |attr_node, attr_name| comment = comments_for(attr_node) next if comment.nil? @@ -137,6 +141,7 @@ def parse_comments # @param tag [YARD::Tags::Tag, nil] The param tag for this attribute.xtract_ # + # @sg-ignore flow sensitive typing needs a not-nil override pin # @return [String] def tag_string(tag) tag&.types&.join(',') || 'undefined' diff --git a/lib/solargraph/convention/struct_definition/struct_definition_node.rb b/lib/solargraph/convention/struct_definition/struct_definition_node.rb index 8f03f1fcb..6e6afec62 100644 --- a/lib/solargraph/convention/struct_definition/struct_definition_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_definition_node.rb @@ -74,6 +74,7 @@ def keyword_init? return false if keyword_init_param.nil? + # @sg-ignore Need to add nil check here keyword_init_param.children[0].children[1].type == :true end diff --git a/lib/solargraph/diagnostics/rubocop_helpers.rb b/lib/solargraph/diagnostics/rubocop_helpers.rb index f6f4c82c8..85a37feeb 100644 --- a/lib/solargraph/diagnostics/rubocop_helpers.rb +++ b/lib/solargraph/diagnostics/rubocop_helpers.rb @@ -18,6 +18,7 @@ def require_rubocop(version = nil) # @type [String] gem_path = Gem::Specification.find_by_name('rubocop', version).full_gem_path gem_lib_path = File.join(gem_path, 'lib') + # @sg-ignore Should better support meaning of '&' in RBS $LOAD_PATH.unshift(gem_lib_path) unless $LOAD_PATH.include?(gem_lib_path) rescue Gem::MissingSpecVersionError => e raise InvalidRubocopVersionError, @@ -47,6 +48,7 @@ def generate_options filename, code # @return [String] def fix_drive_letter path return path unless path.match(/^[a-z]:/) + # @sg-ignore Need to add nil check here path[0].upcase + path[1..-1] end diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index a493f0169..ab396404c 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -218,6 +218,7 @@ def deserialize_yard_pin_cache gemspec cached = PinCache.deserialize_yard_gem(gemspec) if cached + # @sg-ignore flow sensitive typing needs a not-nil override pin logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" } yard_pins_in_memory[[gemspec.name, gemspec.version]] = cached cached @@ -240,6 +241,7 @@ def deserialize_combined_pin_cache(gemspec) cached = PinCache.deserialize_combined_gem(gemspec, rbs_version_cache_key) if cached + # @sg-ignore flow sensitive typing needs a not-nil override pin 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]] @@ -296,6 +298,7 @@ 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 + # @sg-ignore flow sensitive typing needs a not-nil override pin 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 @@ -384,8 +387,11 @@ def inspect # @return [Array, nil] def gemspecs_required_from_bundler # @todo Handle projects with custom Bundler/Gemfile setups + # @sg-ignore Need to add nil check here return unless workspace.gemfile? + # @todo: redundant check + # @sg-ignore Need to add nil check here 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| diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index 7067615c0..bdaf78525 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -105,6 +105,7 @@ def receive request message.process unless cancel?(request['id']) rescue StandardError => e logger.warn "Error processing request: [#{e.class}] #{e.message}" + # @sg-ignore Need to add nil check here logger.warn e.backtrace.join("\n") message.set_error Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, "[#{e.class}] #{e.message}" end diff --git a/lib/solargraph/language_server/message/extended/document.rb b/lib/solargraph/language_server/message/extended/document.rb index 836fc005e..f379d0a6b 100644 --- a/lib/solargraph/language_server/message/extended/document.rb +++ b/lib/solargraph/language_server/message/extended/document.rb @@ -14,6 +14,7 @@ def process ) rescue StandardError => e Solargraph.logger.warn "Error processing document: [#{e.class}] #{e.message}" + # @sg-ignore Need to add nil check here Solargraph.logger.debug e.backtrace.join("\n") end end diff --git a/lib/solargraph/language_server/message/text_document/completion.rb b/lib/solargraph/language_server/message/text_document/completion.rb index ef7ad1be4..5b9acec33 100644 --- a/lib/solargraph/language_server/message/text_document/completion.rb +++ b/lib/solargraph/language_server/message/text_document/completion.rb @@ -15,6 +15,7 @@ def process items = [] last_context = nil idx = -1 + # @sg-ignore Need to add nil check here completion.pins.each do |pin| idx += 1 if last_context != pin.context items.push pin.completion_item.merge({ @@ -37,6 +38,7 @@ def process end rescue FileNotFoundError => e Logging.logger.warn "[#{e.class}] #{e.message}" + # @sg-ignore Need to add nil check here Logging.logger.warn e.backtrace.join("\n") set_result empty_result end diff --git a/lib/solargraph/language_server/message/text_document/definition.rb b/lib/solargraph/language_server/message/text_document/definition.rb index ea0942dd5..4af1131d9 100644 --- a/lib/solargraph/language_server/message/text_document/definition.rb +++ b/lib/solargraph/language_server/message/text_document/definition.rb @@ -13,7 +13,9 @@ def process # @return [Array, nil] def code_location suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column) + # @sg-ignore Need to add nil check here return nil if suggestions.empty? + # @sg-ignore Need to add nil check here suggestions.reject { |pin| pin.best_location.nil? || pin.best_location.filename.nil? }.map do |pin| { uri: file_to_uri(pin.best_location.filename), @@ -31,7 +33,9 @@ def require_location return nil if dloc.nil? [ { + # @sg-ignore flow sensitive typing needs a not-nil override pin uri: file_to_uri(dloc.filename), + # @sg-ignore flow sensitive typing needs a not-nil override pin range: dloc.range.to_hash } ] diff --git a/lib/solargraph/language_server/message/text_document/document_symbol.rb b/lib/solargraph/language_server/message/text_document/document_symbol.rb index 2490f5c6d..3d3cccbde 100644 --- a/lib/solargraph/language_server/message/text_document/document_symbol.rb +++ b/lib/solargraph/language_server/message/text_document/document_symbol.rb @@ -13,7 +13,9 @@ def process containerName: pin.namespace, kind: pin.symbol_kind, location: { + # @sg-ignore Need to add nil check here uri: file_to_uri(pin.best_location.filename), + # @sg-ignore Need to add nil check here range: pin.best_location.range.to_hash }, deprecated: pin.deprecated? diff --git a/lib/solargraph/language_server/message/text_document/formatting.rb b/lib/solargraph/language_server/message/text_document/formatting.rb index b4ee7860e..20dbde474 100644 --- a/lib/solargraph/language_server/message/text_document/formatting.rb +++ b/lib/solargraph/language_server/message/text_document/formatting.rb @@ -101,6 +101,7 @@ def formatter_class(config) # @return [String, nil] def cop_list(value) # @type [String] + # @sg-ignore Translate to something flow sensitive typing understands 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/hover.rb b/lib/solargraph/language_server/message/text_document/hover.rb index 72eff4296..57a9161a3 100644 --- a/lib/solargraph/language_server/message/text_document/hover.rb +++ b/lib/solargraph/language_server/message/text_document/hover.rb @@ -11,6 +11,7 @@ def process contents = [] suggestions = host.definitions_at(params['textDocument']['uri'], line, col) last_link = nil + # @sg-ignore Need to add nil check here suggestions.each do |pin| parts = [] this_link = host.options['enablePages'] ? pin.link_documentation : pin.text_documentation @@ -31,6 +32,7 @@ def process ) rescue FileNotFoundError => e Logging.logger.warn "[#{e.class}] #{e.message}" + # @sg-ignore Need to add nil check here Logging.logger.warn e.backtrace.join("\n") set_result nil end diff --git a/lib/solargraph/language_server/message/text_document/signature_help.rb b/lib/solargraph/language_server/message/text_document/signature_help.rb index e4e8795db..a56b56edd 100644 --- a/lib/solargraph/language_server/message/text_document/signature_help.rb +++ b/lib/solargraph/language_server/message/text_document/signature_help.rb @@ -14,6 +14,7 @@ def process }) rescue FileNotFoundError => e Logging.logger.warn "[#{e.class}] #{e.message}" + # @sg-ignore Need to add nil check here Logging.logger.warn e.backtrace.join("\n") set_result nil end 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 adb24038b..3a2431659 100644 --- a/lib/solargraph/language_server/message/text_document/type_definition.rb +++ b/lib/solargraph/language_server/message/text_document/type_definition.rb @@ -13,7 +13,9 @@ def process # @return [Array, nil] def code_location suggestions = host.type_definitions_at(params['textDocument']['uri'], @line, @column) + # @sg-ignore Need to add nil check here return nil if suggestions.empty? + # @sg-ignore Need to add nil check here suggestions.reject { |pin| pin.best_location.nil? || pin.best_location.filename.nil? }.map do |pin| { uri: file_to_uri(pin.best_location.filename), diff --git a/lib/solargraph/language_server/message/workspace/workspace_symbol.rb b/lib/solargraph/language_server/message/workspace/workspace_symbol.rb index 780e4aa0b..fe7e5efa5 100644 --- a/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +++ b/lib/solargraph/language_server/message/workspace/workspace_symbol.rb @@ -6,6 +6,7 @@ class Solargraph::LanguageServer::Message::Workspace::WorkspaceSymbol < Solargra def process pins = host.query_symbols(params['query']) info = pins.map do |pin| + # @sg-ignore Need to add nil check here uri = file_to_uri(pin.best_location.filename) { name: pin.path, @@ -13,6 +14,7 @@ def process kind: pin.symbol_kind, location: { uri: uri, + # @sg-ignore Need to add nil check here range: pin.best_location.range.to_hash }, deprecated: pin.deprecated? diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 9470a24d4..064b53639 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -57,8 +57,11 @@ def synchronized? # @param source [Source, nil] # @return [void] def attach source + # @sg-ignore flow sensitive typing needs a not-nil override pin if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename) + # @sg-ignore flow sensitive typing needs a not-nil override pin source_map_hash.delete @current.filename + # @sg-ignore flow sensitive typing needs a not-nil override pin source_map_external_require_hash.delete @current.filename @external_requires = nil end @@ -72,7 +75,9 @@ def attach source # # @param filename [String] # @return [Boolean] + # @sg-ignore flow sensitive typing needs a not-nil override pin def attached? filename + # @sg-ignore flow sensitive typing needs a not-nil override pin !@current.nil? && @current.filename == filename end alias open? attached? @@ -82,6 +87,7 @@ def attached? filename # @param filename [String] # @return [Boolean] True if the specified file was detached def detach filename + # @sg-ignore flow sensitive typing needs a not-nil override pin return false if @current.nil? || @current.filename != filename attach nil true @@ -145,6 +151,7 @@ def delete *filenames # @param filename [String] # @return [void] def close filename + # @sg-ignore should understand meaning of &. return unless @current&.filename == filename @current = nil @@ -182,9 +189,12 @@ def definitions_at filename, line, column if cursor.comment? source = read(filename) offset = Solargraph::Position.to_offset(source.code, Solargraph::Position.new(line, column)) + # @sg-ignore Need to add nil check here lft = source.code[0..offset-1].match(/\[[a-z0-9_:<, ]*?([a-z0-9_:]*)\z/i) + # @sg-ignore Need to add nil check here rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i) if lft && rgt + # @sg-ignore flow sensitive typing needs a not-nil override pin tag = (lft[1] + rgt[1]).sub(/:+$/, '') clip = mutex.synchronize { api_map.clip(cursor) } clip.translate tag @@ -255,7 +265,9 @@ def references_from filename, line, column, strip: false, only: false files.uniq(&:filename).each do |source| found = source.references(pin.name) found.select! do |loc| + # @sg-ignore Need to add nil check here referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character).first + # @sg-ignore should understand meaning of &. referenced&.path == pin.path end if pin.path == 'Class#new' @@ -273,6 +285,7 @@ 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| + # @sg-ignore flow sensitive typing needs a not-nil override pin 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 @@ -299,6 +312,7 @@ def locate_pins location def locate_ref location map = source_map_hash[location.filename] return if map.nil? + # @sg-ignore flow sensitive typing needs a not-nil override pin pin = map.requires.select { |p| p.location.range.contain?(location.range.start) }.first return nil if pin.nil? # @param full [String] @@ -433,6 +447,7 @@ def bench source_maps: source_map_hash.values, workspace: workspace, external_requires: external_requires, + # @sg-ignore flow sensitive typing needs a not-nil override pin live_map: @current ? source_map_hash[@current.filename] : nil ) end @@ -471,9 +486,11 @@ def next_map return false if mapped? src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) } if src + # @sg-ignore flow sensitive typing needs a not-nil override pin Logging.logger.debug "Mapping #{src.filename}" # @sg-ignore flow sensitive typing needs a not-nil override pin source_map_hash[src.filename] = Solargraph::SourceMap.map(src) + # @sg-ignore flow sensitive typing needs a not-nil override pin source_map_hash[src.filename] else false @@ -544,6 +561,7 @@ def api_map # @sg-ignore flow sensitive typing needs to handle if foo && ... # @return [Solargraph::Source] def read filename + # @sg-ignore flow sensitive typing needs a not-nil override pin return @current if @current && @current.filename == filename raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename) workspace.source(filename) @@ -638,23 +656,29 @@ def queued_gemspec_cache def report_cache_progress gem_name, pending @total ||= pending @total = pending if pending > @total + # @sg-ignore flow sensitive typing needs a not-nil override pin finished = @total - pending + # @sg-ignore flow sensitive typing needs a not-nil override pin pct = if @total.zero? 0 else + # @sg-ignore flow sensitive typing needs a not-nil override pin ((finished.to_f / @total.to_f) * 100).to_i end message = "#{gem_name}#{pending > 0 ? " (+#{pending})" : ''}" # " if @cache_progress + # @sg-ignore flow sensitive typing needs a not-nil override pin @cache_progress.report(message, pct) else @cache_progress = LanguageServer::Progress.new('Caching gem') # If we don't send both a begin and a report, the progress notification # might get stuck in the status bar forever + # @sg-ignore flow sensitive typing needs a not-nil override pin @cache_progress.begin(message, pct) changed notify_observers @cache_progress + # @sg-ignore flow sensitive typing needs a not-nil override pin @cache_progress.report(message, pct) end changed diff --git a/lib/solargraph/parser/comment_ripper.rb b/lib/solargraph/parser/comment_ripper.rb index ca6c0273e..8bfe166f5 100644 --- a/lib/solargraph/parser/comment_ripper.rb +++ b/lib/solargraph/parser/comment_ripper.rb @@ -23,6 +23,7 @@ def on_comment *args # @sg-ignore # @type [Array(Symbol, String, Array([Integer, nil], [Integer, nil]))] result = super + # @sg-ignore Need to add nil check here if @buffer_lines[result[2][0]][0..result[2][1]].strip =~ /^#/ chomped = result[1].chomp if result[2][0] == 0 && chomped.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '').match(/^#\s*frozen_string_literal:/) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index aa48e6321..42ebd4074 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -132,7 +132,7 @@ def self.visible_pins(pins, name, closure, location) # # @return [ComplexType, nil] def remove_nil(return_type) - # TODO: This probably needs to be some kind of override + # @todo flow sensitive typing needs a not-nil override pin return return_type if return_type.nil? || return_type.undefined? types = return_type.items.reject { |t| t.name == 'nil' } @@ -258,6 +258,7 @@ def find_local(variable_name, position) def process_isa(isa_node, true_presences) isa_type_name, variable_name = parse_isa(isa_node) return if variable_name.nil? || variable_name.empty? + # @sg-ignore Need to add nil check here isa_position = Range.from_node(isa_node).start pin = find_local(variable_name, isa_position) @@ -286,6 +287,7 @@ def process_nilp(nilp_node, true_presences) # we're looking for and typechecking will cover any invalid # ones return unless nilp_arg.nil? + # @sg-ignore Need to add nil check here nilp_position = Range.from_node(nilp_node).start pin = find_local(variable_name, nilp_position) @@ -313,6 +315,7 @@ def process_variable(node, true_presences) variable_name = parse_variable(node) return if variable_name.nil? + # @sg-ignore Need to add nil check here var_position = Range.from_node(node).start # @sg-ignore flow sensitive typing needs a not-nil override pin diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index fad31e95b..f18d623c0 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -68,6 +68,7 @@ def comments_for(node) # @return [Pin::Closure, nil] def named_path_pin position pins.select do |pin| + # @sg-ignore flow sensitive typing needs a not-nil override pin pin.is_a?(Pin::Closure) && pin.path && !pin.path.empty? && pin.location.range.contain?(position) end.last end @@ -77,6 +78,7 @@ def named_path_pin position # @return [Pin::Closure, nil] def block_pin position # @todo determine if this can return a Pin::Block + # @sg-ignore Need to add nil check here pins.select { |pin| pin.is_a?(Pin::Closure) && pin.location.range.contain?(position) }.last end @@ -84,6 +86,7 @@ def block_pin position # @param position [Solargraph::Position] # @return [Pin::Closure, nil] def closure_pin position + # @sg-ignore Need to add nil check here pins.select { |pin| pin.is_a?(Pin::Closure) && pin.location.range.contain?(position) }.last end end diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index 8f9c836a7..9be1daf62 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -52,15 +52,18 @@ def references source, name # @param code [String] # @param offset [Integer] # @return [Array(Integer, Integer), Array(nil, nil)] + # @sg-ignore Need to add nil check here extract_offset = ->(code, offset) { reg.match(code, offset).offset(0) } else # @param code [String] # @param offset [Integer] # @return [Array(Integer, Integer), Array(nil, nil)] + # @sg-ignore Need to add nil check here extract_offset = ->(code, offset) { [soff = code.index(name, offset), soff + name.length] } end inner_node_references(name, source.node).map do |n| rng = Range.from_node(n) + # @sg-ignore Need to add nil check here offset = Position.to_offset(source.code, rng.start) soff, eoff = extract_offset[source.code, offset] Location.new( @@ -137,8 +140,10 @@ def string_ranges node end if node.type == :dstr && node.children.last.nil? last = node.children[-2] + # @sg-ignore Need to add nil check here unless last.nil? rng = Range.from_node(last) + # @sg-ignore Need to add nil check here pos = Position.new(rng.ending.line, rng.ending.column - 1) result.push Range.new(pos, pos) end diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index 4f7428ac8..72e6b20cd 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -61,6 +61,7 @@ def generate_links n result.push Chain::Call.new(n.children[1].to_s, Location.from_node(n), node_args(n), passed_block(n)) elsif n.children[0].nil? args = [] + # @sg-ignore Need to add nil check here n.children[2..-1].each do |c| args.push NodeChainer.chain(c, @filename, n) end @@ -149,6 +150,7 @@ def hash_is_splatted? node # @param node [Parser::AST::Node] # @return [Source::Chain, nil] def passed_block node + # @sg-ignore Should better support meaning of '&' in RBS return unless node == @node && @parent&.type == :block # @sg-ignore Need to add nil check here @@ -156,8 +158,10 @@ def passed_block node end # @param node [Parser::AST::Node] + # @sg-ignore Need to add nil check here # @return [Array] def node_args node + # @sg-ignore Need to add nil check here node.children[2..-1].map do |child| NodeChainer.chain(child, @filename, node) end diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 8321acd21..ddc76f988 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -160,12 +160,15 @@ def call_nodes_from node if node.type == :block result.push node if Parser.is_ast_node?(node.children[0]) && node.children[0].children.length > 2 + # @sg-ignore Need to add nil check here node.children[0].children[2..-1].each { |child| result.concat call_nodes_from(child) } end + # @sg-ignore Need to add nil check here 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) + # @sg-ignore Need to add nil check here node.children[2..-1].each { |child| result.concat call_nodes_from(child) } elsif [:super, :zsuper].include?(node.type) result.push node @@ -210,8 +213,10 @@ def find_recipient_node cursor position = cursor.position offset = cursor.offset tree = if source.synchronized? + # @sg-ignore Need to add nil check here match = source.code[0..offset-1].match(/,\s*\z/) if match + # @sg-ignore Need to add nil check here source.tree_at(position.line, position.column - match[0].length) else source.tree_at(position.line, position.column) @@ -224,7 +229,9 @@ def find_recipient_node cursor tree.each do |node| if node.type == :send args = node.children[2..-1] + # @sg-ignore Need to add nil check here if !args.empty? + # @sg-ignore flow sensitive typing needs a not-nil override pin return node if prev && args.include?(prev) else if source.synchronized? @@ -361,6 +368,7 @@ def from_value_position_statement node, include_explicit_returns: true # that the function is executed here. result.concat explicit_return_values_from_compound_statement(node.children[2]) if include_explicit_returns elsif CASE_STATEMENT.include?(node.type) + # @sg-ignore Need to add nil check here node.children[1..-1].each do |cc| if cc.nil? result.push NIL_NODE @@ -460,7 +468,6 @@ def reduce_to_value_nodes nodes elsif COMPOUND_STATEMENTS.include?(node.type) result.concat from_value_position_compound_statement(node) elsif CONDITIONAL_ALL_BUT_FIRST.include?(node.type) - # @sg-ignore Need to add nil check here result.concat reduce_to_value_nodes(node.children[1..-1]) elsif node.type == :return result.concat reduce_to_value_nodes([node.children[0]]) diff --git a/lib/solargraph/parser/parser_gem/node_processors/args_node.rb b/lib/solargraph/parser/parser_gem/node_processors/args_node.rb index 8d601bf6e..9bb0c4645 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/args_node.rb @@ -20,10 +20,12 @@ def process name: u.children[0].to_s, assignment: u.children[1], asgn_code: u.children[1] ? region.code_for(u.children[1]) : nil, + # @sg-ignore Need to add nil check here presence: callable.location.range, decl: get_decl(u), source: :parser ) + # @sg-ignore Translate to something flow sensitive typing understands callable.parameters.push locals.last end end @@ -40,6 +42,7 @@ def forward(callable) locals.push Solargraph::Pin::Parameter.new( location: loc, closure: callable, + # @sg-ignore Need to add nil check here presence: region.closure.location.range, decl: get_decl(node), source: :parser diff --git a/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb b/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb index 938483652..63e2c55dc 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb @@ -9,6 +9,7 @@ class LvasgnNode < Parser::NodeProcessor::Base def process here = get_node_start_position(node) + # @sg-ignore Need to add nil check here presence = Range.new(here, region.closure.location.range.ending) loc = get_node_location(node) locals.push Solargraph::Pin::LocalVariable.new( diff --git a/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb b/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb index 21e32bd22..c59c2ee04 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb @@ -11,6 +11,7 @@ class ResbodyNode < Parser::NodeProcessor::Base def process if node.children[1] # Exception local variable name here = get_node_start_position(node.children[1]) + # @sg-ignore Need to add nil check here presence = Range.new(here, region.closure.location.range.ending) loc = get_node_location(node.children[1]) types = if node.children[0].nil? 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..bbb210012 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -54,6 +54,7 @@ def process # @return [void] def process_visibility if (node.children.length > 2) + # @sg-ignore Need to add nil check here node.children[2..-1].each do |child| # @sg-ignore Variable type could not be inferred for method_name # @type [Symbol] @@ -83,6 +84,7 @@ def process_visibility # @return [void] def process_attribute + # @sg-ignore Need to add nil check here node.children[2..-1].each do |a| loc = get_node_location(node) clos = region.closure @@ -123,6 +125,7 @@ def process_attribute def process_include if node.children[2].is_a?(AST::Node) && node.children[2].type == :const cp = region.closure + # @sg-ignore Need to add nil check here node.children[2..-1].each do |i| type = region.scope == :class ? Pin::Reference::Extend : Pin::Reference::Include pins.push type.new( @@ -139,6 +142,7 @@ def process_include def process_prepend if node.children[2].is_a?(AST::Node) && node.children[2].type == :const cp = region.closure + # @sg-ignore Need to add nil check here node.children[2..-1].each do |i| pins.push Pin::Reference::Prepend.new( location: get_node_location(i), @@ -152,6 +156,7 @@ def process_prepend # @return [void] def process_extend + # @sg-ignore Need to add nil check here node.children[2..-1].each do |i| loc = get_node_location(node) if i.type == :self @@ -194,6 +199,7 @@ def process_module_function # @todo Smelly instance variable access region.instance_variable_set(:@visibility, :module_function) elsif node.children[2].type == :sym || node.children[2].type == :str + # @sg-ignore Need to add nil check here node.children[2..-1].each do |x| cn = x.children[0].to_s # @type [Pin::Method, nil] diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 89cb0fbf1..436346df7 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -428,6 +428,7 @@ def erase_generics(generics_to_erase) # @return [String, nil] def filename return nil if location.nil? + # @sg-ignore flow sensitive typing needs a not-nil override pin location.filename end @@ -464,6 +465,7 @@ def best_location def nearly? other self.class == other.class && name == other.name && + # @sg-ignore flow sensitive typing needs a not-nil override pin (closure == other.closure || (closure && closure.nearly?(other.closure))) && (comments == other.comments || (((maybe_directives? == false && other.maybe_directives? == false) || compare_directives(directives, other.directives)) && @@ -515,6 +517,7 @@ def macros # # @return [Boolean] def maybe_directives? + # @sg-ignore flow sensitive typing needs a not-nil override pin return !@directives.empty? if defined?(@directives) && @directives @maybe_directives ||= comments.include?('@!') end diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 7d0b7f5a1..75cda2c6a 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -66,7 +66,9 @@ def return_types_from_node(parent_node, api_map) else rng = Range.from_node(node) next if rng.nil? + # @sg-ignore flow sensitive typing needs a not-nil override pin pos = rng.ending + # @sg-ignore Need to add nil check here clip = api_map.clip_at(location.filename, pos) # Use the return node for inference. The clip might infer from the # first node in a method call instead of the entire call. @@ -111,6 +113,7 @@ def == other end def type_desc + # @sg-ignore should understand meaning of &. "#{super} = #{assignment&.type.inspect}" end diff --git a/lib/solargraph/pin/block.rb b/lib/solargraph/pin/block.rb index 2e99ad2df..00fe0b7c1 100644 --- a/lib/solargraph/pin/block.rb +++ b/lib/solargraph/pin/block.rb @@ -30,6 +30,7 @@ def rebind api_map end def binder + # @sg-ignore Need to add nil check here @rebind&.defined? ? @rebind : closure.binder end @@ -50,6 +51,7 @@ def destructure_yield_types(yield_types, parameters) # @return [::Array] def typify_parameters(api_map) chain = Parser.chain(receiver, filename, node) + # @sg-ignore Need to add nil check here clip = api_map.clip_at(location.filename, location.range.start) locals = clip.locals - [self] # @sg-ignore Need to add nil check here @@ -67,6 +69,7 @@ def typify_parameters(api_map) param_type = chain.base.infer(api_map, param, locals) unless arg_type.nil? if arg_type.generic? && param_type.defined? + # @sg-ignore Need to add nil check here namespace_pin = api_map.get_namespace_pins(meth.namespace, closure.namespace).first arg_type.resolve_generics(namespace_pin, param_type) else @@ -86,6 +89,7 @@ def typify_parameters(api_map) def maybe_rebind api_map return ComplexType::UNDEFINED unless receiver + # @sg-ignore Need to add nil check here chain = Parser.chain(receiver, location.filename) # @sg-ignore Need to add nil check here locals = api_map.source_map(location.filename).locals_at(location) diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index c9146f0d0..91885baaf 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -21,8 +21,10 @@ def initialize block: nil, return_type: nil, parameters: [], **splat @parameters = parameters end + # @sg-ignore Need to add nil check here # @return [String] def method_namespace + # @sg-ignore Need to add nil check here closure.namespace end @@ -113,6 +115,7 @@ def resolve_generics_from_context(generics_to_resolve, param.dup else param.resolve_generics_from_context(generics_to_resolve, + # @sg-ignore flow sensitive typing needs a not-nil override pin arg_types[i], resolved_generic_values: resolved_generic_values) end @@ -136,9 +139,11 @@ def typify api_map end end + # @sg-ignore Need to add nil check here # @return [String] def method_name raise "closure was nil in #{self.inspect}" if closure.nil? + # @sg-ignore Need to add nil check here @method_name ||= closure.name end @@ -183,7 +188,6 @@ def resolve_generics_from_context_until_complete(generics_to_resolve, resolved_generic_values: resolved_generic_values) end - # @return [Array] # @yieldparam [ComplexType] # @yieldreturn [ComplexType] # @return [self] @@ -215,6 +219,7 @@ def mandatory_positional_param_count end def to_rbs + # @sg-ignore Need to add nil check here 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 3230603db..2efd6ad53 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -37,6 +37,7 @@ def context @context ||= begin result = super if scope == :instance + # @sg-ignore Need support for reduce_class_type in UniqueType result.reduce_class_type else result @@ -52,6 +53,7 @@ def binder def gates # @todo This check might not be necessary. There should always be a # root pin + # @sg-ignore flow sensitive typing needs a not-nil override pin closure ? closure.gates : [''] end diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 51b47e62f..8fd9cb739 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -72,6 +72,7 @@ def find_context elsif here.is_a?(Pin::Method) return here.context end + # @sg-ignore Need to add nil check here here = here.closure end ComplexType::ROOT diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index 7ce9bd2f9..d56818bda 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -15,6 +15,7 @@ class DelegatedMethod < Pin::Method # @param receiver [Source::Chain, nil] the source code used to resolve the receiver for this delegated method. # @param name [String, nil] # @param receiver_method_name [String, nil] the method name that will be called on the receiver (defaults to :name). + # @sg-ignore should understand meaning of &. 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) super(name: name, **splat) @@ -73,6 +74,7 @@ def resolvable?(api_map) def resolve_method api_map return if @resolved_method + # @sg-ignore Need to add nil check here resolver = @receiver_chain.define(api_map, self, []).first unless resolver @@ -81,16 +83,21 @@ def resolve_method api_map return end + # @sg-ignore Need to add nil check here receiver_type = resolver.return_type + # @sg-ignore Need to add nil check here return if receiver_type.undefined? receiver_path, method_scope = + # @sg-ignore Need to add nil check here if @receiver_chain.constant? # HACK: the `return_type` of a constant is Class, but looking up a method expects # the arguments `"Whatever"` and `scope: :class`. + # @sg-ignore Need to add nil check here [receiver_type.to_s.sub(/^Class<(.+)>$/, '\1'), :class] else + # @sg-ignore Need to add nil check here [receiver_type.to_s, :instance] end diff --git a/lib/solargraph/pin/documenting.rb b/lib/solargraph/pin/documenting.rb index bd8b1fe9a..cbeaf2a0d 100644 --- a/lib/solargraph/pin/documenting.rb +++ b/lib/solargraph/pin/documenting.rb @@ -104,6 +104,7 @@ def self.normalize_indentation text left = text.lines.map do |line| match = line.match(/^ +/) next 0 unless match + # @sg-ignore Need to add nil check here match[0].length end.min return text if left.nil? || left.zero? diff --git a/lib/solargraph/pin/instance_variable.rb b/lib/solargraph/pin/instance_variable.rb index 6774924fb..0f85a4e7a 100644 --- a/lib/solargraph/pin/instance_variable.rb +++ b/lib/solargraph/pin/instance_variable.rb @@ -3,13 +3,17 @@ module Solargraph module Pin class InstanceVariable < BaseVariable + # @sg-ignore Need to add nil check here # @return [ComplexType, ComplexType::UniqueType] def binder + # @sg-ignore Need to add nil check here closure.binder end + # @sg-ignore Need to add nil check here # @return [::Symbol] def scope + # @sg-ignore Need to add nil check here closure.binder.scope end @@ -20,6 +24,7 @@ def context if scope == :class ComplexType.parse("::Class<#{result.rooted_namespace}>") else + # @sg-ignore Need support for reduce_class_type in UniqueType result.reduce_class_type end end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 94b030b4d..129e68aee 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -34,6 +34,7 @@ def combine_with(other, attrs={}) # @param other_closure [Pin::Closure] # @param other_loc [Location] def visible_at?(other_closure, other_loc) + # @sg-ignore Need to add nil check here location.filename == other_loc.filename && presence.include?(other_loc.range.start) && # @sg-ignore Need to add nil check here @@ -67,6 +68,7 @@ def match_named_closure needle, haystack until cursor.nil? return true if needle.path == cursor.path return false if cursor.path && !cursor.path.empty? + # @sg-ignore Need to add nil check here cursor = cursor.closure end false diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 3a392ec28..e7e963383 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -65,7 +65,9 @@ def combine_visibility(other) # @param other [Pin::Method] # @return [Array] def combine_signatures(other) + # @sg-ignore Need to add nil check here all_undefined = signatures.all? { |sig| sig.return_type.undefined? } + # @sg-ignore Need to add nil check here other_all_undefined = other.signatures.all? { |sig| sig.return_type.undefined? } if all_undefined && !other_all_undefined other.signatures @@ -202,6 +204,7 @@ def generate_signature(parameters, return_type) comments: p.text, name: name, decl: decl, + # @sg-ignore flow sensitive typing needs a not-nil override pin presence: location ? location.range : nil, return_type: ComplexType.try_parse(*p.types), source: source @@ -213,6 +216,7 @@ def generate_signature(parameters, return_type) end signature = Signature.new(generics: generics, parameters: parameters, return_type: return_type, block: block, closure: self, source: source, location: location, type_location: type_location) + # @sg-ignore Need to add nil check here block.closure = signature if block signature end @@ -248,6 +252,7 @@ def detail else "(#{signatures.first.parameters.map(&:full).join(', ')}) " unless signatures.first.parameters.empty? end.to_s + # @sg-ignore Need to add nil check here detail += "=#{probed? ? '~' : (proxied? ? '^' : '>')} #{return_type.to_s}" unless return_type.undefined? detail.strip! return nil if detail.empty? @@ -277,6 +282,7 @@ def to_rbs return nil if signatures.empty? rbs = "def #{name}: #{signatures.first.to_rbs}" + # @sg-ignore Need to add nil check here signatures[1..].each do |sig| rbs += "\n" rbs += (' ' * (4 + name.length)) @@ -295,6 +301,7 @@ def method_name end def typify api_map + # @sg-ignore Need to add nil check here logger.debug { "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context.rooted_tags}, return_type=#{return_type.rooted_tags}) - starting" } decl = super unless decl.undefined? @@ -302,9 +309,12 @@ def typify api_map return decl end type = see_reference(api_map) || typify_from_super(api_map) + # @sg-ignore Need to add nil check here logger.debug { "Method#typify(self=#{self}) - type=#{type&.rooted_tags.inspect}" } unless type.nil? + # @sg-ignore flow sensitive typing needs a not-nil override pin qualified = type.qualify(api_map, namespace) + # @sg-ignore flow sensitive typing needs a not-nil override pin logger.debug { "Method#typify(self=#{self}) => #{qualified.rooted_tags.inspect}" } return qualified end @@ -409,6 +419,7 @@ def overloads comments: tag.docstring.all.to_s, name: name, decl: decl, + # @sg-ignore flow sensitive typing needs a not-nil override pin presence: location ? location.range : nil, return_type: param_type_from_name(tag, src.first), source: :overloads @@ -444,6 +455,7 @@ def resolve_ref_tag api_map end next unless ref + # @sg-ignore flow sensitive typing needs a not-nil override pin docstring.add_tag(*ref.docstring.tags(:param)) end self @@ -463,10 +475,12 @@ def rest_of_stack api_map attr_writer :documentation + # @sg-ignore Need to add nil check here def dodgy_visibility_source? # as of 2025-03-12, the RBS generator used for # e.g. activesupport did not understand 'private' markings # inside 'class << self' blocks, but YARD did OK at it + # @sg-ignore Need to add nil check here source == :rbs && scope == :class && type_location&.filename&.include?('generated') && return_type.undefined? || # YARD's RBS generator seems to miss a lot of should-be protected instance methods source == :rbs && scope == :instance && namespace.start_with?('YARD::') || @@ -550,6 +564,7 @@ def typify_from_super api_map stack = rest_of_stack api_map return nil if stack.empty? stack.each do |pin| + # @sg-ignore Need to add nil check here return pin.return_type unless pin.return_type.undefined? end nil @@ -565,6 +580,7 @@ def resolve_reference ref, api_map else fqns = api_map.qualify(parts.first, namespace) return ComplexType::UNDEFINED if fqns.nil? + # @sg-ignore Need to add nil check here path = fqns + ref[parts.first.length] + parts.last end pins = api_map.get_path_pins(path) @@ -600,9 +616,12 @@ def infer_from_return_nodes api_map rng = Range.from_node(n) next unless rng clip = api_map.clip_at( + # @sg-ignore Need to add nil check here location.filename, + # @sg-ignore Need to add nil check here rng.ending ) + # @sg-ignore Need to add nil check here chain = Solargraph::Parser.chain(n, location.filename) type = chain.infer(api_map, self, clip.locals) result.push type unless type.undefined? diff --git a/lib/solargraph/pin/namespace.rb b/lib/solargraph/pin/namespace.rb index 6258db456..8799e970a 100644 --- a/lib/solargraph/pin/namespace.rb +++ b/lib/solargraph/pin/namespace.rb @@ -38,6 +38,7 @@ def initialize type: :class, visibility: :public, gates: [''], name: '', **splat closure_name = if [Solargraph::Pin::ROOT_PIN, nil].include?(closure) '' else + # @sg-ignore Need to add nil check here closure.full_context.namespace + '::' end closure_name += parts.join('::') diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 4a71fb23d..d26eac282 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -21,10 +21,12 @@ def initialize decl: :arg, asgn_code: nil, **splat @decl = decl end + # @sg-ignore Should better support meaning of '&' in RBS def type_location super || closure&.type_location end + # @sg-ignore Should better support meaning of '&' in RBS def location super || closure&.type_location end @@ -43,6 +45,7 @@ def keyword? end def kwrestarg? + # @sg-ignore flow sensitive typing needs a not-nil override pin decl == :kwrestarg || (assignment && [:HASH, :hash].include?(assignment.type)) end @@ -149,7 +152,9 @@ def return_type if @return_type.nil? @return_type = ComplexType::UNDEFINED found = param_tag + # @sg-ignore flow sensitive typing needs a not-nil override pin @return_type = ComplexType.try_parse(*found.types) unless found.nil? or found.types.nil? + # @sg-ignore flow sensitive typing needs a not-nil override pin if @return_type.undefined? if decl == :restarg @return_type = ComplexType.try_parse('::Array') @@ -166,16 +171,17 @@ def return_type # The parameter's zero-based location in the block's signature. # - # @sg-ignore this won't be nil if our code is correct + # @sg-ignore Need to add nil check here # @return [Integer] def index method_pin = closure - # @sg-ignore TODO: Unresolved call to parameter_names + # @sg-ignore Need to add nil check here method_pin.parameter_names.index(name) end # @param api_map [ApiMap] def typify api_map + # @sg-ignore Need to add nil check here return return_type.qualify(api_map, closure.context.namespace) unless return_type.undefined? closure.is_a?(Pin::Block) ? typify_block_param(api_map) : typify_method_param(api_map) end @@ -195,10 +201,11 @@ def compatible_arg?(atype, api_map) ptype.generic? end - # @sg-ignore flow sensitive typing needs a not-nil override pin def documentation tag = param_tag + # @sg-ignore flow sensitive typing needs a not-nil override pin return '' if tag.nil? || tag.text.nil? + # @sg-ignore flow sensitive typing needs a not-nil override pin tag.text end @@ -206,10 +213,13 @@ def documentation # @return [YARD::Tags::Tag, nil] def param_tag + # @sg-ignore Need to add nil check here params = closure.docstring.tags(:param) + # @sg-ignore Need to add nil check here params.each do |p| return p if p.name == name end + # @sg-ignore Need to add nil check here params[index] if index && params[index] && (params[index].name.nil? || params[index].name.empty?) end @@ -226,6 +236,7 @@ def typify_block_param api_map # @param api_map [ApiMap] # @return [ComplexType] def typify_method_param api_map + # @sg-ignore Need to add nil check here meths = api_map.get_method_stack(closure.full_context.tag, closure.name, scope: closure.scope) # meths.shift # Ignore the first one meths.each do |meth| @@ -239,6 +250,7 @@ def typify_method_param api_map if found.nil? and !index.nil? found = params[index] if params[index] && (params[index].name.nil? || params[index].name.empty?) end + # @sg-ignore flow sensitive typing needs a not-nil override pin return ComplexType.try_parse(*found.types).qualify(api_map, meth.context.namespace) unless found.nil? || found.types.nil? end ComplexType::UNDEFINED @@ -272,6 +284,7 @@ def resolve_reference ref, api_map, skip else fqns = api_map.qualify(parts.first, namespace) return nil if fqns.nil? + # @sg-ignore Need to add nil check here path = fqns + ref[parts.first.length] + parts.last end pins = api_map.get_path_pins(path) diff --git a/lib/solargraph/pin/proxy_type.rb b/lib/solargraph/pin/proxy_type.rb index 495f3decc..bfa906c08 100644 --- a/lib/solargraph/pin/proxy_type.rb +++ b/lib/solargraph/pin/proxy_type.rb @@ -22,6 +22,7 @@ def context def self.anonymous context, closure: nil, binder: nil, **kwargs unless closure parts = context.namespace.split('::') + # @sg-ignore Need to add nil check here namespace = parts[0..-2].join('::').to_s closure = Solargraph::Pin::Namespace.new(name: namespace, source: :proxy_type) end diff --git a/lib/solargraph/pin/reference.rb b/lib/solargraph/pin/reference.rb index d678ab7b7..b5afb0451 100644 --- a/lib/solargraph/pin/reference.rb +++ b/lib/solargraph/pin/reference.rb @@ -39,8 +39,10 @@ def parametrized_tag ) end + # @sg-ignore Need to add nil check here # @return [Array] def reference_gates + # @sg-ignore Need to add nil check here closure.gates end end diff --git a/lib/solargraph/pin/reference/superclass.rb b/lib/solargraph/pin/reference/superclass.rb index c50f640df..c13522648 100644 --- a/lib/solargraph/pin/reference/superclass.rb +++ b/lib/solargraph/pin/reference/superclass.rb @@ -6,7 +6,9 @@ class Reference # A Superclass reference pin. # class Superclass < Reference + # @sg-ignore Need to add nil check here def reference_gates + # @sg-ignore Need to add nil check here @reference_gates ||= closure.gates - [closure.path] end end diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 23cc09256..620ae206e 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -21,21 +21,27 @@ def identity attr_writer :closure + # @sg-ignore Should better support meaning of '&' in RBS def dodgy_return_type_source? super || closure&.dodgy_return_type_source? end + # @sg-ignore Should better support meaning of '&' in RBS def type_location super || closure&.type_location end + # @sg-ignore Should better support meaning of '&' in RBS def location super || closure&.location end def typify api_map + # @sg-ignore Need to add nil check here if return_type.defined? + # @sg-ignore Need to add nil check here qualified = return_type.qualify(api_map, closure.namespace) + # @sg-ignore Need to add nil check here logger.debug { "Signature#typify(self=#{self}) => #{qualified.rooted_tags.inspect}" } return qualified end @@ -48,8 +54,11 @@ def typify api_map method_stack.each do |pin| sig = pin.signatures.find { |s| s.arity == self.arity } next unless sig + # @sg-ignore Need to add nil check here unless sig.return_type.undefined? + # @sg-ignore Need to add nil check here qualified = sig.return_type.qualify(api_map, closure.namespace) + # @sg-ignore Need to add nil check here logger.debug { "Signature#typify(self=#{self}) => #{qualified.rooted_tags.inspect}" } return qualified end diff --git a/lib/solargraph/range.rb b/lib/solargraph/range.rb index 9cdc01a9b..0b13ad130 100644 --- a/lib/solargraph/range.rb +++ b/lib/solargraph/range.rb @@ -59,6 +59,7 @@ def contain? position # True if the range contains the specified position and the position does not precede it. # # @param position [Position, Array(Integer, Integer)] + # @sg-ignore Should handle redefinition of types in simple contexts # @return [Boolean] def include? position position = Position.normalize(position) diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index 40e6fed0b..8c47ad21a 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -58,6 +58,7 @@ def cache_key data = gem_config&.to_s end end + # @sg-ignore flow sensitive typing needs a not-nil override pin if data.nil? || data.empty? if resolved? # definitely came from the gem itself and not elsewhere - diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 4888bf744..f4104fccb 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -259,6 +259,7 @@ def create_constant(name, tag, comments, decl, base = nil) parts = name.split('::') if parts.length > 1 name = parts.last + # @sg-ignore Need to add nil check here closure = pins.select { |pin| pin && pin.path == parts[0..-2].join('::') }.first else name = parts.first diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index b6804157f..43b8f745f 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -19,6 +19,7 @@ def initialize library @pins = cached_pins @resolved = true @loaded = true + # @sg-ignore flow sensitive typing needs a not-nil override pin logger.debug { "Deserialized #{cached_pins.length} cached pins for stdlib require #{library.inspect}" } else super diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index ff5298b68..ac846cb1f 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -223,13 +223,16 @@ def scan pin.typify api_map pin.probe api_map rescue StandardError => e + # @sg-ignore Need to add nil check here STDERR.puts "Error testing #{pin_description(pin)} #{pin.location ? "at #{pin.location.filename}:#{pin.location.range.start.line + 1}" : ''}" STDERR.puts "[#{e.class}]: #{e.message}" + # @sg-ignore Need to add nil check here STDERR.puts e.backtrace.join("\n") exit 1 end end } + # @sg-ignore Need to add nil check here puts "Scanned #{directory} (#{api_map.pins.length} pins) in #{time.real} seconds." end @@ -250,6 +253,7 @@ def list def pin_description pin desc = if pin.path.nil? || pin.path.empty? if pin.closure + # @sg-ignore Need to add nil check here "#{pin.closure.path} | #{pin.name}" else "#{pin.context.namespace} | #{pin.name}" @@ -257,6 +261,7 @@ def pin_description pin else pin.path end + # @sg-ignore Need to add nil check here desc += " (#{pin.location.filename} #{pin.location.range.start.line})" if pin.location desc end diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 3c9cbd001..74b011b96 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -133,20 +133,29 @@ def string_at? position return false if Position.to_offset(code, position) >= code.length string_nodes.each do |node| range = Range.from_node(node) + # @sg-ignore Need to add nil check here next if range.ending.line < position.line + # @sg-ignore Need to add nil check here break if range.ending.line > position.line + # @sg-ignore Need to add nil check here return true if node.type == :str && range.include?(position) && range.start != position + # @sg-ignore Need to add nil check here return true if [:STR, :str].include?(node.type) && range.include?(position) && range.start != position if node.type == :dstr inner = node_at(position.line, position.column) next if inner.nil? inner_range = Range.from_node(inner) + # @sg-ignore Need to add nil check here next unless range.include?(inner_range.ending) return true if inner.type == :str + # @sg-ignore Need to add nil check here inner_code = at(Solargraph::Range.new(inner_range.start, position)) + # @sg-ignore Need to add nil check here return true if (inner.type == :dstr && inner_range.ending.character <= position.character) && !inner_code.end_with?('}') || + # @sg-ignore Need to add nil check here (inner.type != :dstr && inner_range.ending.line == position.line && position.character <= inner_range.ending.character && inner_code.end_with?('}')) end + # @sg-ignore Need to add nil check here break if range.ending.line > position.line end false @@ -183,7 +192,9 @@ def error_ranges # @return [String] def code_for(node) rng = Range.from_node(node) + # @sg-ignore Need to add nil check here b = Position.line_char_to_offset(code, rng.start.line, rng.start.column) + # @sg-ignore Need to add nil check here e = Position.line_char_to_offset(code, rng.ending.line, rng.ending.column) frag = code[b..e-1].to_s frag.strip.gsub(/,$/, '') @@ -194,7 +205,9 @@ def code_for(node) # @return [String, nil] def comments_for node rng = Range.from_node(node) + # @sg-ignore Need to add nil check here stringified_comments[rng.start.line] ||= begin + # @sg-ignore Need to add nil check here buff = associated_comments[rng.start.line] # @sg-ignore flow sensitive typing needs a not-nil override pin buff ? stringify_comment_array(buff) : nil @@ -246,14 +259,17 @@ def associated_comments # @type [Integer, nil] last = nil comments.each_pair do |num, snip| + # @sg-ignore flow sensitive typing needs a not-nil override pin if !last || num == last + 1 buffer.concat "#{snip.text}\n" else + # @sg-ignore flow sensitive typing needs a not-nil override pin result[first_not_empty_from(last + 1)] = buffer.clone buffer.replace "#{snip.text}\n" end last = num end + # @sg-ignore Need to add nil check here result[first_not_empty_from(last + 1)] = buffer unless buffer.empty? || last.nil? result end @@ -278,7 +294,9 @@ def inner_folding_ranges top, result = [], parent = nil return unless Parser.is_ast_node?(top) if FOLDING_NODE_TYPES.include?(top.type) range = Range.from_node(top) + # @sg-ignore Need to add nil check here if result.empty? || range.start.line > result.last.start.line + # @sg-ignore Need to add nil check here result.push range unless range.ending.line - range.start.line < 2 end end @@ -303,6 +321,7 @@ def stringify_comment_array comments ctxt.concat p else here = p.index(/[^ \t]/) + # @sg-ignore flow sensitive typing needs a not-nil override pin skip = here if skip.nil? || here < skip ctxt.concat p[skip..-1] end @@ -369,6 +388,7 @@ def string_nodes_in n def inner_tree_at node, position, stack return if node.nil? here = Range.from_node(node) + # @sg-ignore Need to add nil check here if here.contain?(position) stack.unshift node node.children.each do |c| diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index 97f9acc41..87385663c 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -114,6 +114,7 @@ def define api_map, name_pin, locals # # @todo ProxyType uses 'type' for the binder, but ' working_pin = name_pin + # @sg-ignore Need to add nil check here links[0..-2].each do |link| pins = link.resolve(api_map, working_pin, locals) type = infer_from_definitions(pins, working_pin, api_map, locals) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 7898e14a7..563020a53 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -37,6 +37,7 @@ def initialize word, location = nil, arguments = [], block = nil # @sg-ignore Fix "Not enough arguments to Module#protected" protected def equality_fields + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array super + [arguments, block] end @@ -56,22 +57,26 @@ def resolve api_map, name_pin, locals # @type [Array] [] end - # @sg-ignore TODO: Wrong argument type for + # @sg-ignore Wrong argument type for # Solargraph::Source::Chain::Call#inferred_pins: pins # expected Enumerable, received # Array, Array - need to # look through logic to understand whether this is dead # code return inferred_pins(found, api_map, name_pin, locals) unless found.empty? + # @sg-ignore Unresolved call to map on void, ::Enumerator<::Solargraph::ComplexType::UniqueType> pin_groups = name_pin.binder.each_unique_type.map do |context| ns_tag = context.namespace == '' ? '' : context.namespace_type.tag stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) [stack.first].compact end + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array if !api_map.loose_unions && pin_groups.any? { |pins| pins.empty? } pin_groups = [] end + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array pins = pin_groups.flatten.uniq(&:path) + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array return [] if pins.empty? inferred_pins(pins, api_map, name_pin, locals) end @@ -118,6 +123,7 @@ def inferred_pins pins, api_map, name_pin, locals if match if ol.block && with_block? block_atypes = ol.block.parameters.map(&:return_type) + # @sg-ignore Need to add nil check here if block.links.map(&:class) == [BlockSymbol] # like the bar in foo(&:bar) blocktype = block_symbol_call_type(api_map, name_pin.context, block_atypes, locals) @@ -148,6 +154,7 @@ def inferred_pins pins, api_map, name_pin, locals # # qualify(), however, happens in the namespace where # the docs were written - from the method pin. + # @sg-ignore Need to add nil check here type = with_params(new_return_type.self_to_type(self_type), self_type).qualify(api_map, p.namespace) if new_return_type.defined? type ||= ComplexType::UNDEFINED end @@ -168,11 +175,15 @@ def inferred_pins pins, api_map, name_pin, locals logger.debug { "Call#inferred_pins(name_pin.binder=#{name_pin.binder}, word=#{word}, pins=#{pins.map(&:desc)}, name_pin=#{name_pin}) - result=#{result}" } out = result.map do |pin| if pin.path == 'Class#new' && name_pin.binder.tag != 'Class' + # @sg-ignore TODO: UniqueType needs to support reduce_class_type reduced_context = name_pin.binder.reduce_class_type pin.proxy(reduced_context) else + # @sg-ignore Need to add nil check here next pin if pin.return_type.undefined? + # @sg-ignore Need to add nil check here selfy = pin.return_type.self_to_type(name_pin.binder) + # @sg-ignore Need to add nil check here selfy == pin.return_type ? pin : pin.proxy(selfy) end end @@ -222,16 +233,18 @@ def process_directive pin, api_map, context, locals def inner_process_macro pin, macro, api_map, context, locals vals = arguments.map{ |c| Pin::ProxyType.anonymous(c.infer(api_map, pin, locals), source: :chain) } txt = macro.tag.text.clone + # @sg-ignore Need to add nil check here if txt.empty? && macro.tag.name named = api_map.named_macro(macro.tag.name) + # @sg-ignore Need to add nil check here txt = named.tag.text.clone if named end i = 1 vals.each do |v| + # @sg-ignore Need to add nil check here txt.gsub!(/\$#{i}/, v.context.namespace) i += 1 end - # @sg-ignore Need to add support for all unique type match checking on lhs as well docstring = Solargraph::Source.parse_docstring(txt).to_docstring tag = docstring.tag(:return) unless tag.nil? || tag.types.nil? @@ -269,6 +282,7 @@ def find_method_pin(name_pin) def super_pins api_map, name_pin method_pin = find_method_pin(name_pin) return [] if method_pin.nil? + # @sg-ignore Need to add nil check here pins = api_map.get_method_stack(method_pin.namespace, method_pin.name, scope: method_pin.context.scope) pins.reject{|p| p.path == name_pin.path} end @@ -280,6 +294,7 @@ def yield_pins api_map, name_pin method_pin = find_method_pin(name_pin) return [] unless method_pin + # @sg-ignore flow sensitive typing needs a not-nil override pin method_pin.signatures.map(&:block).compact.map do |signature_pin| return_type = signature_pin.return_type.qualify(api_map, name_pin.namespace) signature_pin.proxy(return_type) @@ -304,10 +319,12 @@ def fix_block_pass # @param context [ComplexType, ComplexType::UniqueType] # @param block_parameter_types [::Array] # @param locals [::Array] + # @sg-ignore Need to add nil check here # @return [ComplexType, nil] def block_symbol_call_type(api_map, context, block_parameter_types, locals) # Ruby's shorthand for sending the passed in method name # to the first yield parameter with no arguments + # @sg-ignore Need to add nil check here block_symbol_name = block.links.first.word block_symbol_call_path = "#{block_parameter_types.first}##{block_symbol_name}" callee = api_map.get_path_pins(block_symbol_call_path).first @@ -315,6 +332,7 @@ def block_symbol_call_type(api_map, context, block_parameter_types, locals) # @todo: Figure out why we get unresolved generics at # this point and need to assume method return types # based on the generic type + # @sg-ignore Need to add nil check here return_type ||= api_map.get_path_pins("#{context.subtypes.first}##{block.links.first.word}").first&.return_type return_type || ComplexType::UNDEFINED end @@ -322,6 +340,7 @@ def block_symbol_call_type(api_map, context, block_parameter_types, locals) # @param api_map [ApiMap] # @return [Pin::Block, nil] def find_block_pin(api_map) + # @sg-ignore Need to add nil check here node_location = Solargraph::Location.from_node(block.node) return if node_location.nil? block_pins = api_map.get_block_pins @@ -339,6 +358,7 @@ def block_call_type(api_map, name_pin, locals) block_context_pin = name_pin block_pin = find_block_pin(api_map) + # @sg-ignore flow sensitive typing needs a not-nil override pin block_context_pin = block_pin.closure if block_pin # @sg-ignore Need to add nil check here block.infer(api_map, block_context_pin, locals) diff --git a/lib/solargraph/source/chain/constant.rb b/lib/solargraph/source/chain/constant.rb index 58b274aeb..fa3b1f604 100644 --- a/lib/solargraph/source/chain/constant.rb +++ b/lib/solargraph/source/chain/constant.rb @@ -17,6 +17,7 @@ def resolve api_map, name_pin, locals base = word gates = crawl_gates(name_pin) end + # @sg-ignore Need to add nil check here parts = base.split('::') gates.each do |gate| # @todo 'Wrong argument type for @@ -27,12 +28,14 @@ def resolve api_map, name_pin, locals # shouldn't be. type = deep_constant_type(gate, api_map) # Use deep inference to resolve root + # @sg-ignore Need to add nil check here parts[0..-2].each do |sym| pins = api_map.get_constants('', type.namespace).select{ |pin| pin.name == sym } type = first_pin_type(pins, api_map) break if type.undefined? end next if type.undefined? + # @sg-ignore Need to add nil check here result = api_map.get_constants('', type.namespace).select { |pin| pin.name == parts.last } return result unless result.empty? end @@ -51,6 +54,7 @@ def crawl_gates pin gates.push('') if gates.empty? return gates end + # @sg-ignore Need to add nil check here clos = clos.closure end [''] diff --git a/lib/solargraph/source/chain/hash.rb b/lib/solargraph/source/chain/hash.rb index 045a7d116..bf2aa484c 100644 --- a/lib/solargraph/source/chain/hash.rb +++ b/lib/solargraph/source/chain/hash.rb @@ -14,6 +14,7 @@ def initialize type, node, splatted = false # @sg-ignore Fix "Not enough arguments to Module#protected" protected def equality_fields + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array super + [@splatted] end diff --git a/lib/solargraph/source/chain/if.rb b/lib/solargraph/source/chain/if.rb index 3a7fa0ca9..db0b2481c 100644 --- a/lib/solargraph/source/chain/if.rb +++ b/lib/solargraph/source/chain/if.rb @@ -15,6 +15,7 @@ def initialize links # @sg-ignore Fix "Not enough arguments to Module#protected" protected def equality_fields + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array super + [@links] end diff --git a/lib/solargraph/source/chain/literal.rb b/lib/solargraph/source/chain/literal.rb index 2e0d65c9e..3e4c3bd54 100644 --- a/lib/solargraph/source/chain/literal.rb +++ b/lib/solargraph/source/chain/literal.rb @@ -25,12 +25,14 @@ def initialize type, node end end @type = type + # @sg-ignore need to be able to resolve same method signature on two different types @literal_type = ComplexType.try_parse(@value.inspect) @complex_type = ComplexType.try_parse(type) end # @sg-ignore Fix "Not enough arguments to Module#protected" protected def equality_fields + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array super + [@value, @type, @literal_type, @complex_type] end diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index eef3fd32e..6189a0a40 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -31,9 +31,11 @@ def write text, nullable = false if nullable and !range.nil? and new_text.match(/[.\[{(@$:]$/) [':', '@'].each do |dupable| next unless new_text == dupable + # @sg-ignore flow sensitive typing needs a not-nil override pin offset = Position.to_offset(text, range.start) if text[offset - 1] == dupable p = Position.from_offset(text, offset - 1) + # @sg-ignore flow sensitive typing needs a not-nil override pin r = Change.new(Range.new(p, range.start), ' ') text = r.write(text) end @@ -58,9 +60,12 @@ def repair text fixed else result = commit text, fixed + # @sg-ignore flow sensitive typing needs a not-nil override pin off = Position.to_offset(text, range.start) + # @sg-ignore Need to add nil check here match = result[0, off].match(/[.:]+\z/) if match + # @sg-ignore Need to add nil check here result = result[0, off].sub(/#{match[0]}\z/, ' ' * match[0].length) + result[off..-1] end result @@ -73,7 +78,9 @@ def repair text # @param insert [String] # @return [String] def commit text, insert + # @sg-ignore Need to add nil check here start_offset = Position.to_offset(text, range.start) + # @sg-ignore Need to add nil check here end_offset = Position.to_offset(text, range.ending) (start_offset == 0 ? '' : text[0..start_offset-1].to_s) + normalize(insert) + text[end_offset..-1].to_s end diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 8f33fdf25..9cecee0e2 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -35,13 +35,14 @@ 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 foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' # @return [String] def start_of_word @start_of_word ||= begin match = source.code[0..offset-1].to_s.match(start_word_pattern) + # @sg-ignore flow sensitive typing needs a not-nil override pin result = (match ? match[0] : '') # Including the preceding colon if the word appears to be a symbol + # @sg-ignore Need to add nil check here result = ":#{result}" if source.code[0..offset-result.length-1].end_with?(':') and !source.code[0..offset-result.length-1].end_with?('::') result end @@ -50,11 +51,11 @@ 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 better ||= handling on ivars # @return [String] def end_of_word @end_of_word ||= begin match = source.code[offset..-1].to_s.match(end_word_pattern) + # @sg-ignore flow sensitive typing needs a not-nil override pin match ? match[0] : '' end end @@ -112,6 +113,7 @@ def string? def recipient @recipient ||= begin node = recipient_node + # @sg-ignore flow sensitive typing needs a not-nil override pin node ? Cursor.new(source, Range.from_node(node).ending) : nil end end @@ -126,8 +128,10 @@ def node def node_position @node_position ||= begin if start_of_word.empty? + # @sg-ignore Need to add nil check here match = source.code[0, offset].match(/\s*(\.|:+)\s*$/) if match + # @sg-ignore Need to add nil check here Position.from_offset(source.code, offset - match[0].length) else position diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index 5d5c5237c..c2b15021c 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -33,6 +33,7 @@ def initialize source, position def chain # Special handling for files that end with an integer and a period return Chain.new([Chain::Literal.new('Integer', Integer(phrase[0..-2])), Chain::UNDEFINED_CALL]) if phrase =~ /^[0-9]+\.$/ + # @sg-ignore Need to add nil check here return Chain.new([Chain::Literal.new('Symbol', phrase[1..].to_sym)]) if phrase.start_with?(':') && !phrase.start_with?('::') return SourceChainer.chain(source, Position.new(position.line, position.character + 1)) if end_of_phrase.strip == '::' && source.code[Position.to_offset(source.code, position)].to_s.match?(/[a-z]/i) begin @@ -56,6 +57,7 @@ def chain rescue Parser::SyntaxError return Chain.new([Chain::UNDEFINED_CALL]) end + # @sg-ignore flow sensitive typing needs a not-nil override pin return Chain.new([Chain::UNDEFINED_CALL]) if node.nil? || (node.type == :sym && !phrase.start_with?(':')) # chain = NodeChainer.chain(node, source.filename, parent && parent.type == :block) chain = Parser.chain(node, source.filename, parent) @@ -96,12 +98,12 @@ def fixed_position @fixed_position ||= Position.from_offset(source.code, offset - end_of_phrase.length) end - # @sg-ignore Need better ||= handling on ivars # @return [String] def end_of_phrase @end_of_phrase ||= begin match = phrase.match(/\s*(\.{1}|::)\s*$/) if match + # @sg-ignore flow sensitive typing needs a not-nil override pin match[0] else '' @@ -152,9 +154,12 @@ def get_signature_data_at index in_whitespace = true else if brackets.zero? and parens.zero? and squares.zero? and in_whitespace + # @sg-ignore Need to add nil check here unless char == '.' or @source.code[index+1..-1].strip.start_with?('.') old = @source.code[index+1..-1] + # @sg-ignore flow sensitive typing needs a not-nil override pin nxt = @source.code[index+1..-1].lstrip + # @sg-ignore flow sensitive typing needs a not-nil override pin index += (@source.code[index+1..-1].length - @source.code[index+1..-1].lstrip.length) break end diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 9d5373a3b..d57e71be2 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -205,7 +205,9 @@ def _locate_pin line, character, *klasses # @todo Attribute pins should not be treated like closures, but # there's probably a better way to handle it next if pin.is_a?(Pin::Method) && pin.attribute? + # @sg-ignore Need to add nil check here found = pin if (klasses.empty? || klasses.any? { |kls| pin.is_a?(kls) } ) && pin.location.range.contain?(position) + # @sg-ignore Need to add nil check here break if pin.location.range.start.line > line end # Assuming the root pin is always valid diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index 681542915..149e0ed3e 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -12,6 +12,7 @@ def initialize api_map, cursor @api_map = api_map @cursor = cursor block_pin = block + # @sg-ignore Need to add nil check here block_pin.rebind(api_map) if block_pin.is_a?(Pin::Block) && !Solargraph::Range.from_node(block_pin.receiver).contain?(cursor.range.start) @in_block = nil end @@ -21,6 +22,7 @@ def define return [] if cursor.comment? || cursor.chain.literal? result = cursor.chain.define(api_map, block, locals) result.concat file_global_methods + # @sg-ignore Need to add nil check here result.concat((source_map.pins + source_map.locals).select{ |p| p.name == cursor.word && p.location.range.contain?(cursor.position) }) if result.empty? result end @@ -159,17 +161,23 @@ def package_completions result # @return [Completion] def tag_complete result = [] + # @sg-ignore Need to add nil check here match = source_map.code[0..cursor.offset-1].match(/[\[<, ]([a-z0-9_:]*)\z/i) if match + # @sg-ignore flow sensitive typing needs a not-nil override pin full = match[1] + # @sg-ignore Need to add nil check here if full.include?('::') + # @sg-ignore Need to add nil check here if full.end_with?('::') # @sg-ignore Need to add nil check here result.concat api_map.get_constants(full[0..-3], *gates) else + # @sg-ignore Need to add nil check here result.concat api_map.get_constants(full.split('::')[0..-2].join('::'), *gates) end else + # @sg-ignore Need to add nil check here result.concat api_map.get_constants('', full.end_with?('::') ? '' : context_pin.full_context.namespace, *gates) #.select { |pin| pin.name.start_with?(full) } end end @@ -186,6 +194,7 @@ def code_complete cursor.chain.base.infer(api_map, context_pin, locals) else if full.include?('::') && cursor.chain.links.length == 1 + # @sg-ignore Need to add nil check here ComplexType.try_parse(full.split('::')[0..-2].join('::')) elsif cursor.chain.links.length > 1 ComplexType.try_parse(full) diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 4f151b59e..34fb8fbe9 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -62,6 +62,7 @@ def pins # @param position [Solargraph::Position] # @return [Solargraph::Pin::Closure] def closure_at(position) + # @sg-ignore Need to add nil check here pins.select{|pin| pin.is_a?(Pin::Closure) and pin.location.range.contain?(position)}.last end @@ -90,11 +91,13 @@ def process_comment source_position, comment_position, comment def find_directive_line_number comment, tag, start # Avoid overruning the index return start unless start < comment.lines.length + # @sg-ignore Need to add nil check here num = comment.lines[start..-1].find_index do |line| # Legacy method directives might be `@method` instead of `@!method` # @todo Legacy syntax should probably emit a warning line.include?("@!#{tag}") || (tag == 'method' && line.include?("@#{tag}")) end + # @sg-ignore Need to add nil check here num.to_i + start end @@ -109,6 +112,7 @@ def process_directive source_position, comment_position, directive case directive.tag.tag_name when 'method' namespace = closure_at(source_position) || @pins.first + # @sg-ignore Need to add nil check here if namespace.location.range.start.line < comment_position.line namespace = closure_at(comment_position) end @@ -133,6 +137,7 @@ def process_directive source_position, comment_position, directive return if directive.tag.name.nil? namespace = closure_at(source_position) t = (directive.tag.types.nil? || directive.tag.types.empty?) ? nil : directive.tag.types.flatten.join('') + # @sg-ignore flow sensitive typing needs a not-nil override pin if t.nil? || t.include?('r') pins.push Solargraph::Pin::Method.new( location: location, @@ -146,6 +151,7 @@ def process_directive source_position, comment_position, directive source: :source_map ) end + # @sg-ignore flow sensitive typing needs a not-nil override pin if t.nil? || t.include?('w') method_pin = Solargraph::Pin::Method.new( location: location, @@ -170,6 +176,7 @@ def process_directive source_position, comment_position, directive name = directive.tag.name closure = closure_at(source_position) || @pins.first + # @sg-ignore Need to add nil check here if closure.location.range.start.line < comment_position.line closure = closure_at(comment_position) end @@ -198,6 +205,7 @@ def process_directive source_position, comment_position, directive comment_position.line end Parser.process_node(src.node, region, @pins) + # @sg-ignore Need to add nil check here @pins[index..-1].each do |p| # @todo Smelly instance variable access p.location.range.start.instance_variable_set(:@line, p.location.range.start.line + loff) @@ -208,6 +216,7 @@ def process_directive source_position, comment_position, directive end when 'domain' namespace = closure_at(source_position) || Pin::ROOT_PIN + # @sg-ignore Need to add nil check here namespace.domains.concat directive.tag.types unless directive.tag.types.nil? when 'override' # @sg-ignore Need to add nil check here @@ -220,7 +229,9 @@ def process_directive source_position, comment_position, directive # @param line1 [Integer] # @param line2 [Integer] + # @sg-ignore Need to add nil check here def no_empty_lines?(line1, line2) + # @sg-ignore Need to add nil check here @code.lines[line1..line2].none? { |line| line.strip.empty? } end @@ -238,6 +249,7 @@ def remove_inline_comment_hashes comment started = true elsif started && !p.strip.empty? cur = p.index(/[^ ]/) + # @sg-ignore flow sensitive typing needs a not-nil override pin num = cur if cur < num end ctxt += "#{p[num..-1]}" if started diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 380b639b0..7711d4b0b 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -42,7 +42,7 @@ def source_map # @return [Source] def source - @source_map.source + source_map.source end # @param inferred [ComplexType] @@ -111,6 +111,7 @@ def load_string code, filename = nil, level = :normal, api_map: nil source = Solargraph::Source.load_string(code, filename) rules = Rules.new(level) api_map ||= Solargraph::ApiMap.new(loose_unions: !rules.require_all_unique_types_match_expected?) + # @sg-ignore Need better ||= handling on locals api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end @@ -136,6 +137,7 @@ def method_return_type_problems_for pin result = [] declared = pin.typify(api_map).self_to_type(pin.full_context).qualify(api_map, pin.full_context.tag) if declared.undefined? + # @sg-ignore Need to add nil check here if pin.return_type.undefined? && rules.require_type_tags? if pin.attribute? inferred = pin.probe(api_map).self_to_type(pin.full_context) @@ -143,6 +145,7 @@ def method_return_type_problems_for pin else result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", pin: pin) end + # @sg-ignore Need to add nil check here elsif pin.return_type.defined? && !resolved_constant?(pin) result.push Problem.new(pin.location, "Unresolved return type #{pin.return_type} for #{pin.path}", pin: pin) elsif rules.must_tag_or_infer? && pin.probe(api_map).undefined? @@ -183,6 +186,7 @@ def resolved_constant? pin # @param pin [Pin::Base] def virtual_pin? pin + # @sg-ignore Need to add nil check here pin.location && source.comment_at?(pin.location.range.ending) end @@ -277,8 +281,9 @@ def const_problems Solargraph::Parser::NodeMethods.const_nodes_from(source.node).each do |const| rng = Solargraph::Range.from_node(const) chain = Solargraph::Parser.chain(const, filename) + # @sg-ignore Need to add nil check here block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore Need to add nil check here location = Location.new(filename, rng) locals = source_map.locals_at(location) pins = chain.define(api_map, block_pin, locals) @@ -295,10 +300,12 @@ def call_problems result = [] Solargraph::Parser::NodeMethods.call_nodes_from(source.node).each do |call| rng = Solargraph::Range.from_node(call) + # @sg-ignore Need to add nil check here next if @marked_ranges.any? { |d| d.contain?(rng.start) } chain = Solargraph::Parser.chain(call, filename) + # @sg-ignore Need to add nil check here block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore Need to add nil check here location = Location.new(filename, rng) locals = source_map.locals_at(location) type = chain.infer(api_map, block_pin, locals) @@ -313,12 +320,16 @@ def call_problems missing = base base = base.base end + # @sg-ignore flow sensitive typing needs a not-nil override pin closest = found.typify(api_map) if found # @todo remove the internal_or_core? check at a higher-than-strict level # @sg-ignore flow sensitive typing needs a not-nil override pin if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) + # @sg-ignore need to be able to resolve same method signature on two different types unless closest.generic? || ignored_pins.include?(found) + # @sg-ignore need to be able to resolve same method signature on two different types if closest.defined? + # @sg-ignore need to be able to resolve same method signature on two different types result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest.rooted_tags}") else result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}") @@ -364,6 +375,7 @@ def argument_problems_for chain, api_map, closure_pin, locals, location end init = api_map.get_method_stack(fqns, 'initialize').first + # @type [::Array] init ? arity_problems_for(init, arguments, location) : [] else arity_problems_for(pin, arguments, location) @@ -570,6 +582,7 @@ def first_param_hash(pins) first_pin = pins.first.proxy first_pin_type param_names = first_pin.parameter_names results = param_hash(first_pin) + # @sg-ignore Need to add nil check here pins[1..].each do |pin| # @todo this assignment from parametric use of Hash should not lose its generic # @type [Hash{String => Hash{Symbol => BasicObject}}] @@ -591,6 +604,7 @@ def first_param_hash(pins) # @param pin [Pin::Base] def internal? pin return false if pin.nil? + # @sg-ignore flow sensitive typing needs a not-nil override pin pin.location && api_map.bundled?(pin.location.filename) end @@ -611,6 +625,7 @@ def declared_externally? pin return true if pin.assignment.nil? chain = Solargraph::Parser.chain(pin.assignment, filename) rng = Solargraph::Range.from_node(pin.assignment) + # @sg-ignore Need to add nil check here block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column) # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" location = Location.new(filename, Range.from_node(pin.assignment)) @@ -627,6 +642,7 @@ def declared_externally? pin missing = base base = base.base end + # @sg-ignore flow sensitive typing needs a not-nil override pin closest = found.typify(api_map) if found # @sg-ignore flow sensitive typing needs to handle "if !foo" if !found || closest.defined? || internal?(found) @@ -729,8 +745,10 @@ def optional_param_count(parameters) end # @param pin [Pin::Method] + # @sg-ignore need boolish support for ? methods def abstract? pin pin.docstring.has_tag?('abstract') || + # @sg-ignore flow sensitive typing needs a not-nil override pin (pin.closure && pin.closure.docstring.has_tag?('abstract')) end @@ -791,6 +809,7 @@ def without_ignored problems problems.reject do |problem| node = source.node_at(problem.location.range.start.line, problem.location.range.start.column) ignored = node && source.comments_for(node)&.include?('@sg-ignore') + # @sg-ignore need to be able to resolve same method signature on two different types 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}" } diff --git a/lib/solargraph/workspace/require_paths.rb b/lib/solargraph/workspace/require_paths.rb index c8eea161b..10dce4053 100644 --- a/lib/solargraph/workspace/require_paths.rb +++ b/lib/solargraph/workspace/require_paths.rb @@ -83,6 +83,7 @@ def require_path_from_gemspec_file gemspec_file_path return [] if hash.empty? hash['paths'].map { |path| File.join(base, path) } rescue StandardError => e + # @sg-ignore Should handle redefinition of types in simple contexts Solargraph.logger.warn "Error reading #{gemspec_file_path}: [#{e.class}] #{e.message}" [] end diff --git a/lib/solargraph/yard_map/mapper.rb b/lib/solargraph/yard_map/mapper.rb index 592b3805e..69a6c1b33 100644 --- a/lib/solargraph/yard_map/mapper.rb +++ b/lib/solargraph/yard_map/mapper.rb @@ -24,6 +24,7 @@ def map end # Some yardocs contain documentation for dependencies that can be # ignored here. The YardMap will load dependencies separately. + # @sg-ignore Need to add nil check here @pins.keep_if { |pin| pin.location.nil? || File.file?(pin.location.filename) } if @spec @pins end diff --git a/lib/solargraph/yard_map/mapper/to_method.rb b/lib/solargraph/yard_map/mapper/to_method.rb index d4547a233..e388e4904 100644 --- a/lib/solargraph/yard_map/mapper/to_method.rb +++ b/lib/solargraph/yard_map/mapper/to_method.rb @@ -26,9 +26,12 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = return_type = ComplexType::SELF if name == 'new' comments = code_object.docstring ? code_object.docstring.all.to_s : '' final_scope = scope || code_object.scope + # @sg-ignore Need to add nil check here override_key = [closure.path, final_scope, name] final_visibility = VISIBILITY_OVERRIDE[override_key] + # @sg-ignore Need to add nil check here final_visibility ||= VISIBILITY_OVERRIDE[[closure.path, final_scope]] + # @sg-ignore Need to add nil check here 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 diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index eb0aa0bbd..1755afdc5 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -70,6 +70,7 @@ def load!(gemspec) # @return [Hash{String => String}] a hash of environment variables to override def current_bundle_env_tweaks tweaks = {} + # @sg-ignore flow sensitive typing needs a not-nil override pin if ENV['BUNDLE_GEMFILE'] && !ENV['BUNDLE_GEMFILE'].empty? tweaks['BUNDLE_GEMFILE'] = File.expand_path(ENV['BUNDLE_GEMFILE']) end diff --git a/solargraph.gemspec b/solargraph.gemspec index 22843e481..579b13db2 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -1,3 +1,4 @@ +# @sg-ignore Should better support meaning of '&' in RBS $LOAD_PATH.unshift File.dirname(__FILE__) + '/lib' require 'solargraph/version' require 'date' diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index b0505e091..a8f70aae1 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -5,6 +5,21 @@ def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :alpha) end + + it 'allows a compatible function call from two distinct types in a union' do + checker = type_checker(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz.nil? + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end + it 'does not falsely enforce nil in return types' do checker = type_checker(%( # @return [Integer] From a1034375ad28207814b02b183932a4c02bf565fe Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 17 Sep 2025 17:33:36 -0400 Subject: [PATCH 321/930] Add ivar spec --- spec/type_checker/levels/alpha_spec.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index a8f70aae1..611031d6d 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -48,5 +48,28 @@ def bar(b) expect(checker.problems.map(&:message)) .to eq(['Wrong argument type for #foo: a expected String, received String, nil']) end + + it 'tracks type of ivar' do + checker = type_checker(%( + class Foo + # @return [void] + def initialize + @sync_count = 0 + end + + # @return [void] + def synchronized? + @sync_count < 2 + end + + # @return [void] + def catalog + @sync_count += 1 + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end end end From fbe7d83fa89fb15d169061fd53a9086bbb159d27 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 17 Sep 2025 19:38:28 -0400 Subject: [PATCH 322/930] Correct labels on @sg-ignores --- lib/solargraph/convention/struct_definition.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index a2a1022b0..cea91c51e 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -17,7 +17,7 @@ def process type: :class, location: loc, closure: region.closure, - # @sg-ignore need to be able to resolve same method signature on two different types + # @sg-ignore flow sensitive typing needs a not-nil override pin name: struct_definition_node.class_name, docstring: docstring, visibility: :public, @@ -40,7 +40,7 @@ def process pins.push initialize_method_pin - # @sg-ignore need to be able to resolve same method signature on two different types + # @sg-ignore flow sensitive typing needs a not-nil override pin struct_definition_node.attributes.map do |attribute_node, attribute_name| initialize_method_pin.parameters.push( Pin::Parameter.new( From a226a547ea8ddc4bc2acbc65d3e814332d10e908 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 17 Sep 2025 20:29:13 -0400 Subject: [PATCH 323/930] Add better ||= support for getters --- lib/solargraph/api_map/store.rb | 1 - lib/solargraph/complex_type/unique_type.rb | 2 +- lib/solargraph/convention/data_definition.rb | 1 + .../convention/struct_definition.rb | 1 + lib/solargraph/library.rb | 1 - .../parser/parser_gem/node_chainer.rb | 12 ++++- lib/solargraph/pin/base.rb | 4 -- lib/solargraph/pin/base_variable.rb | 1 - lib/solargraph/pin/closure.rb | 3 +- lib/solargraph/pin/constant.rb | 1 - lib/solargraph/pin/conversions.rb | 2 - lib/solargraph/pin/instance_variable.rb | 1 + lib/solargraph/pin/method.rb | 1 - lib/solargraph/pin/signature.rb | 3 +- lib/solargraph/source.rb | 1 - lib/solargraph/source/cursor.rb | 1 + lib/solargraph/source/source_chainer.rb | 1 + lib/solargraph/source_map.rb | 1 - lib/solargraph/source_map/data.rb | 4 +- lib/solargraph/type_checker.rb | 2 +- lib/solargraph/type_checker/rules.rb | 17 ++++--- lib/solargraph/workspace.rb | 2 +- spec/type_checker/levels/strict_spec.rb | 50 +++++++++++++++++-- spec/type_checker/levels/strong_spec.rb | 1 - 24 files changed, 79 insertions(+), 35 deletions(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 34d9f9205..94b835483 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -295,7 +295,6 @@ def catalog pinsets true end - # @sg-ignore Need better ||= handling on ivars # @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/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index dc544593b..a6ea2393d 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -439,7 +439,7 @@ def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: new_key_types ||= @key_types new_subtypes ||= @subtypes make_rooted = @rooted if make_rooted.nil? - # @sg-ignore Need better ||= handling on locals + # @sg-ignore flow sensitive typing needs a not-nil override pin UniqueType.new(new_name, new_key_types, new_subtypes, rooted: make_rooted, parameters_type: parameters_type) end diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index 1db23451c..6672f95e6 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -81,6 +81,7 @@ def process private + # @sg-ignore Need to handle implicit nil on else # @return [DataDefinition::DataDefintionNode, DataDefinition::DataAssignmentNode, nil] def data_definition_node @data_definition_node ||= if DataDefintionNode.match?(node) diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index cea91c51e..ac31c67d5 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -105,6 +105,7 @@ def process private + # @sg-ignore Need to handle implicit nil on else # @return [StructDefinition::StructDefintionNode, StructDefinition::StructAssignmentNode, nil] def struct_definition_node @struct_definition_node ||= if StructDefintionNode.match?(node) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 064b53639..05268e9a4 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -511,7 +511,6 @@ def pins @pins ||= [] end - # @sg-ignore Need better ||= handling on ivars # @return [Set] def external_requires @external_requires ||= source_map_external_require_hash.values.flatten.to_set diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index 72e6b20cd..9bfdef8f2 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -100,8 +100,16 @@ 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 - new_node = n.updated(n.children[0].type, n.children[0].children + [n.children[1]]) - result.concat generate_links new_node + # @bar ||= 123 translates to: + # + # s(:or_asgn, + # s(:ivasgn, :@bar), + # s(:int, 123)) + lhs_chain = NodeChainer.chain n.children[0] # s(:ivasgn, :@bar) + rhs_chain = NodeChainer.chain n.children[1] # s(:int, 123) + or_link = Chain::Or.new([lhs_chain, rhs_chain]) + # this is just for a call chain, so we don't need to record the assignment + result.push(or_link) elsif [:class, :module, :def, :defs].include?(n.type) # @todo Undefined or what? result.push Chain::UNDEFINED_CALL diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 436346df7..feacca93f 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -379,7 +379,6 @@ def assert_source_provided Solargraph.assert_or_log(:source, "source not provided - #{@path} #{@source} #{self.class}") if source.nil? end - # @sg-ignore Need better ||= handling on ivars # @return [String] def comments @comments ||= '' @@ -489,7 +488,6 @@ def return_type @return_type ||= ComplexType::UNDEFINED end - # @sg-ignore Need better ||= handling on ivars # @return [YARD::Docstring] def docstring parse_comments unless @docstring @@ -522,7 +520,6 @@ def maybe_directives? @maybe_directives ||= comments.include?('@!') end - # @sg-ignore Need better ||= handling on ivars # @return [Boolean] def deprecated? @deprecated ||= docstring.has_tag?('deprecated') @@ -592,7 +589,6 @@ def proxy return_type result end - # @sg-ignore Need better ||= handling on ivars # @deprecated # @return [String] def identity diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 75cda2c6a..23d80275a 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 better ||= handling on ivars def return_type @return_type ||= generate_complex_type end diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index 2efd6ad53..8010de37b 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -57,8 +57,7 @@ def gates closure ? closure.gates : [''] end - # @sg-ignore Need better ||= handling on ivars - # @return [::Array] + # @return [::Array] def generics @generics ||= docstring.tags(:generic).map(&:name) end diff --git a/lib/solargraph/pin/constant.rb b/lib/solargraph/pin/constant.rb index 881072641..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 better ||= handling on ivars def return_type @return_type ||= generate_complex_type end diff --git a/lib/solargraph/pin/conversions.rb b/lib/solargraph/pin/conversions.rb index e46e959fe..43050fb94 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 better ||= handling on ivars # @return [Hash] def completion_item @completion_item ||= { @@ -50,7 +49,6 @@ def completion_item } end - # @sg-ignore Need better ||= handling on ivars # @return [Hash] def resolve_completion_item @resolve_completion_item ||= begin diff --git a/lib/solargraph/pin/instance_variable.rb b/lib/solargraph/pin/instance_variable.rb index 0f85a4e7a..b0471d120 100644 --- a/lib/solargraph/pin/instance_variable.rb +++ b/lib/solargraph/pin/instance_variable.rb @@ -17,6 +17,7 @@ def scope closure.binder.scope end + # @sg-ignore Need support for reduce_class_type in UniqueType # @return [ComplexType] def context @context ||= begin diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index e7e963383..e8c5764a5 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -221,7 +221,6 @@ def generate_signature(parameters, return_type) signature end - # @sg-ignore Need better ||= handling on ivars # @return [::Array] def signatures @signatures ||= begin diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 620ae206e..004f9bb87 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -9,12 +9,11 @@ def initialize **splat super(**splat) end - # @sg-ignore Need better ||= handling on ivars def generics + # @type [Array<::String, nil>] @generics ||= [].freeze end - # @sg-ignore Need better ||= handling on ivars def identity @identity ||= "signature#{object_id}" end diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 74b011b96..2a66c7b6b 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -484,7 +484,6 @@ def repaired private - # @sg-ignore Need better ||= handling on ivars # @return [Array] def code_lines @code_lines ||= code.lines diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 9cecee0e2..53742fa38 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -52,6 +52,7 @@ def start_of_word # `foo.bar`, the end_of_word at position (0,6) is `r`. # # @return [String] + # @sg-ignore Need to add nil check here def end_of_word @end_of_word ||= begin match = source.code[offset..-1].to_s.match(end_word_pattern) diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index c2b15021c..e1a7c4154 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -99,6 +99,7 @@ def fixed_position end # @return [String] + # @sg-ignore Need to add nil check here def end_of_phrase @end_of_phrase ||= begin match = phrase.match(/\s*(\.{1}|::)\s*$/) diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index d57e71be2..18936b2e0 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -86,7 +86,6 @@ def conventions_environ # all pins except Solargraph::Pin::Reference::Reference # - # @sg-ignore Need better ||= handling on ivars # @return [Array] def document_symbols @document_symbols ||= (pins + convention_pins).select do |pin| diff --git a/lib/solargraph/source_map/data.rb b/lib/solargraph/source_map/data.rb index 93ed147d0..ee9405e58 100644 --- a/lib/solargraph/source_map/data.rb +++ b/lib/solargraph/source_map/data.rb @@ -12,7 +12,7 @@ def initialize source @locals = nil end - # @sg-ignore Need better || handling on ivars + # @sg-ignore flow sensitive typing needs a not-nil override pin # @return [Array] def pins generate @@ -21,7 +21,7 @@ def pins @pins || empty_pins end - # @sg-ignore Need better || handling on ivars + # @sg-ignore flow sensitive typing needs a not-nil override pin # @return [Array] def locals generate diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 7711d4b0b..a910003b3 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -111,8 +111,8 @@ def load_string code, filename = nil, level = :normal, api_map: nil source = Solargraph::Source.load_string(code, filename) rules = Rules.new(level) api_map ||= Solargraph::ApiMap.new(loose_unions: !rules.require_all_unique_types_match_expected?) - # @sg-ignore Need better ||= handling on locals api_map.map(source) + # @sg-ignore flow sensitive typing needs a not-nil override pin new(filename, api_map: api_map, level: level, rules: rules) end end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index b86fe0f01..664dabdd1 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,17 +58,22 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo 33: flow sensitive typing needs a not-nil override pin - # @todo 32: Need to add nil check here - # @todo 18: Need better ||= handling on ivars + # @todo 128: flow sensitive typing needs a not-nil override pin + # @todo 65: Need to add nil check here + # @todo 12: need to be able to resolve same method signature on two different types # @todo 9: Need to validate config - # @todo 5: need boolish support for ? methods - # @todo 2: Need better || handling on ivars + # @todo 8: Should better support meaning of '&' in RBS + # @todo 7: literal arrays in this module turn into ::Solargraph::Source::Chain::Array + # @todo 4: need boolish support for ? methods + # @todo 4: should understand meaning of &. + # @todo 4: Need support for reduce_class_type in UniqueType + # @todo 3: Need to handle implicit nil on else + # @todo 2: Should handle redefinition of types in simple contexts + # @todo 2: Translate to something flow sensitive typing understands # @todo 1: flow sensitive typing needs to handle && with variables # @todo 1: To make JSON strongly typed we'll need a record syntax # @todo 1: Untyped method Solargraph::Pin::Base#assert_same could not be inferred # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' - # @todo 1: Need better ||= handling on locals def require_all_unique_types_match_expected? rank >= LEVELS[:strong] end diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 1cebb1efb..0b39ef89d 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -39,7 +39,6 @@ def require_paths @require_paths ||= RequirePaths.new(directory_or_nil, config).generate end - # @sg-ignore Need better ||= handling on ivars # @return [Solargraph::Workspace::Config] def config @config ||= Solargraph::Workspace::Config.new(directory) @@ -137,6 +136,7 @@ def rbs_collection_path @gem_rbs_collection ||= read_rbs_collection_path end + # @sg-ignore Need to handle implicit nil on else # @return [String, nil] def rbs_collection_config_path @rbs_collection_config_path ||= begin diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index e271d2c89..cb0dd9b49 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -611,6 +611,46 @@ def bar expect(checker.problems).to be_empty end + it 'Can infer through simple ||= on ivar' do + checker = type_checker(%( + class Foo + def recipient + @recipient ||= true + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'Can infer through simple ||= on lvar' do + checker = type_checker(%( + def recipient + recip ||= true + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'Can infer through simple ||= on cvar' do + checker = type_checker(%( + class Foo + def recipient + @@recipient ||= true + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'Can infer through simple ||= on civar' do + checker = type_checker(%( + class Foo + @recipient ||= true + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'Can infer through ||= with a begin+end' do checker = type_checker(%( def recipient @@ -1033,14 +1073,16 @@ class Foo def bar @bar ||= if rand - 123 - elsif rand - 456 - end + 123 + elsif rand + 456 + end end end )) + pending('Need to handle implicit nil on else') + expect(checker.problems.map(&:message)).to eq([]) end end diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 0bde28a66..dfb9dd300 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -77,7 +77,6 @@ def bar end end )) - pending('support for @@var ||= expr pattern') expect(checker.problems.map(&:message)).to eq([]) end From 632553b70416a7791532e238d3fb62dd12b28f12 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 17 Sep 2025 20:35:28 -0400 Subject: [PATCH 324/930] Add better ||= support for getters --- lib/solargraph/pin/closure.rb | 3 +++ lib/solargraph/pin/signature.rb | 1 + lib/solargraph/type_checker.rb | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index 8010de37b..d26a0d773 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -33,6 +33,7 @@ def combine_with(other, attrs={}) super(other, new_attrs) end + # @sg-ignore Need support for reduce_class_type in UniqueType def context @context ||= begin result = super @@ -57,8 +58,10 @@ def gates closure ? closure.gates : [''] end + # @sg-ignore flow sensitive typing needs a not-nil override pin # @return [::Array] def generics + # @sg-ignore flow sensitive typing needs a not-nil override pin @generics ||= docstring.tags(:generic).map(&:name) end diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 004f9bb87..f35601ca8 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -9,6 +9,7 @@ def initialize **splat super(**splat) end + # @sg-ignore flow sensitive typing needs a not-nil override pin def generics # @type [Array<::String, nil>] @generics ||= [].freeze diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index a910003b3..6314e86e5 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -111,8 +111,8 @@ def load_string code, filename = nil, level = :normal, api_map: nil source = Solargraph::Source.load_string(code, filename) rules = Rules.new(level) api_map ||= Solargraph::ApiMap.new(loose_unions: !rules.require_all_unique_types_match_expected?) - api_map.map(source) # @sg-ignore flow sensitive typing needs a not-nil override pin + api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end end From 04dc627107746d1e419d652f17a2d94905ead9a1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 17 Sep 2025 20:46:15 -0400 Subject: [PATCH 325/930] Add better ||= support for getters --- lib/solargraph/pin/closure.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index d26a0d773..6d42186cd 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -61,7 +61,6 @@ def gates # @sg-ignore flow sensitive typing needs a not-nil override pin # @return [::Array] def generics - # @sg-ignore flow sensitive typing needs a not-nil override pin @generics ||= docstring.tags(:generic).map(&:name) end From 27bde68aaf7e3cc0380d04b4a446f9fc387cdb92 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 18 Sep 2025 05:43:49 -0400 Subject: [PATCH 326/930] 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 327/930] 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 328/930] 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 329/930] 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 330/930] 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 331/930] 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 332/930] 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 1338f27e430194df74e2f9dbffa2be8b7b947f53 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 18 Sep 2025 09:12:47 -0400 Subject: [PATCH 333/930] Teach flow sensitive typing how to defer removal of a type like nil --- lib/solargraph/api_map.rb | 1 - lib/solargraph/api_map/constants.rb | 1 - lib/solargraph/api_map/index.rb | 1 - lib/solargraph/library.rb | 3 - .../parser/flow_sensitive_typing.rb | 30 ++++------ .../parser/parser_gem/node_chainer.rb | 1 - lib/solargraph/pin/base.rb | 1 + lib/solargraph/pin/common.rb | 1 + lib/solargraph/pin/local_variable.rb | 59 ++++++++++++++++++- lib/solargraph/pin/method.rb | 3 - lib/solargraph/rbs_map/conversions.rb | 1 - lib/solargraph/source.rb | 1 - lib/solargraph/source/chain/call.rb | 2 - lib/solargraph/source/cursor.rb | 3 - lib/solargraph/source/source_chainer.rb | 1 - lib/solargraph/source_map/clip.rb | 1 - lib/solargraph/type_checker.rb | 5 +- lib/solargraph/type_checker/rules.rb | 5 +- spec/type_checker/levels/strong_spec.rb | 1 - 19 files changed, 78 insertions(+), 43 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index f5ba0eb37..f9c93996c 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -898,7 +898,6 @@ def resolve_method_alias(alias_pin) break if original end - # @sg-ignore ignore `received nil` for original create_resolved_alias_pin(alias_pin, original) if original end diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index dc50bf34b..6d03fc3a9 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -213,7 +213,6 @@ def inner_get_constants fqns, visibility, skip end sc_ref = store.get_superclass(fqns) if sc_ref - # @sg-ignore flow sensitive typing needs a not-nil override pin fqsc = dereference(sc_ref) result.concat inner_get_constants(fqsc, [:public], skip) unless %w[Object BasicObject].include?(fqsc) end diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 0d4431e70..d4d0baefc 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -147,7 +147,6 @@ def map_overrides if new_pin # @sg-ignore flow sensitive typing needs a not-nil override pin new_pin.docstring.add_tag(tag) - # @sg-ignore flow sensitive typing needs a not-nil override pin redefine_return_type new_pin, tag end end diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 05268e9a4..aaa746a98 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -486,11 +486,8 @@ def next_map return false if mapped? src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) } if src - # @sg-ignore flow sensitive typing needs a not-nil override pin Logging.logger.debug "Mapping #{src.filename}" - # @sg-ignore flow sensitive typing needs a not-nil override pin source_map_hash[src.filename] = Solargraph::SourceMap.map(src) - # @sg-ignore flow sensitive typing needs a not-nil override pin source_map_hash[src.filename] else false diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 42ebd4074..f911089df 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -128,31 +128,22 @@ def self.visible_pins(pins, name, closure, location) private - # @param return_type [ComplexType, nil] - # - # @return [ComplexType, nil] - def remove_nil(return_type) - # @todo flow sensitive typing needs a not-nil override pin - return return_type if return_type.nil? || return_type.undefined? - - types = return_type.items.reject { |t| t.name == 'nil' } - ComplexType.new(types) - end - # @param pin [Pin::LocalVariable] # @param downcast_type_name [String, :not_nil] # @param presence [Range] # # @return [void] def add_downcast_local(pin, downcast_type_name, presence) - type = if downcast_type_name == :not_nil - remove_nil(pin.return_type) - else - ComplexType.try_parse(downcast_type_name) - end - return if type == pin.return_type + return_type = if downcast_type_name == :not_nil + pin.return_type + else + ComplexType.parse(downcast_type_name) + end + exclude_return_type = downcast_type_name == :not_nil ? ComplexType::NIL : nil + # @todo Create pin#update method new_pin = Solargraph::Pin::LocalVariable.new( + presence_certain: true, location: pin.location, closure: pin.closure, name: pin.name, @@ -160,10 +151,11 @@ def add_downcast_local(pin, downcast_type_name, presence) # that it implies comments: pin.comments, presence: presence, - return_type: type, - presence_certain: true, + return_type: return_type, + exclude_return_type: exclude_return_type, source: :flow_sensitive_typing ) + new_pin.reset_generated! locals.push(new_pin) end diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index 9bfdef8f2..55dedcb10 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -141,7 +141,6 @@ def generate_links n result.push Source::Chain::Array.new(chained_children, n) else lit = infer_literal_node_type(n) - # @sg-ignore flow sensitive typing needs a not-nil override pin result.push (lit ? Chain::Literal.new(lit, n) : Chain::Link.new) end result diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index feacca93f..d0ab9eb7f 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -586,6 +586,7 @@ def proxy return_type result = dup result.return_type = return_type result.proxied = true + result.reset_generated! result end diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 8fd9cb739..4f19c7339 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -24,6 +24,7 @@ def name @name ||= '' end + # @todo redundant with Base#return_type? # @return [ComplexType] def return_type @return_type ||= ComplexType::UNDEFINED diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 129e68aee..dc8566964 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -6,6 +6,9 @@ class LocalVariable < BaseVariable # @return [Range] attr_reader :presence + # @return [Boolean] + attr_reader :presence_certain + def presence_certain? @presence_certain end @@ -13,24 +16,76 @@ def presence_certain? # @param assignment [AST::Node, nil] # @param presence [Range, nil] # @param presence_certain [Boolean] + # @param exclude_return_type [ComplexType, nil] Ensure any return + # type returned will never include these unique types in the + # unique types of its complex type # @param splat [Hash] - def initialize assignment: nil, presence: nil, presence_certain: false, **splat + def initialize assignment: nil, presence: nil, presence_certain: false, + exclude_return_type: nil, **splat super(**splat) @assignment = assignment @presence = presence @presence_certain = presence_certain + @exclude_return_type = exclude_return_type end def combine_with(other, attrs={}) new_attrs = { assignment: assert_same(other, :assignment), presence_certain: assert_same(other, :presence_certain?), + exclude_return_type: combine_types(other, :exclude_return_type), }.merge(attrs) new_attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence) + new_attrs[:presence_certain] = assert_same(other, :presence_certain) unless attrs.key?(:presence_certain) super(other, new_attrs) end + # @param other [self] + # @param attr [::Symbol] + # + # @return [ComplexType, nil] + def combine_types(other, attr) + # @type [ComplexType, nil] + type1 = send(attr) + # @type [ComplexType, nil] + type2 = other.send(attr) + if type1 && type2 + types = (type1.items + type2.items).uniq + ComplexType.new(types) + else + type1 || type2 + end + end + + def reset_generated! + @return_type_minus_exclusions = nil + super + end + + # @return [ComplexType, nil] + def return_type + @return_type = return_type_minus_exclusions(super) + end + + # @param raw_return_type [ComplexType, nil] + # @return [ComplexType, nil] + def return_type_minus_exclusions(raw_return_type) + @return_type_minus_exclusions ||= + if exclude_return_type && raw_return_type + # TODO: Should either complain at strong level on .items + # dereferences or not show ', nil' in hover docs' + types = raw_return_type.items - exclude_return_type.items + # @sg-ignore TODO: Get this to not error out - not clear + # why it would given hoverover docs + types = [ComplexType::UniqueType::UNDEFINED] if types.empty? + ComplexType.new(types) + else + raw_return_type + end + @return_type_minus_exclusions + end + # @param other_closure [Pin::Closure] # @param other_loc [Location] def visible_at?(other_closure, other_loc) @@ -47,6 +102,8 @@ def to_rbs private + attr_reader :exclude_return_type + # @param tag1 [String] # @param tag2 [String] # @return [Boolean] diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index e8c5764a5..50a4715f3 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -193,9 +193,7 @@ def generate_signature(parameters, return_type) name = p.name decl = :arg if name - # @sg-ignore flow sensitive typing needs a not-nil override pin decl = select_decl(name, false) - # @sg-ignore flow sensitive typing needs a not-nil override pin name = clean_param(name) end Pin::Parameter.new( @@ -216,7 +214,6 @@ def generate_signature(parameters, return_type) end signature = Signature.new(generics: generics, parameters: parameters, return_type: return_type, block: block, closure: self, source: source, location: location, type_location: type_location) - # @sg-ignore Need to add nil check here block.closure = signature if block signature end diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index f4104fccb..29dfe7be7 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -447,7 +447,6 @@ def method_def_to_sigs decl, pin signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin) rbs_block = overload.method_type.block block = if rbs_block - # @sg-ignore flow sensitive typing needs a not-nil override pin block_parameters, block_return_type = parts_of_function(rbs_block, pin) Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, type_location: type_location, closure: pin) diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 2a66c7b6b..170a865ac 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -209,7 +209,6 @@ def comments_for node stringified_comments[rng.start.line] ||= begin # @sg-ignore Need to add nil check here buff = associated_comments[rng.start.line] - # @sg-ignore flow sensitive typing needs a not-nil override pin buff ? stringify_comment_array(buff) : nil end end diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 563020a53..4a948db5e 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -236,7 +236,6 @@ def inner_process_macro pin, macro, api_map, context, locals # @sg-ignore Need to add nil check here if txt.empty? && macro.tag.name named = api_map.named_macro(macro.tag.name) - # @sg-ignore Need to add nil check here txt = named.tag.text.clone if named end i = 1 @@ -358,7 +357,6 @@ def block_call_type(api_map, name_pin, locals) block_context_pin = name_pin block_pin = find_block_pin(api_map) - # @sg-ignore flow sensitive typing needs a not-nil override pin block_context_pin = block_pin.closure if block_pin # @sg-ignore Need to add nil check here block.infer(api_map, block_context_pin, locals) diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 53742fa38..8e7e1ed8b 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -39,7 +39,6 @@ def word def start_of_word @start_of_word ||= begin match = source.code[0..offset-1].to_s.match(start_word_pattern) - # @sg-ignore flow sensitive typing needs a not-nil override pin result = (match ? match[0] : '') # Including the preceding colon if the word appears to be a symbol # @sg-ignore Need to add nil check here @@ -56,7 +55,6 @@ def start_of_word def end_of_word @end_of_word ||= begin match = source.code[offset..-1].to_s.match(end_word_pattern) - # @sg-ignore flow sensitive typing needs a not-nil override pin match ? match[0] : '' end end @@ -132,7 +130,6 @@ def node_position # @sg-ignore Need to add nil check here match = source.code[0, offset].match(/\s*(\.|:+)\s*$/) if match - # @sg-ignore Need to add nil check here Position.from_offset(source.code, offset - match[0].length) else position diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index e1a7c4154..6be8b8fdb 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -104,7 +104,6 @@ def end_of_phrase @end_of_phrase ||= begin match = phrase.match(/\s*(\.{1}|::)\s*$/) if match - # @sg-ignore flow sensitive typing needs a not-nil override pin match[0] else '' diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index 149e0ed3e..fbbc2c6fb 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -164,7 +164,6 @@ def tag_complete # @sg-ignore Need to add nil check here match = source_map.code[0..cursor.offset-1].match(/[\[<, ]([a-z0-9_:]*)\z/i) if match - # @sg-ignore flow sensitive typing needs a not-nil override pin full = match[1] # @sg-ignore Need to add nil check here if full.include?('::') diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 6314e86e5..1cadb990f 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -320,7 +320,6 @@ def call_problems missing = base base = base.base end - # @sg-ignore flow sensitive typing needs a not-nil override pin closest = found.typify(api_map) if found # @todo remove the internal_or_core? check at a higher-than-strict level # @sg-ignore flow sensitive typing needs a not-nil override pin @@ -443,6 +442,7 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum if argchain.node.type == :splat && argchain == arguments.last final_arg = argchain end + # @sg-ignore flow sensitive typing needs to handle "&&" 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 @@ -466,7 +466,9 @@ 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) + # @sg-ignore flow sensitive typing needs to handle "else" argtype = argtype.self_to_type(closure_pin.context) + # @sg-ignore flow sensitive typing needs to handle "else" 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 @@ -642,7 +644,6 @@ def declared_externally? pin missing = base base = base.base end - # @sg-ignore flow sensitive typing needs a not-nil override pin closest = found.typify(api_map) if found # @sg-ignore flow sensitive typing needs to handle "if !foo" if !found || closest.defined? || internal?(found) diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 664dabdd1..2aa058881 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,7 +58,10 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo 128: flow sensitive typing needs a not-nil override pin + # flow sensitive typing needs to handle "else" + # flow sensitive typing needs to handle "&&" + # flow sensitive typing needs to handle "if !foo" + # flow sensitive typing needs a not-nil override pin # @todo 65: Need to add nil check here # @todo 12: need to be able to resolve same method signature on two different types # @todo 9: Need to validate config diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index dfb9dd300..8d0307d79 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -47,7 +47,6 @@ def foo bar end end )) - pending 'not-nil override pin available and used by flow sensitive typing' expect(checker.problems.map(&:message)).to be_empty end From ed3809f4695a3555892a84a48f36d11328de9003 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 18 Sep 2025 09:43:27 -0400 Subject: [PATCH 334/930] 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 335/930] 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 2d9e0eec9928b7c19935a0ac9d142faecfe69585 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 18 Sep 2025 11:33:20 -0400 Subject: [PATCH 336/930] Update @sg-ignore messages from lastest code --- lib/solargraph.rb | 2 +- lib/solargraph/api_map.rb | 14 +++---- lib/solargraph/api_map/constants.rb | 2 +- lib/solargraph/api_map/index.rb | 15 +++++++- lib/solargraph/api_map/source_to_yard.rb | 16 ++++---- lib/solargraph/api_map/store.rb | 14 +++---- lib/solargraph/complex_type/unique_type.rb | 2 +- .../convention/active_support_concern.rb | 4 +- lib/solargraph/convention/data_definition.rb | 4 +- .../convention/struct_definition.rb | 6 +-- lib/solargraph/doc_map.rb | 8 ++-- .../message/text_document/definition.rb | 4 +- lib/solargraph/library.rb | 38 +++++++++---------- .../parser/flow_sensitive_typing.rb | 4 +- lib/solargraph/parser/node_processor/base.rb | 2 +- .../parser/parser_gem/node_methods.rb | 2 +- lib/solargraph/pin/base.rb | 8 ++-- lib/solargraph/pin/base_variable.rb | 2 +- lib/solargraph/pin/callable.rb | 2 +- lib/solargraph/pin/closure.rb | 4 +- lib/solargraph/pin/method.rb | 12 +++--- lib/solargraph/pin/parameter.rb | 14 +++---- lib/solargraph/pin/signature.rb | 2 +- lib/solargraph/position.rb | 2 +- lib/solargraph/rbs_map.rb | 2 +- lib/solargraph/rbs_map/stdlib_map.rb | 2 +- lib/solargraph/source.rb | 6 +-- lib/solargraph/source/chain/call.rb | 8 ++-- lib/solargraph/source/change.rb | 6 +-- lib/solargraph/source/cursor.rb | 2 +- lib/solargraph/source/source_chainer.rb | 6 +-- lib/solargraph/source_map/data.rb | 4 +- lib/solargraph/source_map/mapper.rb | 6 +-- lib/solargraph/type_checker.rb | 10 ++--- lib/solargraph/type_checker/rules.rb | 20 +++++++--- lib/solargraph/yardoc.rb | 2 +- 36 files changed, 138 insertions(+), 119 deletions(-) diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 67d2e10e4..6924b7619 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -57,7 +57,7 @@ class InvalidRubocopVersionError < RuntimeError; end # @param type [Symbol] Type of assert. def self.asserts_on?(type) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables if ENV['SOLARGRAPH_ASSERTS'].nil? || ENV['SOLARGRAPH_ASSERTS'].empty? false elsif ENV['SOLARGRAPH_ASSERTS'] == 'on' diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index f9c93996c..57f9e82e7 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -343,7 +343,7 @@ def get_instance_variable_pins(namespace, scope = :instance) result.concat store.get_instance_variables(namespace, scope) sc_fqns = namespace while (sc = store.get_superclass(sc_fqns)) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "while foo"" sc_fqns = store.constants.dereference(sc) result.concat store.get_instance_variables(sc_fqns, scope) end @@ -652,7 +652,7 @@ def super_and_sub?(sup, sub) return true if sup == sub sc_fqns = sub while (sc = store.get_superclass(sc_fqns)) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "while foo"" sc_new = store.constants.dereference(sc) # Cyclical inheritance is invalid return false if sc_new == sc_fqns @@ -767,7 +767,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if deep && scope == :instance store.get_prepends(fqns).reverse.each do |im| fqim = store.constants.dereference(im) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil? end end @@ -796,19 +796,19 @@ 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? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" 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}" } store.get_extends(fqns).reverse.each do |em| fqem = store.constants.dereference(em) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil? end rooted_sc_tag = qualify_superclass(rooted_tag) unless rooted_sc_tag.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" 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? @@ -845,7 +845,7 @@ def get_namespace_type fqns # @type [Pin::Namespace, nil] pin = store.get_path_pins(fqns).select{|p| p.is_a?(Pin::Namespace)}.first return nil if pin.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" pin.type end diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 6d03fc3a9..29e49e5e4 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -69,7 +69,7 @@ def qualify tag, context_tag = '' fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace) return unless fqns - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" fqns + type.substring end diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index d4d0baefc..d19a09f16 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -128,6 +128,8 @@ def map_references klass, hash # @return [void] def map_overrides + # @todo should complain when type for 'ovr' is not provided + # @param ovr [Pin::Reference::Override] pins_by_class(Pin::Reference::Override).each do |ovr| logger.debug { "ApiMap::Index#map_overrides: Looking at override #{ovr} for #{ovr.name}" } pins = path_pin_hash[ovr.name] @@ -137,16 +139,25 @@ def map_overrides path_pin_hash[pin.path.sub(/#initialize/, '.new')].first end (ovr.tags.map(&:tag_name) + ovr.delete).uniq.each do |tag| + # @sg-ignore Wrong argument type for + # YARD::Docstring#delete_tags: name expected String, + # received String, Symbol - delete_tags is ok with a + # _ToS, but we should fix anyway pin.docstring.delete_tags tag - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore Wrong argument type for + # YARD::Docstring#delete_tags: name expected String, + # received String, Symbol - delete_tags is ok with a + # _ToS, but we should fix anyway new_pin.docstring.delete_tags tag if new_pin end ovr.tags.each do |tag| pin.docstring.add_tag(tag) redefine_return_type pin, tag if new_pin - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle inner closures new_pin.docstring.add_tag(tag) + # TODO: Should complain that new_pin is a Pin::Base + # being passed into Pin::Method redefine_return_type new_pin, tag end end diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index d0f1224a0..a494a8948 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -36,16 +36,16 @@ def rake_yard store end if pin.type == :class code_object_map[pin.path] ||= YARD::CodeObjects::ClassObject.new(root_code_object, pin.path) { |obj| - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) } else code_object_map[pin.path] ||= YARD::CodeObjects::ModuleObject.new(root_code_object, pin.path) { |obj| - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs better handling of ||= on lvars obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) } end @@ -53,7 +53,7 @@ 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? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" include_object.instance_mixins.push code_object_map[ref.parametrized_tag.to_s] end end @@ -62,7 +62,7 @@ def rake_yard store next unless extend_object code_object = code_object_map[ref.parametrized_tag.to_s] next unless code_object - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" extend_object.class_mixins.push code_object # @todo add spec showing why this next line is necessary # @sg-ignore need to be able to resolve same method signature on two different types @@ -77,9 +77,9 @@ def rake_yard store # @sg-ignore Need to add nil check here code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope) { |obj| - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables obj.add_file pin.location.filename, pin.location.range.start.line } method_object = code_object_at(pin.path, YARD::CodeObjects::MethodObject) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 94b835483..b929d6167 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -97,10 +97,10 @@ def qualify_superclass fq_sub_tag return type.simplify_literals.to_s if type.literal? ref = get_superclass(fq_sub_tag) return unless ref - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" res = constants.dereference(ref) return unless res - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" res + type.substring end @@ -212,9 +212,9 @@ def fqns_pins fqns return [] if fqns.nil? if fqns.include?('::') parts = fqns.split('::') - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle 'return if' name = parts.pop - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle 'return if' base = parts.join('::') else base = '' @@ -244,7 +244,7 @@ def get_ancestors(fqns) ref = get_superclass(current) # @sg-ignore flow sensitive typing needs to handle && with variables superclass = ref && constants.dereference(ref) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables if superclass && !superclass.empty? && !visited.include?(superclass) ancestors << superclass queue << superclass @@ -375,10 +375,10 @@ def uncached_qualify_superclass fq_sub_tag return type.simplify_literals.to_s if type.literal? ref = get_superclass(fq_sub_tag) return unless ref - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" res = constants.dereference(ref) return unless res - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" res + type.substring end end diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index a6ea2393d..a2a7d01e0 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -439,7 +439,7 @@ def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: new_key_types ||= @key_types new_subtypes ||= @subtypes make_rooted = @rooted if make_rooted.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs better handling of ||= on lvars UniqueType.new(new_name, new_key_types, new_subtypes, rooted: make_rooted, parameters_type: parameters_type) end diff --git a/lib/solargraph/convention/active_support_concern.rb b/lib/solargraph/convention/active_support_concern.rb index fb27286cd..b5104b17b 100644 --- a/lib/solargraph/convention/active_support_concern.rb +++ b/lib/solargraph/convention/active_support_concern.rb @@ -80,14 +80,14 @@ def process_include include_tag "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ "Handling class include include_tag=#{include_tag}" end - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" module_extends = api_map.get_extends(rooted_include_tag).map(&:parametrized_tag).map(&:to_s) logger.debug do "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ "found module extends of #{rooted_include_tag}: #{module_extends}" end return unless module_extends.include? 'ActiveSupport::Concern' - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" included_class_pins = api_map.inner_get_methods_from_reference(rooted_include_tag, namespace_pin, rooted_type, :class, visibility, deep, skip, true) logger.debug do diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index 6672f95e6..c851a951e 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -96,10 +96,10 @@ def data_definition_node # @return [String, nil] def attribute_comments(attribute_node, attribute_name) data_comments = comments_for(attribute_node) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables return if data_comments.nil? || data_comments.empty? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" data_comments.split("\n").find do |row| row.include?(attribute_name) end&.gsub('@param', '@return')&.gsub(attribute_name, '') diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index ac31c67d5..dc1b777bc 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -17,7 +17,7 @@ def process type: :class, location: loc, closure: region.closure, - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle 'return if' name: struct_definition_node.class_name, docstring: docstring, visibility: :public, @@ -40,7 +40,7 @@ def process pins.push initialize_method_pin - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" struct_definition_node.attributes.map do |attribute_node, attribute_name| initialize_method_pin.parameters.push( Pin::Parameter.new( @@ -142,7 +142,7 @@ def parse_comments # @param tag [YARD::Tags::Tag, nil] The param tag for this attribute.xtract_ # - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables # @return [String] def tag_string(tag) tag&.types&.join(',') || 'undefined' diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index ab396404c..0409e4db3 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -218,7 +218,7 @@ def deserialize_yard_pin_cache gemspec cached = PinCache.deserialize_yard_gem(gemspec) if cached - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle inner closures logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" } yard_pins_in_memory[[gemspec.name, gemspec.version]] = cached cached @@ -241,7 +241,7 @@ def deserialize_combined_pin_cache(gemspec) cached = PinCache.deserialize_combined_gem(gemspec, rbs_version_cache_key) if cached - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle inner closures 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]] @@ -298,7 +298,7 @@ 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 - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle inner closures 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 @@ -333,7 +333,7 @@ def resolve_path_to_gemspecs path end end return nil if gemspec.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" [gemspec_or_preference(gemspec)] end diff --git a/lib/solargraph/language_server/message/text_document/definition.rb b/lib/solargraph/language_server/message/text_document/definition.rb index 4af1131d9..3e7e0b690 100644 --- a/lib/solargraph/language_server/message/text_document/definition.rb +++ b/lib/solargraph/language_server/message/text_document/definition.rb @@ -33,9 +33,9 @@ def require_location return nil if dloc.nil? [ { - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" uri: file_to_uri(dloc.filename), - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" range: dloc.range.to_hash } ] diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index aaa746a98..7098216a4 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -57,11 +57,11 @@ def synchronized? # @param source [Source, nil] # @return [void] def attach source - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables source_map_hash.delete @current.filename - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables source_map_external_require_hash.delete @current.filename @external_requires = nil end @@ -75,9 +75,9 @@ def attach source # # @param filename [String] # @return [Boolean] - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables def attached? filename - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables !@current.nil? && @current.filename == filename end alias open? attached? @@ -87,7 +87,7 @@ def attached? filename # @param filename [String] # @return [Boolean] True if the specified file was detached def detach filename - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables return false if @current.nil? || @current.filename != filename attach nil true @@ -194,7 +194,7 @@ def definitions_at filename, line, column # @sg-ignore Need to add nil check here rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i) if lft && rgt - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables tag = (lft[1] + rgt[1]).sub(/:+$/, '') clip = mutex.synchronize { api_map.clip(cursor) } clip.translate tag @@ -285,7 +285,7 @@ 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| - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables 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 @@ -312,7 +312,7 @@ def locate_pins location def locate_ref location map = source_map_hash[location.filename] return if map.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" pin = map.requires.select { |p| p.location.range.contain?(location.range.start) }.first return nil if pin.nil? # @param full [String] @@ -447,7 +447,7 @@ def bench source_maps: source_map_hash.values, workspace: workspace, external_requires: external_requires, - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle ternary operator live_map: @current ? source_map_hash[@current.filename] : nil ) end @@ -557,7 +557,7 @@ def api_map # @sg-ignore flow sensitive typing needs to handle if foo && ... # @return [Solargraph::Source] def read filename - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables return @current if @current && @current.filename == filename raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename) workspace.source(filename) @@ -581,11 +581,11 @@ def maybe_map source return unless source return unless @current == source || workspace.has_file?(source.filename) if source_map_hash.key?(source.filename) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables new_map = Solargraph::SourceMap.map(source) source_map_hash[source.filename] = new_map else - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "else" source_map_hash[source.filename] = Solargraph::SourceMap.map(source) end end @@ -652,29 +652,29 @@ def queued_gemspec_cache def report_cache_progress gem_name, pending @total ||= pending @total = pending if pending > @total - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs better handling of ||= on ivars finished = @total - pending - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs better handling of ||= on ivars pct = if @total.zero? 0 else - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs better handling of ||= on ivars ((finished.to_f / @total.to_f) * 100).to_i end message = "#{gem_name}#{pending > 0 ? " (+#{pending})" : ''}" # " if @cache_progress - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore oflow sensitive typing needs to handle if on ivars @cache_progress.report(message, pct) else @cache_progress = LanguageServer::Progress.new('Caching gem') # If we don't send both a begin and a report, the progress notification # might get stuck in the status bar forever - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore Should handle redefinition of types in simple contexts @cache_progress.begin(message, pct) changed notify_observers @cache_progress - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore Should handle redefinition of types in simple contexts @cache_progress.report(message, pct) end changed diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index f911089df..563a74bab 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -173,7 +173,7 @@ def process_facts(facts_by_pin, presences) nilp = fact.fetch(:nil, nil) not_nilp = fact.fetch(:not_nil, nil) presences.each do |presence| - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" add_downcast_local(pin, downcast_type_name, presence) unless downcast_type_name.nil? add_downcast_local(pin, 'nil', presence) if nilp == true add_downcast_local(pin, :not_nil, presence) if not_nilp == true @@ -310,7 +310,7 @@ def process_variable(node, true_presences) # @sg-ignore Need to add nil check here var_position = Range.from_node(node).start - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle 'return if' pin = find_local(variable_name, var_position) return unless pin diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index f18d623c0..93b302c48 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -68,7 +68,7 @@ def comments_for(node) # @return [Pin::Closure, nil] def named_path_pin position pins.select do |pin| - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables pin.is_a?(Pin::Closure) && pin.path && !pin.path.empty? && pin.location.range.contain?(position) end.last end diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index ddc76f988..771ed9cdf 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -231,7 +231,7 @@ def find_recipient_node cursor args = node.children[2..-1] # @sg-ignore Need to add nil check here if !args.empty? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables return node if prev && args.include?(prev) else if source.synchronized? diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index d0ab9eb7f..3d302bc8d 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -81,7 +81,7 @@ def closure # @param other [self] # @param attrs [Hash{::Symbol => Object}] # - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" # @return [self] def combine_with(other, attrs={}) raise "tried to combine #{other.class} with #{self.class}" unless other.class == self.class @@ -427,7 +427,7 @@ def erase_generics(generics_to_erase) # @return [String, nil] def filename return nil if location.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" location.filename end @@ -464,7 +464,7 @@ def best_location def nearly? other self.class == other.class && name == other.name && - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables (closure == other.closure || (closure && closure.nearly?(other.closure))) && (comments == other.comments || (((maybe_directives? == false && other.maybe_directives? == false) || compare_directives(directives, other.directives)) && @@ -515,7 +515,7 @@ def macros # # @return [Boolean] def maybe_directives? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables return !@directives.empty? if defined?(@directives) && @directives @maybe_directives ||= comments.include?('@!') end diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 23d80275a..5333d86ac 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -65,7 +65,7 @@ def return_types_from_node(parent_node, api_map) else rng = Range.from_node(node) next if rng.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" pos = rng.ending # @sg-ignore Need to add nil check here clip = api_map.clip_at(location.filename, pos) diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 91885baaf..540a940f8 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -115,7 +115,7 @@ def resolve_generics_from_context(generics_to_resolve, param.dup else param.resolve_generics_from_context(generics_to_resolve, - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "else" arg_types[i], resolved_generic_values: resolved_generic_values) end diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index 6d42186cd..4d030fde0 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -54,11 +54,11 @@ def binder def gates # @todo This check might not be necessary. There should always be a # root pin - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle ternary operator closure ? closure.gates : [''] end - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs better handling of ||= on ivars # @return [::Array] def generics @generics ||= docstring.tags(:generic).map(&:name) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 50a4715f3..f84ae1a7a 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -78,7 +78,7 @@ def combine_signatures(other) end end - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" def combine_with(other, attrs = {}) priority_choice = choose_priority(other) return priority_choice unless priority_choice.nil? @@ -202,7 +202,7 @@ def generate_signature(parameters, return_type) comments: p.text, name: name, decl: decl, - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle ternary operator presence: location ? location.range : nil, return_type: ComplexType.try_parse(*p.types), source: source @@ -308,9 +308,9 @@ def typify api_map # @sg-ignore Need to add nil check here logger.debug { "Method#typify(self=#{self}) - type=#{type&.rooted_tags.inspect}" } unless type.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" qualified = type.qualify(api_map, namespace) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" logger.debug { "Method#typify(self=#{self}) => #{qualified.rooted_tags.inspect}" } return qualified end @@ -415,7 +415,7 @@ def overloads comments: tag.docstring.all.to_s, name: name, decl: decl, - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle ternary operator presence: location ? location.range : nil, return_type: param_type_from_name(tag, src.first), source: :overloads @@ -451,7 +451,7 @@ def resolve_ref_tag api_map end next unless ref - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" docstring.add_tag(*ref.docstring.tags(:param)) end self diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index d26eac282..0b3cef911 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -45,7 +45,7 @@ def keyword? end def kwrestarg? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables decl == :kwrestarg || (assignment && [:HASH, :hash].include?(assignment.type)) end @@ -152,9 +152,9 @@ def return_type if @return_type.nil? @return_type = ComplexType::UNDEFINED found = param_tag - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables @return_type = ComplexType.try_parse(*found.types) unless found.nil? or found.types.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore Need to add nil check here if @return_type.undefined? if decl == :restarg @return_type = ComplexType.try_parse('::Array') @@ -203,9 +203,9 @@ def compatible_arg?(atype, api_map) def documentation tag = param_tag - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables return '' if tag.nil? || tag.text.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" tag.text end @@ -250,7 +250,7 @@ def typify_method_param api_map if found.nil? and !index.nil? found = params[index] if params[index] && (params[index].name.nil? || params[index].name.empty?) end - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables return ComplexType.try_parse(*found.types).qualify(api_map, meth.context.namespace) unless found.nil? || found.types.nil? end ComplexType::UNDEFINED @@ -261,7 +261,7 @@ def typify_method_param api_map # @param skip [::Array] # # @return [::Array] - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" def see_reference heredoc, api_map, skip = [] heredoc.ref_tags.each do |ref| next unless ref.tag_name == 'param' && ref.owner diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index f35601ca8..05aa6a66b 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -9,7 +9,7 @@ def initialize **splat super(**splat) end - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs better handling of ||= on ivars def generics # @type [Array<::String, nil>] @generics ||= [].freeze diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 0b4985de1..45d404ba4 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -92,7 +92,7 @@ def self.from_offset text, offset end character = 0 if character.nil? and (cursor - offset).between?(0, 1) raise InvalidOffsetError if character.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle 'raise if' Position.new(line, character) end diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index 8c47ad21a..9c4ff8a84 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -58,7 +58,7 @@ def cache_key data = gem_config&.to_s end end - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables if data.nil? || data.empty? if resolved? # definitely came from the gem itself and not elsewhere - diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index 43b8f745f..b24c4223a 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -19,7 +19,7 @@ def initialize library @pins = cached_pins @resolved = true @loaded = true - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle inner closures logger.debug { "Deserialized #{cached_pins.length} cached pins for stdlib require #{library.inspect}" } else super diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 170a865ac..6e652e0a8 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -258,11 +258,11 @@ def associated_comments # @type [Integer, nil] last = nil comments.each_pair do |num, snip| - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables if !last || num == last + 1 buffer.concat "#{snip.text}\n" else - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables result[first_not_empty_from(last + 1)] = buffer.clone buffer.replace "#{snip.text}\n" end @@ -320,7 +320,7 @@ def stringify_comment_array comments ctxt.concat p else here = p.index(/[^ \t]/) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables skip = here if skip.nil? || here < skip ctxt.concat p[skip..-1] end diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 4a948db5e..316a17cb7 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -160,7 +160,7 @@ def inferred_pins pins, api_map, name_pin, locals end break if type.defined? end - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" p = p.with_single_signature(new_signature_pin) unless new_signature_pin.nil? next p.proxy(type) if type.defined? if !p.macros.empty? @@ -217,7 +217,7 @@ def process_directive pin, api_map, context, locals pin.directives.each do |dir| macro = api_map.named_macro(dir.tag.name) next if macro.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" result = inner_process_macro(pin, macro, api_map, context, locals) return result unless result.return_type.undefined? end @@ -293,7 +293,7 @@ def yield_pins api_map, name_pin method_pin = find_method_pin(name_pin) return [] unless method_pin - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" method_pin.signatures.map(&:block).compact.map do |signature_pin| return_type = signature_pin.return_type.qualify(api_map, name_pin.namespace) signature_pin.proxy(return_type) @@ -343,7 +343,7 @@ def find_block_pin(api_map) node_location = Solargraph::Location.from_node(block.node) return if node_location.nil? block_pins = api_map.get_block_pins - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" block_pins.find { |pin| pin.location.contain?(node_location) } end diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index 6189a0a40..1d969825e 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -31,11 +31,11 @@ def write text, nullable = false if nullable and !range.nil? and new_text.match(/[.\[{(@$:]$/) [':', '@'].each do |dupable| next unless new_text == dupable - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables offset = Position.to_offset(text, range.start) if text[offset - 1] == dupable p = Position.from_offset(text, offset - 1) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables r = Change.new(Range.new(p, range.start), ' ') text = r.write(text) end @@ -60,7 +60,7 @@ def repair text fixed else result = commit text, fixed - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "else" off = Position.to_offset(text, range.start) # @sg-ignore Need to add nil check here match = result[0, off].match(/[.:]+\z/) diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 8e7e1ed8b..62c1c330a 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -112,7 +112,7 @@ def string? def recipient @recipient ||= begin node = recipient_node - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle ternary operator node ? Cursor.new(source, Range.from_node(node).ending) : nil end end diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index 6be8b8fdb..2c5dc2c52 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -57,7 +57,7 @@ def chain rescue Parser::SyntaxError return Chain.new([Chain::UNDEFINED_CALL]) end - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables return Chain.new([Chain::UNDEFINED_CALL]) if node.nil? || (node.type == :sym && !phrase.start_with?(':')) # chain = NodeChainer.chain(node, source.filename, parent && parent.type == :block) chain = Parser.chain(node, source.filename, parent) @@ -157,9 +157,9 @@ def get_signature_data_at index # @sg-ignore Need to add nil check here unless char == '.' or @source.code[index+1..-1].strip.start_with?('.') old = @source.code[index+1..-1] - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore Need to add nil check here nxt = @source.code[index+1..-1].lstrip - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore Need to add nil check here index += (@source.code[index+1..-1].length - @source.code[index+1..-1].lstrip.length) break end diff --git a/lib/solargraph/source_map/data.rb b/lib/solargraph/source_map/data.rb index ee9405e58..3aef395f2 100644 --- a/lib/solargraph/source_map/data.rb +++ b/lib/solargraph/source_map/data.rb @@ -12,7 +12,7 @@ def initialize source @locals = nil end - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables # @return [Array] def pins generate @@ -21,7 +21,7 @@ def pins @pins || empty_pins end - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables # @return [Array] def locals generate diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 34fb8fbe9..21cc2c99e 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -137,7 +137,7 @@ def process_directive source_position, comment_position, directive return if directive.tag.name.nil? namespace = closure_at(source_position) t = (directive.tag.types.nil? || directive.tag.types.empty?) ? nil : directive.tag.types.flatten.join('') - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" if t.nil? || t.include?('r') pins.push Solargraph::Pin::Method.new( location: location, @@ -151,7 +151,7 @@ def process_directive source_position, comment_position, directive source: :source_map ) end - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" if t.nil? || t.include?('w') method_pin = Solargraph::Pin::Method.new( location: location, @@ -249,7 +249,7 @@ def remove_inline_comment_hashes comment started = true elsif started && !p.strip.empty? cur = p.index(/[^ ]/) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "else" num = cur if cur < num end ctxt += "#{p[num..-1]}" if started diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 1cadb990f..25ee4e9c5 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -111,7 +111,7 @@ def load_string code, filename = nil, level = :normal, api_map: nil source = Solargraph::Source.load_string(code, filename) rules = Rules.new(level) api_map ||= Solargraph::ApiMap.new(loose_unions: !rules.require_all_unique_types_match_expected?) - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end @@ -322,7 +322,7 @@ def call_problems end closest = found.typify(api_map) if found # @todo remove the internal_or_core? check at a higher-than-strict level - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) # @sg-ignore need to be able to resolve same method signature on two different types unless closest.generic? || ignored_pins.include?(found) @@ -442,7 +442,7 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum if argchain.node.type == :splat && argchain == arguments.last final_arg = argchain end - # @sg-ignore flow sensitive typing needs to handle "&&" + # @sg-ignore flow sensitive typing needs to handle && with variables 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 @@ -606,7 +606,7 @@ def first_param_hash(pins) # @param pin [Pin::Base] def internal? pin return false if pin.nil? - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" pin.location && api_map.bundled?(pin.location.filename) end @@ -749,7 +749,7 @@ def optional_param_count(parameters) # @sg-ignore need boolish support for ? methods def abstract? pin pin.docstring.has_tag?('abstract') || - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables (pin.closure && pin.closure.docstring.has_tag?('abstract')) end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 2aa058881..249a7e3bc 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,25 +58,33 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # flow sensitive typing needs to handle "else" - # flow sensitive typing needs to handle "&&" - # flow sensitive typing needs to handle "if !foo" - # flow sensitive typing needs a not-nil override pin - # @todo 65: Need to add nil check here + # @todo 64: Need to add nil check here + # @todo 43: flow sensitive typing needs to handle && with variables + # @todo 25: flow sensitive typing needs to handle "return if foo.nil?" # @todo 12: need to be able to resolve same method signature on two different types + # @todo 11: flow sensitive typing needs to handle "unless foo.nil?" # @todo 9: Need to validate config # @todo 8: Should better support meaning of '&' in RBS # @todo 7: literal arrays in this module turn into ::Solargraph::Source::Chain::Array + # @todo 6: flow sensitive typing needs to handle "else" + # @todo 5: flow sensitive typing needs to handle ternary operator + # @todo 5: flow sensitive typing needs to handle inner closures + # @todo 5: flow sensitive typing needs better handling of ||= on ivars # @todo 4: need boolish support for ? methods # @todo 4: should understand meaning of &. # @todo 4: Need support for reduce_class_type in UniqueType + # @todo 4: flow sensitive typing needs to handle 'return if' # @todo 3: Need to handle implicit nil on else + # @todo 2: flow sensitive typing needs better handling of ||= on lvars # @todo 2: Should handle redefinition of types in simple contexts # @todo 2: Translate to something flow sensitive typing understands - # @todo 1: flow sensitive typing needs to handle && with variables + # @todo 2: flow sensitive typing needs to handle if on ivars + # @todo 2: flow sensitive typing needs to handle "while foo"" + # @todo 1: flow sensitive typing needs to handle 'raise if' # @todo 1: To make JSON strongly typed we'll need a record syntax # @todo 1: Untyped method Solargraph::Pin::Base#assert_same could not be inferred # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' + # @todo 1: flow sensitive typing needs to handle "if !foo" def require_all_unique_types_match_expected? rank >= LEVELS[:strong] end diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 1755afdc5..256f3784c 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -70,7 +70,7 @@ def load!(gemspec) # @return [Hash{String => String}] a hash of environment variables to override def current_bundle_env_tweaks tweaks = {} - # @sg-ignore flow sensitive typing needs a not-nil override pin + # @sg-ignore flow sensitive typing needs to handle && with variables if ENV['BUNDLE_GEMFILE'] && !ENV['BUNDLE_GEMFILE'].empty? tweaks['BUNDLE_GEMFILE'] = File.expand_path(ENV['BUNDLE_GEMFILE']) end From e863df72cf1e588f4055ec9e3079623934f8f3da Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 18 Sep 2025 11:33:56 -0400 Subject: [PATCH 337/930] Fix annotation --- lib/solargraph/pin/reference/override.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/pin/reference/override.rb b/lib/solargraph/pin/reference/override.rb index 878c309db..76711f5dd 100644 --- a/lib/solargraph/pin/reference/override.rb +++ b/lib/solargraph/pin/reference/override.rb @@ -7,7 +7,7 @@ class Override < Reference # @return [::Array] attr_reader :tags - # @return [::Array] + # @return [::Array<::Symbol>] attr_reader :delete def closure From 3bd84ff3f4b4f70365934b01852a8cd2d8c70f5b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 18 Sep 2025 15:27:25 -0400 Subject: [PATCH 338/930] Initial shot at flow sensitive typing for variables within && exprs --- lib/solargraph/api_map/store.rb | 2 - lib/solargraph/complex_type/unique_type.rb | 1 + lib/solargraph/library.rb | 1 - .../parser/flow_sensitive_typing.rb | 19 +- .../parser/parser_gem/node_methods.rb | 1 + lib/solargraph/pin/local_variable.rb | 2 - lib/solargraph/source.rb | 2 +- lib/solargraph/type_checker.rb | 1 - lib/solargraph/type_checker/rules.rb | 4 +- spec/parser/flow_sensitive_typing_spec.rb | 200 +++++++++++++++++- 10 files changed, 215 insertions(+), 18 deletions(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index b929d6167..1d5c66c13 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -242,9 +242,7 @@ def get_ancestors(fqns) # Add superclass ref = get_superclass(current) - # @sg-ignore flow sensitive typing needs to handle && with variables superclass = ref && constants.dereference(ref) - # @sg-ignore flow sensitive typing needs to handle && with variables if superclass && !superclass.empty? && !visited.include?(superclass) ancestors << superclass queue << superclass diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index a2a7d01e0..d7da41943 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -366,6 +366,7 @@ def resolve_param_generics_from_context(generics_to_resolve, context_type, resol context_params = yield context_type if context_type if context_params && context_params[i] type_arg = context_params[i] + # @sg-ignore Translate to something flow sensitive typing understands type_arg.map do |new_unique_context_type| ut.resolve_generics_from_context generics_to_resolve, new_unique_context_type, resolved_generic_values: resolved_generic_values end diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 7098216a4..70cc5a0dc 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -194,7 +194,6 @@ def definitions_at filename, line, column # @sg-ignore Need to add nil check here rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i) if lft && rgt - # @sg-ignore flow sensitive typing needs to handle && with variables tag = (lft[1] + rgt[1]).sub(/:+$/, '') clip = mutex.synchronize { api_map.clip(cursor) } clip.translate tag diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 563a74bab..38a24c627 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -15,6 +15,8 @@ def initialize(locals, enclosing_breakable_pin = nil) # # @return [void] def process_and(and_node, true_ranges = []) + return unless and_node.type == :and + # @type [Parser::AST::Node] lhs = and_node.children[0] # @type [Parser::AST::Node] @@ -25,7 +27,8 @@ def process_and(and_node, true_ranges = []) rhs_presence = Range.new(before_rhs_pos, get_node_end_position(rhs)) - process_calls(lhs, true_ranges + [rhs_presence]) + process_conditional(lhs, true_ranges + [rhs_presence]) + process_conditional(rhs, true_ranges) end # @param node [Parser::AST::Node] @@ -33,6 +36,8 @@ def process_and(and_node, true_ranges = []) # # @return [void] def process_calls(node, true_presences) + return unless node.type == :send + process_isa(node, true_presences) process_nilp(node, true_presences) end @@ -187,13 +192,9 @@ def process_facts(facts_by_pin, presences) # # @return [void] def process_conditional(conditional_node, true_ranges) - if conditional_node.type == :send - process_calls(conditional_node, true_ranges) - elsif conditional_node.type == :and - process_and(conditional_node, true_ranges) - elsif [:lvar, :ivar, :cvar, :gvar].include?(conditional_node.type) - process_variable(conditional_node, true_ranges) - end + process_calls(conditional_node, true_ranges) + process_and(conditional_node, true_ranges) + process_variable(conditional_node, true_ranges) end # @param call_node [Parser::AST::Node] @@ -304,6 +305,8 @@ def parse_variable(var_node) # @param node [Parser::AST::Node] # @param true_presences [Array] def process_variable(node, true_presences) + return unless [:lvar, :ivar, :cvar, :gvar].include?(node.type) + variable_name = parse_variable(node) return if variable_name.nil? diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 771ed9cdf..f05363ecd 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -251,6 +251,7 @@ def find_recipient_node cursor def repaired_find_recipient_node cursor cursor = cursor.source.cursor_at([cursor.position.line, cursor.position.column - 1]) node = cursor.source.tree_at(cursor.position.line, cursor.position.column).first + # @sg-ignore flow sensitive typing needs to handle && with variables return node if node && node.type == :send end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index dc8566964..b41857777 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -76,8 +76,6 @@ def return_type_minus_exclusions(raw_return_type) # TODO: Should either complain at strong level on .items # dereferences or not show ', nil' in hover docs' types = raw_return_type.items - exclude_return_type.items - # @sg-ignore TODO: Get this to not error out - not clear - # why it would given hoverover docs types = [ComplexType::UniqueType::UNDEFINED] if types.empty? ComplexType.new(types) else diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 6e652e0a8..2fe06ee42 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -83,7 +83,7 @@ def node_at(line, column) # # @param line [Integer] # @param column [Integer] - # @return [Array] + # @return [Array] def tree_at(line, column) position = Position.new(line, column) stack = [] diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 25ee4e9c5..cbbd26eec 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -442,7 +442,6 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum if argchain.node.type == :splat && argchain == arguments.last final_arg = argchain end - # @sg-ignore flow sensitive typing needs to handle && with variables 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 diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 249a7e3bc..3208cd872 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -59,7 +59,7 @@ def require_inferred_type_params? end # @todo 64: Need to add nil check here - # @todo 43: flow sensitive typing needs to handle && with variables + # flow sensitive typing needs to handle && with variables # @todo 25: flow sensitive typing needs to handle "return if foo.nil?" # @todo 12: need to be able to resolve same method signature on two different types # @todo 11: flow sensitive typing needs to handle "unless foo.nil?" @@ -77,7 +77,7 @@ def require_inferred_type_params? # @todo 3: Need to handle implicit nil on else # @todo 2: flow sensitive typing needs better handling of ||= on lvars # @todo 2: Should handle redefinition of types in simple contexts - # @todo 2: Translate to something flow sensitive typing understands + # Translate to something flow sensitive typing understands # @todo 2: flow sensitive typing needs to handle if on ivars # @todo 2: flow sensitive typing needs to handle "while foo"" # @todo 1: flow sensitive typing needs to handle 'raise if' diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index f814c914a..5a749831f 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -254,7 +254,7 @@ def baz; end expect { clip.infer.to_s }.not_to raise_error end - it 'uses nil? in a simple if() to refine types on a simple class' do + it 'uses nil? in a simple if() to refine nilness' do source = Solargraph::Source.load_string(%( # @param repr [Integer, nil] def verify_repro(repr) @@ -280,6 +280,204 @@ def verify_repro(repr) expect(clip.infer.rooted_tags).to eq('::Integer') end + it 'uses nil? and && in a simple if() to refine nilness - nil? first' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr.nil? && throw_the_dice + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses nil? and && in a simple if() to refine nilness - nil? second' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if throw_the_dice && repr.nil? + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses nil? and || in a simple if() - nil? first' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr.nil? || throw_the_dice + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses nil? and || in a simple if() - nil? second' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if throw_the_dice || repr.nil? + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses varname and || in a simple if() - varname first' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr || throw_the_dice + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending('supporting else after || on varname') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses varname and || in a simple if() - varname second' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if throw_the_dice || repr + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending('supporting else after || on varname') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses varname and && in a simple if() - varname first' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr && throw_the_dice + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses varname and && in a simple if() - varname second' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if throw_the_dice && repr + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + pending('supporting else after && on varname') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + it 'uses variable in a simple if() to refine types on a simple class' do source = Solargraph::Source.load_string(%( # @param repr [Integer, nil] From 5299b0685cef30a73491b942acb5f10989d0ade3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 18 Sep 2025 16:29:20 -0400 Subject: [PATCH 339/930] Clarify next problem to solve for each --- lib/solargraph.rb | 2 +- lib/solargraph/api_map.rb | 2 +- lib/solargraph/api_map/source_to_yard.rb | 10 +++++----- lib/solargraph/convention/data_definition.rb | 2 +- lib/solargraph/convention/struct_definition.rb | 2 +- lib/solargraph/library.rb | 18 +++++++++--------- lib/solargraph/parser/node_processor/base.rb | 2 +- .../parser/parser_gem/node_methods.rb | 4 ++-- lib/solargraph/pin/base.rb | 4 ++-- lib/solargraph/pin/parameter.rb | 8 ++++---- lib/solargraph/rbs_map.rb | 2 +- lib/solargraph/source.rb | 6 +++--- lib/solargraph/source/change.rb | 4 ++-- lib/solargraph/source/source_chainer.rb | 2 +- lib/solargraph/source_map/data.rb | 5 ++--- lib/solargraph/type_checker.rb | 6 +++--- lib/solargraph/type_checker/rules.rb | 18 +++++++++++------- lib/solargraph/yardoc.rb | 2 +- 18 files changed, 51 insertions(+), 48 deletions(-) diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 6924b7619..e76891080 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -57,7 +57,7 @@ class InvalidRubocopVersionError < RuntimeError; end # @param type [Symbol] Type of assert. def self.asserts_on?(type) - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || with variables if ENV['SOLARGRAPH_ASSERTS'].nil? || ENV['SOLARGRAPH_ASSERTS'].empty? false elsif ENV['SOLARGRAPH_ASSERTS'] == 'on' diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 57f9e82e7..ac4ad10ae 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -767,7 +767,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if deep && scope == :instance store.get_prepends(fqns).reverse.each do |im| fqim = store.constants.dereference(im) - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil? end end diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index a494a8948..0f7f07411 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -36,14 +36,14 @@ def rake_yard store end if pin.type == :class code_object_map[pin.path] ||= YARD::CodeObjects::ClassObject.new(root_code_object, pin.path) { |obj| - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || with variables next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || with variables obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) } else code_object_map[pin.path] ||= YARD::CodeObjects::ModuleObject.new(root_code_object, pin.path) { |obj| - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || with variables next if pin.location.nil? || pin.location.filename.nil? # @sg-ignore flow sensitive typing needs better handling of ||= on lvars obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) @@ -77,9 +77,9 @@ def rake_yard store # @sg-ignore Need to add nil check here code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope) { |obj| - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || with variables next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || with variables obj.add_file pin.location.filename, pin.location.range.start.line } method_object = code_object_at(pin.path, YARD::CodeObjects::MethodObject) diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index c851a951e..26f725641 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -96,7 +96,7 @@ def data_definition_node # @return [String, nil] def attribute_comments(attribute_node, attribute_name) data_comments = comments_for(attribute_node) - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || with variables return if data_comments.nil? || data_comments.empty? # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index dc1b777bc..532b04d77 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -142,7 +142,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 && with variables + # @sg-ignore flow sensitive typing needs to handle || with variables # @return [String] def tag_string(tag) tag&.types&.join(',') || 'undefined' diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 70cc5a0dc..1cf44a639 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -57,11 +57,11 @@ def synchronized? # @param source [Source, nil] # @return [void] def attach source - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "if !foo" if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename) - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "if !foo" source_map_hash.delete @current.filename - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "if !foo" source_map_external_require_hash.delete @current.filename @external_requires = nil end @@ -75,9 +75,9 @@ def attach source # # @param filename [String] # @return [Boolean] - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "if !foo" def attached? filename - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "if !foo" !@current.nil? && @current.filename == filename end alias open? attached? @@ -87,7 +87,7 @@ def attached? filename # @param filename [String] # @return [Boolean] True if the specified file was detached def detach filename - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || with variables return false if @current.nil? || @current.filename != filename attach nil true @@ -284,7 +284,7 @@ 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| - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore Need to add nil check here 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 @@ -556,7 +556,7 @@ def api_map # @sg-ignore flow sensitive typing needs to handle if foo && ... # @return [Solargraph::Source] def read filename - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle && with ivars return @current if @current && @current.filename == filename raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename) workspace.source(filename) @@ -580,7 +580,7 @@ def maybe_map source return unless source return unless @current == source || workspace.has_file?(source.filename) if source_map_hash.key?(source.filename) - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" new_map = Solargraph::SourceMap.map(source) source_map_hash[source.filename] = new_map else diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index 93b302c48..b7fb230cd 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -68,7 +68,7 @@ def comments_for(node) # @return [Pin::Closure, nil] def named_path_pin position pins.select do |pin| - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore Translate to something flow sensitive typing understands pin.is_a?(Pin::Closure) && pin.path && !pin.path.empty? && pin.location.range.contain?(position) end.last end diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index f05363ecd..bea80ce4e 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -231,7 +231,7 @@ def find_recipient_node cursor args = node.children[2..-1] # @sg-ignore Need to add nil check here if !args.empty? - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore Need to add nil check here return node if prev && args.include?(prev) else if source.synchronized? @@ -251,7 +251,7 @@ def find_recipient_node cursor def repaired_find_recipient_node cursor cursor = cursor.source.cursor_at([cursor.position.line, cursor.position.column - 1]) node = cursor.source.tree_at(cursor.position.line, cursor.position.column).first - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore investigate why Parser::AST#type isn't available here return node if node && node.type == :send end diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 3d302bc8d..ebadc1dfe 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -464,7 +464,7 @@ def best_location def nearly? other self.class == other.class && name == other.name && - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || within && (closure == other.closure || (closure && closure.nearly?(other.closure))) && (comments == other.comments || (((maybe_directives? == false && other.maybe_directives? == false) || compare_directives(directives, other.directives)) && @@ -515,7 +515,7 @@ def macros # # @return [Boolean] def maybe_directives? - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle && with unrelated calls return !@directives.empty? if defined?(@directives) && @directives @maybe_directives ||= comments.include?('@!') end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 0b3cef911..1274c5429 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -45,7 +45,7 @@ def keyword? end def kwrestarg? - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || within && decl == :kwrestarg || (assignment && [:HASH, :hash].include?(assignment.type)) end @@ -152,7 +152,7 @@ def return_type if @return_type.nil? @return_type = ComplexType::UNDEFINED found = param_tag - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" @return_type = ComplexType.try_parse(*found.types) unless found.nil? or found.types.nil? # @sg-ignore Need to add nil check here if @return_type.undefined? @@ -203,7 +203,7 @@ def compatible_arg?(atype, api_map) def documentation tag = param_tag - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || with variables return '' if tag.nil? || tag.text.nil? # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" tag.text @@ -250,7 +250,7 @@ def typify_method_param api_map if found.nil? and !index.nil? found = params[index] if params[index] && (params[index].name.nil? || params[index].name.empty?) end - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" return ComplexType.try_parse(*found.types).qualify(api_map, meth.context.namespace) unless found.nil? || found.types.nil? end ComplexType::UNDEFINED diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index 9c4ff8a84..4939ec19a 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -58,7 +58,7 @@ def cache_key data = gem_config&.to_s end end - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || with variables if data.nil? || data.empty? if resolved? # definitely came from the gem itself and not elsewhere - diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 2fe06ee42..11cd7c0d4 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -258,11 +258,11 @@ def associated_comments # @type [Integer, nil] last = nil comments.each_pair do |num, snip| - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "if !foo" if !last || num == last + 1 buffer.concat "#{snip.text}\n" else - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "if !foo" result[first_not_empty_from(last + 1)] = buffer.clone buffer.replace "#{snip.text}\n" end @@ -320,7 +320,7 @@ def stringify_comment_array comments ctxt.concat p else here = p.index(/[^ \t]/) - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore Should handle redefinition of types in simple contexts skip = here if skip.nil? || here < skip ctxt.concat p[skip..-1] end diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index 1d969825e..c9d197092 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -31,11 +31,11 @@ def write text, nullable = false if nullable and !range.nil? and new_text.match(/[.\[{(@$:]$/) [':', '@'].each do |dupable| next unless new_text == dupable - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "if !foo" offset = Position.to_offset(text, range.start) if text[offset - 1] == dupable p = Position.from_offset(text, offset - 1) - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "if !foo" r = Change.new(Range.new(p, range.start), ' ') text = r.write(text) end diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index 2c5dc2c52..8ffe9e63c 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -57,7 +57,7 @@ def chain rescue Parser::SyntaxError return Chain.new([Chain::UNDEFINED_CALL]) end - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || within && return Chain.new([Chain::UNDEFINED_CALL]) if node.nil? || (node.type == :sym && !phrase.start_with?(':')) # chain = NodeChainer.chain(node, source.filename, parent && parent.type == :block) chain = Parser.chain(node, source.filename, parent) diff --git a/lib/solargraph/source_map/data.rb b/lib/solargraph/source_map/data.rb index 3aef395f2..2201c2221 100644 --- a/lib/solargraph/source_map/data.rb +++ b/lib/solargraph/source_map/data.rb @@ -12,7 +12,7 @@ def initialize source @locals = nil end - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || with variables # @return [Array] def pins generate @@ -21,8 +21,7 @@ def pins @pins || empty_pins end - # @sg-ignore flow sensitive typing needs to handle && with variables - # @return [Array] + # @sg-ignore flow sensitive typing needs to handle || with variables def locals generate # @type [Array] diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index cbbd26eec..82a32fc43 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -111,7 +111,7 @@ def load_string code, filename = nil, level = :normal, api_map: nil source = Solargraph::Source.load_string(code, filename) rules = Rules.new(level) api_map ||= Solargraph::ApiMap.new(loose_unions: !rules.require_all_unique_types_match_expected?) - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs better handling of ||= on lvars api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end @@ -322,7 +322,7 @@ def call_problems end closest = found.typify(api_map) if found # @todo remove the internal_or_core? check at a higher-than-strict level - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle "if !foo" if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) # @sg-ignore need to be able to resolve same method signature on two different types unless closest.generic? || ignored_pins.include?(found) @@ -748,7 +748,7 @@ def optional_param_count(parameters) # @sg-ignore need boolish support for ? methods def abstract? pin pin.docstring.has_tag?('abstract') || - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore flow sensitive typing needs to handle || within && (pin.closure && pin.closure.docstring.has_tag?('abstract')) end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 3208cd872..8005efc73 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,33 +58,37 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo 64: Need to add nil check here - # flow sensitive typing needs to handle && with variables + # @todo 240: Need to add nil check here # @todo 25: flow sensitive typing needs to handle "return if foo.nil?" + # @todo 13: flow sensitive typing needs to handle "unless foo.nil?" + # @todo 13: flow sensitive typing needs to handle || with variables # @todo 12: need to be able to resolve same method signature on two different types - # @todo 11: flow sensitive typing needs to handle "unless foo.nil?" # @todo 9: Need to validate config # @todo 8: Should better support meaning of '&' in RBS + # @todo 8: flow sensitive typing needs to handle "if !foo" # @todo 7: literal arrays in this module turn into ::Solargraph::Source::Chain::Array # @todo 6: flow sensitive typing needs to handle "else" # @todo 5: flow sensitive typing needs to handle ternary operator # @todo 5: flow sensitive typing needs to handle inner closures # @todo 5: flow sensitive typing needs better handling of ||= on ivars + # @todo 5: Translate to something flow sensitive typing understands + # @todo 4: Should handle redefinition of types in simple contexts # @todo 4: need boolish support for ? methods # @todo 4: should understand meaning of &. # @todo 4: Need support for reduce_class_type in UniqueType # @todo 4: flow sensitive typing needs to handle 'return if' + # @todo 4: flow sensitive typing needs to handle || within && # @todo 3: Need to handle implicit nil on else - # @todo 2: flow sensitive typing needs better handling of ||= on lvars - # @todo 2: Should handle redefinition of types in simple contexts - # Translate to something flow sensitive typing understands + # @todo 3: flow sensitive typing needs better handling of ||= on lvars # @todo 2: flow sensitive typing needs to handle if on ivars # @todo 2: flow sensitive typing needs to handle "while foo"" + # @todo 1: flow sensitive typing needs to handle && with ivars + # @todo 1: investigate why Parser::AST#type isn't available here + # @todo 1: flow sensitive typing needs to handle && with unrelated calls # @todo 1: flow sensitive typing needs to handle 'raise if' # @todo 1: To make JSON strongly typed we'll need a record syntax # @todo 1: Untyped method Solargraph::Pin::Base#assert_same could not be inferred # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' - # @todo 1: flow sensitive typing needs to handle "if !foo" def require_all_unique_types_match_expected? rank >= LEVELS[:strong] end diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 256f3784c..337b02a4f 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -70,7 +70,7 @@ def load!(gemspec) # @return [Hash{String => String}] a hash of environment variables to override def current_bundle_env_tweaks tweaks = {} - # @sg-ignore flow sensitive typing needs to handle && with variables + # @sg-ignore Translate to something flow sensitive typing understands if ENV['BUNDLE_GEMFILE'] && !ENV['BUNDLE_GEMFILE'].empty? tweaks['BUNDLE_GEMFILE'] = File.expand_path(ENV['BUNDLE_GEMFILE']) end From 8c8edd204605c39d8cab73c3c23690e419772230 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 18 Sep 2025 16:52:09 -0400 Subject: [PATCH 340/930] Type fixup --- lib/solargraph/source_map/data.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/source_map/data.rb b/lib/solargraph/source_map/data.rb index 2201c2221..1d00fda3f 100644 --- a/lib/solargraph/source_map/data.rb +++ b/lib/solargraph/source_map/data.rb @@ -22,6 +22,7 @@ def pins end # @sg-ignore flow sensitive typing needs to handle || with variables + # @return [Array] def locals generate # @type [Array] From abcaa46145e290ff50072ec2c0a991ba073950d3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 18 Sep 2025 19:15:53 -0400 Subject: [PATCH 341/930] Get more flow sensitive typing specs working --- .../parser/flow_sensitive_typing.rb | 65 ++++++++++++++----- spec/parser/flow_sensitive_typing_spec.rb | 51 +++++++++++++++ 2 files changed, 98 insertions(+), 18 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 38a24c627..636010335 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -12,9 +12,10 @@ def initialize(locals, enclosing_breakable_pin = nil) # @param and_node [Parser::AST::Node] # @param true_ranges [Array] + # @param false_ranges [Array] # # @return [void] - def process_and(and_node, true_ranges = []) + def process_and(and_node, true_ranges = [], false_ranges = []) return unless and_node.type == :and # @type [Parser::AST::Node] @@ -27,19 +28,20 @@ def process_and(and_node, true_ranges = []) rhs_presence = Range.new(before_rhs_pos, get_node_end_position(rhs)) - process_conditional(lhs, true_ranges + [rhs_presence]) - process_conditional(rhs, true_ranges) + process_conditional(lhs, true_ranges + [rhs_presence], false_ranges) + process_conditional(rhs, true_ranges, false_ranges) end # @param node [Parser::AST::Node] # @param true_presences [Array] + # @param false_presences [Array] # # @return [void] - def process_calls(node, true_presences) + def process_calls(node, true_presences, false_presences) return unless node.type == :send - process_isa(node, true_presences) - process_nilp(node, true_presences) + process_isa(node, true_presences, false_presences) + process_nilp(node, true_presences, false_presences) end # @param if_node [Parser::AST::Node] @@ -64,10 +66,17 @@ def process_if(if_node) else_clause = if_node.children[2] true_ranges = [] - if always_breaks?(else_clause) - unless enclosing_breakable_pin.nil? - rest_of_breakable_body = Range.new(get_node_end_position(if_node), - get_node_end_position(enclosing_breakable_pin.node)) + false_ranges = [] + + unless enclosing_breakable_pin.nil? + rest_of_breakable_body = Range.new(get_node_end_position(if_node), + get_node_end_position(enclosing_breakable_pin.node)) + + if always_breaks?(then_clause) + false_ranges << rest_of_breakable_body + end + + if always_breaks?(else_clause) true_ranges << rest_of_breakable_body end end @@ -82,7 +91,7 @@ def process_if(if_node) get_node_end_position(then_clause)) end - process_conditional(conditional_node, true_ranges) + process_conditional(conditional_node, true_ranges, false_ranges) end class << self @@ -189,12 +198,13 @@ def process_facts(facts_by_pin, presences) # @param conditional_node [Parser::AST::Node] # @param true_ranges [Array] + # @param false_ranges [Array] # # @return [void] - def process_conditional(conditional_node, true_ranges) - process_calls(conditional_node, true_ranges) - process_and(conditional_node, true_ranges) - process_variable(conditional_node, true_ranges) + def process_conditional(conditional_node, true_ranges, false_ranges) + process_calls(conditional_node, true_ranges, false_ranges) + process_and(conditional_node, true_ranges, false_ranges) + process_variable(conditional_node, true_ranges, false_ranges) end # @param call_node [Parser::AST::Node] @@ -246,9 +256,10 @@ def find_local(variable_name, position) # @param isa_node [Parser::AST::Node] # @param true_presences [Array] + # @param false_presences [Array] # # @return [void] - def process_isa(isa_node, true_presences) + def process_isa(isa_node, true_presences, false_presences) isa_type_name, variable_name = parse_isa(isa_node) return if variable_name.nil? || variable_name.empty? # @sg-ignore Need to add nil check here @@ -261,6 +272,12 @@ def process_isa(isa_node, true_presences) if_true[pin] ||= [] if_true[pin] << { type: isa_type_name } process_facts(if_true, true_presences) + + if_false = {} + if_true[pin] ||= [] + # @todo: should add support for not_type + # if_true[pin] << { not_type: isa_type_name } # + process_facts(if_true, false_presences) end # @param nilp_node [Parser::AST::Node] @@ -271,9 +288,10 @@ def parse_nilp(nilp_node) # @param nilp_node [Parser::AST::Node] # @param true_presences [Array] + # @param false_presences [Array] # # @return [void] - def process_nilp(nilp_node, true_presences) + def process_nilp(nilp_node, true_presences, false_presences) nilp_arg, variable_name = parse_nilp(nilp_node) return if variable_name.nil? || variable_name.empty? # if .nil? got an argument, move on, this isn't the situation @@ -290,6 +308,11 @@ def process_nilp(nilp_node, true_presences) if_true[pin] ||= [] if_true[pin] << { nil: true } process_facts(if_true, true_presences) + + if_false = {} + if_false[pin] ||= [] + if_false[pin] << { not_nil: true } + process_facts(if_false, false_presences) end # @param var_node [Parser::AST::Node] @@ -304,7 +327,8 @@ def parse_variable(var_node) # @return [void] # @param node [Parser::AST::Node] # @param true_presences [Array] - def process_variable(node, true_presences) + # @param false_presences [Array] + def process_variable(node, true_presences, false_presences) return unless [:lvar, :ivar, :cvar, :gvar].include?(node.type) variable_name = parse_variable(node) @@ -321,6 +345,11 @@ def process_variable(node, true_presences) if_true[pin] ||= [] if_true[pin] << { not_nil: true } process_facts(if_true, true_presences) + + if_false = {} + if_false[pin] ||= [] + if_false[pin] << { nil: true } + process_facts(if_false, false_presences) end # @param node [Parser::AST::Node] diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 5a749831f..eaceb8986 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -212,6 +212,38 @@ class Repro < ReproBase; end expect(clip.infer.to_s).to eq('Float') end + it 'uses varname in a "break unless" statement in a while to refine types' do + source = Solargraph::Source.load_string(%( + class ReproBase; end + class Repro < ReproBase; end + # @type [ReproBase, nil] + value = bar + while !is_done() + break unless value + value + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 8]) + expect(clip.infer.to_s).to eq('ReproBase') + end + + it 'uses varname in a "break if" statement in a while to refine types' do + source = Solargraph::Source.load_string(%( + class ReproBase; end + class Repro < ReproBase; end + # @type [ReproBase, nil] + value = bar + while !is_done() + break if value.nil? + value + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 8]) + expect(clip.infer.to_s).to eq('ReproBase') + end + it 'understands compatible reassignments' do source = Solargraph::Source.load_string(%( class Foo @@ -527,4 +559,23 @@ def verify_repro(repr = nil) clip = api_map.clip_at('test.rb', [7, 10]) expect(clip.infer.rooted_tags).to eq('nil') end + + it 'uses variable in a simple if() to refine types on a simple class using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + return if baz.nil? + baz + end + end + ), 'test.rb') + + pending('return if support') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + end end From 6e77dd4ce4e5837d1c9f1c82c9301e97cce73bdd Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 19 Sep 2025 07:48:58 -0400 Subject: [PATCH 342/930] Rename examples --- spec/parser/flow_sensitive_typing_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index eaceb8986..7082292f9 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -3,7 +3,7 @@ # @todo These tests depend on `Clip`, but we're putting the tests here to # avoid overloading clip_spec.rb. describe Solargraph::Parser::FlowSensitiveTyping do - it 'uses is_a? in a simple if() to refine types on a simple class' do + it 'uses is_a? in a simple if() to refine types' do source = Solargraph::Source.load_string(%( class ReproBase; end class Repro < ReproBase; end @@ -72,7 +72,7 @@ def verify_repro(repr) expect(clip.infer.to_s).to eq('ReproBase') end - it 'uses is_a? in a simple unless statement to refine types on a simple class' do + it 'uses is_a? in a simple unless statement to refine types' do source = Solargraph::Source.load_string(%( class ReproBase; end class Repro < ReproBase; end @@ -510,7 +510,7 @@ def verify_repro(repr, throw_the_dice) expect(clip.infer.rooted_tags).to eq('nil') end - it 'uses variable in a simple if() to refine types on a simple class' do + it 'uses variable in a simple if() to refine types' do source = Solargraph::Source.load_string(%( # @param repr [Integer, nil] def verify_repro(repr) @@ -535,7 +535,7 @@ def verify_repro(repr) expect(clip.infer.rooted_tags).to eq('nil') end - it 'uses variable in a simple if() to refine types on a simple class using nil checks' do + it 'uses variable in a simple if() to refine types using nil checks' do source = Solargraph::Source.load_string(%( def verify_repro(repr = nil) repr = 10 if floop @@ -560,7 +560,7 @@ def verify_repro(repr = nil) expect(clip.infer.rooted_tags).to eq('nil') end - it 'uses variable in a simple if() to refine types on a simple class using nil checks' do + it 'uses .nil? in a return if() in an if to refine types using nil checks' do source = Solargraph::Source.load_string(%( class Foo # @param baz [::Boolean, nil] From a7d70ecf57e75c559aae482700b852cf0046b5b0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 19 Sep 2025 07:49:24 -0400 Subject: [PATCH 343/930] Add draft of next set of examples --- spec/parser/flow_sensitive_typing_spec.rb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 7082292f9..8e31d5f3d 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -566,7 +566,10 @@ class Foo # @param baz [::Boolean, nil] # @return [void] def bar(baz: nil) - return if baz.nil? + if rand + return if baz.nil? + baz + end baz end end @@ -575,7 +578,17 @@ def bar(baz: nil) pending('return if support') api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [6, 10]) + clip = api_map.clip_at('test.rb', [7, 12]) expect(clip.infer.rooted_tags).to eq('::Boolean') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [9, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') end + + it 'uses .nil? in a return if() in a method to refine types using nil checks' + it 'uses .nil? in a return if() in a block to refine types using nil checks' + it 'uses .nil? in a return if() in an unless to refine types using nil checks' + it 'uses .nil? in a return if() in a while to refine types using nil checks' + it 'uses .nil? in a return if() in anb until to refine types using nil checks' end From e5d88a51307a948de85880fcbbde4a8ec790d199 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 19 Sep 2025 18:15:17 -0400 Subject: [PATCH 344/930] 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 e5f6c4eef95c3d16609bae97e0574e43f6913e34 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 19 Sep 2025 18:18:12 -0400 Subject: [PATCH 345/930] Handle 'return' in conditionals in flow sensitive typing --- .../parser/flow_sensitive_typing.rb | 59 +++++++++++++++--- .../parser_gem/node_processors/and_node.rb | 9 ++- .../parser_gem/node_processors/if_node.rb | 16 ++++- lib/solargraph/pin.rb | 3 + lib/solargraph/pin/base_variable.rb | 13 +++- lib/solargraph/pin/breakable.rb | 5 +- lib/solargraph/pin/compound_statementable.rb | 47 ++++++++++++++ lib/solargraph/pin/if.rb | 18 ++++++ lib/solargraph/pin/method.rb | 1 + lib/solargraph/source_map/mapper.rb | 4 +- spec/parser/flow_sensitive_typing_spec.rb | 44 ++++++++++--- spec/type_checker/levels/strong_spec.rb | 61 ++++++++++++++++++- 12 files changed, 253 insertions(+), 27 deletions(-) create mode 100644 lib/solargraph/pin/compound_statementable.rb create mode 100644 lib/solargraph/pin/if.rb diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 636010335..692542929 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -5,9 +5,11 @@ class FlowSensitiveTyping # @param locals [Array] # @param enclosing_breakable_pin [Solargraph::Pin::Breakable, nil] - def initialize(locals, enclosing_breakable_pin = nil) + # @param enclosing_compound_statement_pin [Solargraph::Pin::CompoundStatementable, nil] + def initialize(locals, enclosing_breakable_pin, enclosing_compound_statement_pin) @locals = locals @enclosing_breakable_pin = enclosing_breakable_pin + @enclosing_compound_statement_pin = enclosing_compound_statement_pin end # @param and_node [Parser::AST::Node] @@ -28,8 +30,12 @@ def process_and(and_node, true_ranges = [], false_ranges = []) rhs_presence = Range.new(before_rhs_pos, get_node_end_position(rhs)) - process_conditional(lhs, true_ranges + [rhs_presence], false_ranges) - process_conditional(rhs, true_ranges, false_ranges) + + # can't assume if an and is false that every single condition + # is false, so don't provide any false ranges to assert facts + # on + process_conditional(lhs, true_ranges + [rhs_presence], []) + process_conditional(rhs, true_ranges, []) end # @param node [Parser::AST::Node] @@ -81,9 +87,27 @@ def process_if(if_node) end end + unless enclosing_compound_statement_pin.nil? + rest_of_returnable_body = Range.new(get_node_end_position(if_node), + get_node_end_position(enclosing_compound_statement_pin.node)) + + # + # if one of the clauses always leaves the compound + # statement, we can assume things about the rest of the + # compound statement + # + if always_leaves_compound_statement?(then_clause) + false_ranges << rest_of_returnable_body + end + + if always_leaves_compound_statement?(else_clause) + true_ranges << rest_of_returnable_body + end + end + unless then_clause.nil? # - # Add specialized locals for the then clause range + # If the condition is true we can assume things about the then clause # before_then_clause_loc = then_clause.location.expression.adjust(begin_pos: -1) before_then_clause_pos = Position.new(before_then_clause_loc.line, before_then_clause_loc.column) @@ -91,6 +115,16 @@ def process_if(if_node) get_node_end_position(then_clause)) end + unless else_clause.nil? + # + # If the condition is true we can assume things about the else clause + # + before_else_clause_loc = else_clause.location.expression.adjust(begin_pos: -1) + before_else_clause_pos = Position.new(before_else_clause_loc.line, before_else_clause_loc.column) + false_ranges << Range.new(before_else_clause_pos, + get_node_end_position(else_clause)) + end + process_conditional(conditional_node, true_ranges, false_ranges) end @@ -161,8 +195,7 @@ def add_downcast_local(pin, downcast_type_name, presence) location: pin.location, closure: pin.closure, name: pin.name, - # not sending along assignment as we are changing the type - # that it implies + assignment: pin.assignment, comments: pin.comments, presence: presence, return_type: return_type, @@ -274,10 +307,10 @@ def process_isa(isa_node, true_presences, false_presences) process_facts(if_true, true_presences) if_false = {} - if_true[pin] ||= [] + if_false[pin] ||= [] # @todo: should add support for not_type - # if_true[pin] << { not_type: isa_type_name } # - process_facts(if_true, false_presences) + # if_true[pin] << { not_type: isa_type_name } + process_facts(if_false, false_presences) end # @param nilp_node [Parser::AST::Node] @@ -376,9 +409,15 @@ def always_breaks?(clause_node) clause_node&.type == :break end + # @param clause_node [Parser::AST::Node, nil] + def always_leaves_compound_statement?(clause_node) + # https://docs.ruby-lang.org/en/2.2.0/keywords_rdoc.html + [:return, :raise, :next, :redo, :retry].include?(clause_node&.type) + end + attr_reader :locals - attr_reader :enclosing_breakable_pin + attr_reader :enclosing_breakable_pin, :enclosing_compound_statement_pin end end end diff --git a/lib/solargraph/parser/parser_gem/node_processors/and_node.rb b/lib/solargraph/parser/parser_gem/node_processors/and_node.rb index d3485af7c..bce5c06f5 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/and_node.rb @@ -11,8 +11,15 @@ 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_and(node) + # @sg-ignore downcast output of Enumerable#select + # @type [Pin::CompoundStatementable] + enclosing_compound_statement_pin = pins.select{|pin| pin.is_a?(Solargraph::Pin::CompoundStatementable) && pin.location.range.contain?(position)}.last + FlowSensitiveTyping.new(locals, + enclosing_breakable_pin, + enclosing_compound_statement_pin).process_and(node) end end end 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 2452b9cc5..ba65a0388 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb @@ -14,7 +14,21 @@ def process # @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) + # @sg-ignore downcast output of Enumerable#select + # @type [Solargraph::Pin::CompoundStatementable, nil] + enclosing_compound_statement_pin = pins.select{|pin| pin.is_a?(Pin::CompoundStatementable) && pin.location.range.contain?(position)}.last + FlowSensitiveTyping.new(locals, + enclosing_breakable_pin, + enclosing_compound_statement_pin).process_if(node) + + pins.push Solargraph::Pin::If.new( + location: get_node_location(node), + closure: region.closure, + node: node, + comments: comments_for(node), + source: :parser, + ) + process_children region end end end diff --git a/lib/solargraph/pin.rb b/lib/solargraph/pin.rb index 526ac6fc3..aa68aa4b1 100644 --- a/lib/solargraph/pin.rb +++ b/lib/solargraph/pin.rb @@ -35,9 +35,12 @@ module Pin autoload :KeywordParam, 'solargraph/pin/keyword_param' autoload :Search, 'solargraph/pin/search' autoload :Breakable, 'solargraph/pin/breakable' + autoload :If, 'solargraph/pin/if' autoload :Until, 'solargraph/pin/until' autoload :While, 'solargraph/pin/while' autoload :Callable, 'solargraph/pin/callable' + autoload :CompoundStatementable, + 'solargraph/pin/compound_statementable' ROOT_PIN = Pin::Namespace.new(type: :class, name: '', closure: nil, source: :pin_rb) end diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 5333d86ac..a69c877de 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -80,13 +80,24 @@ def return_types_from_node(parent_node, api_map) types end + # @return [ComplexType, nil] + def exclude_return_type + nil + end + # @param api_map [ApiMap] # @return [ComplexType] def probe api_map + if presence_certain? && return_type.defined? + # flow sensitive typing has already figured out this type + return return_type.qualify(api_map, namespace) + end + unless @assignment.nil? # @sg-ignore sensitive typing needs to handle "unless foo.nil?" types = return_types_from_node(@assignment, api_map) - return ComplexType.new(types.uniq) unless types.empty? + exclude_items = exclude_return_type&.items&.uniq + return ComplexType.new(types.flat_map(&:items).uniq - (exclude_items || [])) unless types.empty? end unless @mass_assignment.nil? diff --git a/lib/solargraph/pin/breakable.rb b/lib/solargraph/pin/breakable.rb index 05907b1bb..ca1f6af40 100644 --- a/lib/solargraph/pin/breakable.rb +++ b/lib/solargraph/pin/breakable.rb @@ -1,7 +1,10 @@ module Solargraph module Pin - # Mix-in for pins which enclose code which the 'break' statement works with-in - e.g., blocks, when, until, ... + # Mix-in for pins which enclose code which the 'break' statement + # works with-in - e.g., blocks, when, until, ... module Breakable + include CompoundStatementable + # @return [Parser::AST::Node] attr_reader :node end diff --git a/lib/solargraph/pin/compound_statementable.rb b/lib/solargraph/pin/compound_statementable.rb new file mode 100644 index 000000000..e952ca2b6 --- /dev/null +++ b/lib/solargraph/pin/compound_statementable.rb @@ -0,0 +1,47 @@ +module Solargraph + module Pin + # A series of statements where if a given statement executes, /all + # of the previous statements in the sequence must have executed as + # well/. In other words, the statements are run from the top in + # sequence, until interrupted by something like a + # return/break/next/raise/etc. + # + # This mix-in is used in flow sensitive typing to determine how + # far we can assume a given assertion about a type can be trusted + # to be true. + # + # Some examples in Ruby: + # + # * Bodies of methods and Ruby blocks + # * Branches of conditionals and loops - if/elsif/else, + # unless/else, when, until, ||=, ?:, switch/case/else + # * The body of begin-end/try/rescue/ensure statements + # + # Compare/contrast with: + # + # * Scope - a sequence where variables declared are not available + # after the end of the scope. Note that this is not necessarily + # true for a compound statement. + # * Compound statement - synonym + # * Block - in Ruby this has a special meaning (a closure passed to a method), but + # in general parlance this is also a synonym. + # * Closure - a sequence which is also a scope + # * Namespace - a named sequence which is also a scope and a + # closure + # + # See: + # https://cse.buffalo.edu/~regan/cse305/RubyBNF.pdf + # https://ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/syntax.html + # https://en.wikipedia.org/wiki/Block_(programming) + # + # Note: + # + # Just because statement #1 in a sequence is executed, it doesn't + # mean that future ones will. Consider the effect of + # break/next/return/raise/etc. on control flow. + module CompoundStatementable + # @return [Parser::AST::Node] + attr_reader :node + end + end +end diff --git a/lib/solargraph/pin/if.rb b/lib/solargraph/pin/if.rb new file mode 100644 index 000000000..35b8a9bfc --- /dev/null +++ b/lib/solargraph/pin/if.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Solargraph + module Pin + class If < Base + include CompoundStatementable + + # @param receiver [Parser::AST::Node, nil] + # @param node [Parser::AST::Node, nil] + # @param context [ComplexType, nil] + # @param args [::Array] + def initialize node: nil, **splat + super(**splat) + @node = node + end + end + end +end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index f84ae1a7a..79dc3fdb9 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -6,6 +6,7 @@ module Pin # class Method < Callable include Solargraph::Parser::NodeMethods + include CompoundStatementable # @return [::Symbol] :public, :private, or :protected attr_reader :visibility diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 21cc2c99e..89ba70e44 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -24,8 +24,8 @@ def map source @code = source.code @comments = source.comments @pins, @locals = Parser.map(source) - @pins.each { |p| p.source = :code } - @locals.each { |l| l.source = :code } + @pins.each { |p| p.source ||= :code } + @locals.each { |l| l.source ||= :code } process_comment_directives [@pins, @locals] # rescue Exception => e diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 8e31d5f3d..84f4c07ab 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -307,7 +307,6 @@ def verify_repro(repr) # the 10 here is arguably a bug expect(clip.infer.rooted_tags).to eq('nil') - pending('handling else case in flow senstiive typing') clip = api_map.clip_at('test.rb', [8, 10]) expect(clip.infer.rooted_tags).to eq('::Integer') end @@ -530,7 +529,6 @@ def verify_repro(repr) clip = api_map.clip_at('test.rb', [6, 10]) expect(clip.infer.rooted_tags).to eq('::Integer') - pending('handling else case in flow senstiive typing') clip = api_map.clip_at('test.rb', [8, 10]) expect(clip.infer.rooted_tags).to eq('nil') end @@ -575,20 +573,48 @@ def bar(baz: nil) end ), 'test.rb') - pending('return if support') - api_map = Solargraph::ApiMap.new.map(source) clip = api_map.clip_at('test.rb', [7, 12]) expect(clip.infer.rooted_tags).to eq('::Boolean') - - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [9, 10]) - expect(clip.infer.rooted_tags).to eq('::Boolean, nil') end + # https://cse.buffalo.edu/~regan/cse305/RubyBNF.pdf + # https://ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/syntax.html it 'uses .nil? in a return if() in a method to refine types using nil checks' it 'uses .nil? in a return if() in a block to refine types using nil checks' it 'uses .nil? in a return if() in an unless to refine types using nil checks' it 'uses .nil? in a return if() in a while to refine types using nil checks' - it 'uses .nil? in a return if() in anb until to refine types using nil checks' + it 'uses .nil? in a return if() in an until to refine types using nil checks' + it 'uses .nil? in a return if() in a switch/case/else to refine types using nil checks' + it 'uses .nil? in a return if() in a ternary operator to refine types using nil checks' + it 'uses .nil? in a return if() in a begin/end to refine types using nil checks' + it 'uses .nil? in a return if() in a ||= to refine types using nil checks' + it 'uses .nil? in a return if() in a try / rescue / ensure to refine types using nil checks' + it 'uses .nil? in a return if() in top level namespace to refine types using nil checks' + + it 'provides a useful pin after a return if .nil?' do + source = Solargraph::Source.load_string(%( + class A + # @param b [Hash{String => String}] + # @return [void] + def a b + c = b["123"] + c + return c if c.nil? + c + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.to_s).to eq('String') + + clip = api_map.clip_at('test.rb', [7, 17]) + expect(clip.infer.to_s).to eq('nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.to_s).to eq('String') + end end diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 8d0307d79..21054e8d2 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -4,6 +4,20 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end + it 'does not misunderstand types during flow-sensitive typing' do + checker = type_checker(%( + class A + # @param b [Hash{String => String}] + # @return [void] + def a b + c = b["123"] + return if c.nil? + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'respects pin visibility in if/nil? pattern' do checker = type_checker(%( class Foo @@ -17,7 +31,6 @@ def foo bar end end )) - pending('recognizing returning branches in flow sensitive typing') expect(checker.problems.map(&:message)).to be_empty end @@ -25,8 +38,18 @@ def foo bar checker = type_checker(%( # @return [String] def global_config_path - out = ENV['SOLARGRAPH_GLOBAL_CONFIG'] || + ENV['SOLARGRAPH_GLOBAL_CONFIG'] || File.join(Dir.home, '.config', 'solargraph', 'config.yml') + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'is able to probe type over an assignment' do + checker = type_checker(%( + # @return [String] + def global_config_path + out = 'foo' out end )) @@ -50,6 +73,40 @@ def foo bar expect(checker.problems.map(&:message)).to be_empty end + it 'handles a flow sensitive typing if correctly' do + checker = type_checker(%( + # @param a [String, nil] + # @return [void] + def foo a = nil + b = a + if b + b.upcase + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'handles another flow sensitive typing if correctly' do + checker = type_checker(%( + class A + # @param e [String] + # @param f [String] + # @return [void] + def d(e, f:); end + + # @return [void] + def a + c = rand ? nil : "foo" + if c + d(c, f: c) + end + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'does not complain on array dereference' do checker = type_checker(%( # @param idx [Integer, nil] an index From ad13db2f196c649e11d7a3ebcacdf2978765057b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 19 Sep 2025 18:21:22 -0400 Subject: [PATCH 346/930] Add matching annotation changes --- lib/solargraph/api_map.rb | 6 +---- lib/solargraph/api_map/constants.rb | 1 - lib/solargraph/api_map/index.rb | 3 +-- lib/solargraph/api_map/source_to_yard.rb | 3 --- lib/solargraph/api_map/store.rb | 6 ----- lib/solargraph/complex_type/unique_type.rb | 1 - .../convention/active_support_concern.rb | 2 -- .../struct_definition_node.rb | 1 - lib/solargraph/doc_map.rb | 1 - .../language_server/host/message_worker.rb | 4 +--- .../message/text_document/definition.rb | 2 -- lib/solargraph/library.rb | 5 ++-- .../parser/flow_sensitive_typing.rb | 7 +++--- .../parser/parser_gem/node_methods.rb | 1 - lib/solargraph/pin/base.rb | 1 - lib/solargraph/pin/base_variable.rb | 1 - lib/solargraph/pin/local_variable.rb | 3 +-- lib/solargraph/pin/method.rb | 7 +----- lib/solargraph/pin/parameter.rb | 3 +-- lib/solargraph/pin_cache.rb | 3 +++ lib/solargraph/source/chain/call.rb | 4 +--- lib/solargraph/source/cursor.rb | 2 ++ lib/solargraph/source_map/clip.rb | 1 + lib/solargraph/type_checker.rb | 24 ++++++++----------- lib/solargraph/type_checker/rules.rb | 17 +++++++------ lib/solargraph/yard_map/mapper.rb | 4 ++++ 26 files changed, 42 insertions(+), 71 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index ac4ad10ae..f935a03f7 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -767,7 +767,6 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if deep && scope == :instance store.get_prepends(fqns).reverse.each do |im| fqim = store.constants.dereference(im) - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil? end end @@ -796,19 +795,16 @@ 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? - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" 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}" } store.get_extends(fqns).reverse.each do |em| fqem = store.constants.dereference(em) - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil? end rooted_sc_tag = qualify_superclass(rooted_tag) unless rooted_sc_tag.nil? - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" 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? @@ -845,7 +841,6 @@ def get_namespace_type fqns # @type [Pin::Namespace, nil] pin = store.get_path_pins(fqns).select{|p| p.is_a?(Pin::Namespace)}.first return nil if pin.nil? - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" pin.type end @@ -874,6 +869,7 @@ def prefer_non_nil_variables pins # @return [Pin::Method, nil] def resolve_method_alias(alias_pin) ancestors = store.get_ancestors(alias_pin.full_context.tag) + # @type [Pin::Method, nil] original = nil # Search each ancestor for the original method diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 29e49e5e4..625504152 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -69,7 +69,6 @@ def qualify tag, context_tag = '' fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace) return unless fqns - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" fqns + type.substring end diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index d19a09f16..9750fee32 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -156,8 +156,7 @@ def map_overrides if new_pin # @sg-ignore flow sensitive typing needs to handle inner closures new_pin.docstring.add_tag(tag) - # TODO: Should complain that new_pin is a Pin::Base - # being passed into Pin::Method + # @sg-ignore need to do a downcast check on new_pi here redefine_return_type new_pin, tag end end diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index 0f7f07411..b48b2d791 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -62,10 +62,7 @@ def rake_yard store next unless extend_object code_object = code_object_map[ref.parametrized_tag.to_s] next unless code_object - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" extend_object.class_mixins.push code_object - # @todo add spec showing why this next line is necessary - # @sg-ignore need to be able to resolve same method signature on two different types extend_object.instance_mixins.push code_object end end diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 1d5c66c13..52a9141c6 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -97,10 +97,8 @@ def qualify_superclass fq_sub_tag return type.simplify_literals.to_s if type.literal? ref = get_superclass(fq_sub_tag) return unless ref - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" res = constants.dereference(ref) return unless res - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" res + type.substring end @@ -212,9 +210,7 @@ def fqns_pins fqns return [] if fqns.nil? if fqns.include?('::') parts = fqns.split('::') - # @sg-ignore flow sensitive typing needs to handle 'return if' name = parts.pop - # @sg-ignore flow sensitive typing needs to handle 'return if' base = parts.join('::') else base = '' @@ -373,10 +369,8 @@ def uncached_qualify_superclass fq_sub_tag return type.simplify_literals.to_s if type.literal? ref = get_superclass(fq_sub_tag) return unless ref - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" res = constants.dereference(ref) return unless res - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" res + type.substring end end diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index d7da41943..a2a7d01e0 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -366,7 +366,6 @@ def resolve_param_generics_from_context(generics_to_resolve, context_type, resol context_params = yield context_type if context_type if context_params && context_params[i] type_arg = context_params[i] - # @sg-ignore Translate to something flow sensitive typing understands type_arg.map do |new_unique_context_type| ut.resolve_generics_from_context generics_to_resolve, new_unique_context_type, resolved_generic_values: resolved_generic_values end diff --git a/lib/solargraph/convention/active_support_concern.rb b/lib/solargraph/convention/active_support_concern.rb index b5104b17b..74c9ce765 100644 --- a/lib/solargraph/convention/active_support_concern.rb +++ b/lib/solargraph/convention/active_support_concern.rb @@ -80,14 +80,12 @@ def process_include include_tag "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ "Handling class include include_tag=#{include_tag}" end - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" module_extends = api_map.get_extends(rooted_include_tag).map(&:parametrized_tag).map(&:to_s) logger.debug do "ActiveSupportConcern#object(#{fqns}, #{scope}, #{visibility}, #{deep}) - " \ "found module extends of #{rooted_include_tag}: #{module_extends}" end return unless module_extends.include? 'ActiveSupport::Concern' - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" included_class_pins = api_map.inner_get_methods_from_reference(rooted_include_tag, namespace_pin, rooted_type, :class, visibility, deep, skip, true) logger.debug do diff --git a/lib/solargraph/convention/struct_definition/struct_definition_node.rb b/lib/solargraph/convention/struct_definition/struct_definition_node.rb index 6e6afec62..8f03f1fcb 100644 --- a/lib/solargraph/convention/struct_definition/struct_definition_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_definition_node.rb @@ -74,7 +74,6 @@ def keyword_init? return false if keyword_init_param.nil? - # @sg-ignore Need to add nil check here keyword_init_param.children[0].children[1].type == :true end diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 0409e4db3..0669503bb 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -333,7 +333,6 @@ def resolve_path_to_gemspecs path end end return nil if gemspec.nil? - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" [gemspec_or_preference(gemspec)] end diff --git a/lib/solargraph/language_server/host/message_worker.rb b/lib/solargraph/language_server/host/message_worker.rb index 29637bcaf..5f955bf0a 100644 --- a/lib/solargraph/language_server/host/message_worker.rb +++ b/lib/solargraph/language_server/host/message_worker.rb @@ -78,10 +78,8 @@ def next_message cancel_message || next_priority end - # @sg-ignore Declared return type ::Hash, nil does not match - # inferred type ::Hash, ::Array<::Hash>, nil for - # Solargraph::LanguageServer::Host::MessageWorker#cancel_message # @return [Hash, nil] + # @sg-ignore We should understand reassignment of variable to new type def cancel_message # Handle cancellations first idx = messages.find_index { |msg| msg['method'] == '$/cancelRequest' } diff --git a/lib/solargraph/language_server/message/text_document/definition.rb b/lib/solargraph/language_server/message/text_document/definition.rb index 3e7e0b690..96c1e988a 100644 --- a/lib/solargraph/language_server/message/text_document/definition.rb +++ b/lib/solargraph/language_server/message/text_document/definition.rb @@ -33,9 +33,7 @@ def require_location return nil if dloc.nil? [ { - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" uri: file_to_uri(dloc.filename), - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" range: dloc.range.to_hash } ] diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 1cf44a639..f5b7b7af1 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -194,6 +194,7 @@ def definitions_at filename, line, column # @sg-ignore Need to add nil check here rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i) if lft && rgt + # @sg-ignore flow sensitive typing needs to handle && tag = (lft[1] + rgt[1]).sub(/:+$/, '') clip = mutex.synchronize { api_map.clip(cursor) } clip.translate tag @@ -311,7 +312,7 @@ def locate_pins location def locate_ref location map = source_map_hash[location.filename] return if map.nil? - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" + # @sg-ignore Need to add nil check here pin = map.requires.select { |p| p.location.range.contain?(location.range.start) }.first return nil if pin.nil? # @param full [String] @@ -580,11 +581,9 @@ def maybe_map source return unless source return unless @current == source || workspace.has_file?(source.filename) if source_map_hash.key?(source.filename) - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" new_map = Solargraph::SourceMap.map(source) source_map_hash[source.filename] = new_map else - # @sg-ignore flow sensitive typing needs to handle "else" source_map_hash[source.filename] = Solargraph::SourceMap.map(source) end end diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 692542929..dfb0af8b4 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -66,9 +66,9 @@ def process_if(if_node) # s(:send, nil, :bar)) # [4] pry(main)> conditional_node = if_node.children[0] - # @type [Parser::AST::Node] + # @type [Parser::AST::Node, nil] then_clause = if_node.children[1] - # @type [Parser::AST::Node] + # @type [Parser::AST::Node, nil] else_clause = if_node.children[2] true_ranges = [] @@ -370,7 +370,6 @@ def process_variable(node, true_presences, false_presences) # @sg-ignore Need to add nil check here var_position = Range.from_node(node).start - # @sg-ignore flow sensitive typing needs to handle 'return if' pin = find_local(variable_name, var_position) return unless pin @@ -403,7 +402,7 @@ def type_name(node) "#{module_type_name}::#{class_node}" end - # @param clause_node [Parser::AST::Node] + # @param clause_node [Parser::AST::Node, nil] # @sg-ignore need boolish support for ? methods def always_breaks?(clause_node) clause_node&.type == :break diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index bea80ce4e..9cef85d5c 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -251,7 +251,6 @@ def find_recipient_node cursor def repaired_find_recipient_node cursor cursor = cursor.source.cursor_at([cursor.position.line, cursor.position.column - 1]) node = cursor.source.tree_at(cursor.position.line, cursor.position.column).first - # @sg-ignore investigate why Parser::AST#type isn't available here return node if node && node.type == :send end diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index ebadc1dfe..d8faee795 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -81,7 +81,6 @@ def closure # @param other [self] # @param attrs [Hash{::Symbol => Object}] # - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" # @return [self] def combine_with(other, attrs={}) raise "tried to combine #{other.class} with #{self.class}" unless other.class == self.class diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index a69c877de..be05d0c7f 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -65,7 +65,6 @@ def return_types_from_node(parent_node, api_map) else rng = Range.from_node(node) next if rng.nil? - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" pos = rng.ending # @sg-ignore Need to add nil check here clip = api_map.clip_at(location.filename, pos) diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index b41857777..687cd7898 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -73,8 +73,7 @@ def return_type def return_type_minus_exclusions(raw_return_type) @return_type_minus_exclusions ||= if exclude_return_type && raw_return_type - # TODO: Should either complain at strong level on .items - # dereferences or not show ', nil' in hover docs' + # @sg-ignore flow sensitive typing needs to handle && types = raw_return_type.items - exclude_return_type.items types = [ComplexType::UniqueType::UNDEFINED] if types.empty? ComplexType.new(types) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 79dc3fdb9..90c792dc2 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -79,7 +79,6 @@ def combine_signatures(other) end end - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" def combine_with(other, attrs = {}) priority_choice = choose_priority(other) return priority_choice unless priority_choice.nil? @@ -183,6 +182,7 @@ def return_type # @param return_type [ComplexType, nil] # @return [Signature] def generate_signature(parameters, return_type) + # @type [Pin::Signature, nil] block = nil yieldparam_tags = docstring.tags(:yieldparam) yieldreturn_tags = docstring.tags(:yieldreturn) @@ -309,9 +309,7 @@ def typify api_map # @sg-ignore Need to add nil check here logger.debug { "Method#typify(self=#{self}) - type=#{type&.rooted_tags.inspect}" } unless type.nil? - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" qualified = type.qualify(api_map, namespace) - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" logger.debug { "Method#typify(self=#{self}) => #{qualified.rooted_tags.inspect}" } return qualified end @@ -452,7 +450,6 @@ def resolve_ref_tag api_map end next unless ref - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" docstring.add_tag(*ref.docstring.tags(:param)) end self @@ -577,7 +574,6 @@ def resolve_reference ref, api_map else fqns = api_map.qualify(parts.first, namespace) return ComplexType::UNDEFINED if fqns.nil? - # @sg-ignore Need to add nil check here path = fqns + ref[parts.first.length] + parts.last end pins = api_map.get_path_pins(path) @@ -615,7 +611,6 @@ def infer_from_return_nodes api_map clip = api_map.clip_at( # @sg-ignore Need to add nil check here location.filename, - # @sg-ignore Need to add nil check here rng.ending ) # @sg-ignore Need to add nil check here diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 1274c5429..55aa437a0 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -261,7 +261,7 @@ def typify_method_param api_map # @param skip [::Array] # # @return [::Array] - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" def see_reference heredoc, api_map, skip = [] heredoc.ref_tags.each do |ref| next unless ref.tag_name == 'param' && ref.owner @@ -284,7 +284,6 @@ def resolve_reference ref, api_map, skip else fqns = api_map.qualify(parts.first, namespace) return nil if fqns.nil? - # @sg-ignore Need to add nil check here path = fqns + ref[parts.first.length] + parts.last end pins = api_map.get_path_pins(path) diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 9cb93fbd8..450a22c94 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -225,12 +225,15 @@ def uncache *path_segments, out: nil path = File.join(*path_segments) if File.exist?(path) FileUtils.rm_rf path, secure: true + # @sg-ignore Need to add nil check here out.puts "Clearing pin cache in #{path}" unless out.nil? end end # @return [void] # @param path_segments [Array] + # @param out [StringIO, IO, nil] + # @todo need to warn when no @param exists for 'out' def uncache_by_prefix *path_segments, out: nil path = File.join(*path_segments) glob = "#{path}*" diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 316a17cb7..bdd2bc3a8 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -160,7 +160,6 @@ def inferred_pins pins, api_map, name_pin, locals end break if type.defined? end - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" p = p.with_single_signature(new_signature_pin) unless new_signature_pin.nil? next p.proxy(type) if type.defined? if !p.macros.empty? @@ -217,7 +216,6 @@ def process_directive pin, api_map, context, locals pin.directives.each do |dir| macro = api_map.named_macro(dir.tag.name) next if macro.nil? - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" result = inner_process_macro(pin, macro, api_map, context, locals) return result unless result.return_type.undefined? end @@ -244,6 +242,7 @@ def inner_process_macro pin, macro, api_map, context, locals txt.gsub!(/\$#{i}/, v.context.namespace) i += 1 end + # @sg-ignore Need to add nil check here docstring = Solargraph::Source.parse_docstring(txt).to_docstring tag = docstring.tag(:return) unless tag.nil? || tag.types.nil? @@ -281,7 +280,6 @@ def find_method_pin(name_pin) def super_pins api_map, name_pin method_pin = find_method_pin(name_pin) return [] if method_pin.nil? - # @sg-ignore Need to add nil check here pins = api_map.get_method_stack(method_pin.namespace, method_pin.name, scope: method_pin.context.scope) pins.reject{|p| p.path == name_pin.path} end diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 62c1c330a..48848d5c2 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 add nil check here # @return [String] def start_of_word @start_of_word ||= begin @@ -130,6 +131,7 @@ def node_position # @sg-ignore Need to add nil check here match = source.code[0, offset].match(/\s*(\.|:+)\s*$/) if match + # @sg-ignore Need to add nil check here Position.from_offset(source.code, offset - match[0].length) else position diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index fbbc2c6fb..16a9d9bfb 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -164,6 +164,7 @@ def tag_complete # @sg-ignore Need to add nil check here match = source_map.code[0..cursor.offset-1].match(/[\[<, ]([a-z0-9_:]*)\z/i) if match + # @sg-ignore Need to add nil check here full = match[1] # @sg-ignore Need to add nil check here if full.include?('::') diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 21a05b255..c760c9c74 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -45,26 +45,26 @@ def source source_map.source end - # @param inferred [ComplexType] - # @param expected [ComplexType] + # @param inferred [ComplexType, ComplexType::UniqueType] + # @param expected [ComplexType, ComplexType::UniqueType] def return_type_conforms_to?(inferred, expected) conforms_to?(inferred, expected, :return_type) end - # @param inferred [ComplexType] - # @param expected [ComplexType] + # @param inferred [ComplexType, ComplexType::UniqueType] + # @param expected [ComplexType, ComplexType::UniqueType] def arg_conforms_to?(inferred, expected) conforms_to?(inferred, expected, :method_call) end - # @param inferred [ComplexType] - # @param expected [ComplexType] + # @param inferred [ComplexType, ComplexType::UniqueType] + # @param expected [ComplexType, ComplexType::UniqueType] def assignment_conforms_to?(inferred, expected) conforms_to?(inferred, expected, :assignment) end - # @param inferred [ComplexType] - # @param expected [ComplexType] + # @param inferred [ComplexType, ComplexType::UniqueType] + # @param expected [ComplexType, ComplexType::UniqueType] # @param scenario [Symbol] def conforms_to?(inferred, expected, scenario) rules_arr = [] @@ -329,11 +329,8 @@ def call_problems # @todo remove the internal_or_core? check at a higher-than-strict level # @sg-ignore flow sensitive typing needs to handle "if !foo" if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) - # @sg-ignore need to be able to resolve same method signature on two different types unless closest.generic? || ignored_pins.include?(found) - # @sg-ignore need to be able to resolve same method signature on two different types if closest.defined? - # @sg-ignore need to be able to resolve same method signature on two different types result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest.rooted_tags}") else result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}") @@ -470,9 +467,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) - # @sg-ignore flow sensitive typing needs to handle "else" argtype = argtype.self_to_type(closure_pin.context) - # @sg-ignore flow sensitive typing needs to handle "else" 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 @@ -512,6 +507,7 @@ def kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, if data.nil? # @todo Some level (strong, I guess) should require the param here else + # @type [ComplexType, ComplexType::UniqueType] ptype = data[:qualified] ptype = ptype.self_to_type(pin.context) unless ptype.undefined? @@ -652,9 +648,9 @@ def declared_externally? pin missing = base base = base.base end - # @sg-ignore flow sensitive typing needs to handle "if !foo" all_closest = all_found.map { |pin| pin.typify(api_map) } closest = ComplexType.new(all_closest.flat_map(&:items).uniq) + # @sg-ignore flow sensitive typing needs to handle "if !foo" if !found || closest.defined? || internal?(found) return false end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 8005efc73..ab29029f5 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,25 +58,28 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo 240: Need to add nil check here - # @todo 25: flow sensitive typing needs to handle "return if foo.nil?" - # @todo 13: flow sensitive typing needs to handle "unless foo.nil?" + # Need to support nested flow sensitive types + # Need to add nil check here + # REFILE: flow sensitive typing needs to handle "return if foo.nil?" + # downcast output of Enumerable#select + # flow sensitive typing needs to handle && + # REFILE: flow sensitive typing needs to handle "unless foo.nil?" # @todo 13: flow sensitive typing needs to handle || with variables # @todo 12: need to be able to resolve same method signature on two different types # @todo 9: Need to validate config # @todo 8: Should better support meaning of '&' in RBS # @todo 8: flow sensitive typing needs to handle "if !foo" # @todo 7: literal arrays in this module turn into ::Solargraph::Source::Chain::Array - # @todo 6: flow sensitive typing needs to handle "else" + # REFILE: flow sensitive typing needs to handle "else" # @todo 5: flow sensitive typing needs to handle ternary operator - # @todo 5: flow sensitive typing needs to handle inner closures + # flow sensitive typing needs to handle inner closures # @todo 5: flow sensitive typing needs better handling of ||= on ivars # @todo 5: Translate to something flow sensitive typing understands # @todo 4: Should handle redefinition of types in simple contexts - # @todo 4: need boolish support for ? methods + # need boolish support for ? methods # @todo 4: should understand meaning of &. # @todo 4: Need support for reduce_class_type in UniqueType - # @todo 4: flow sensitive typing needs to handle 'return if' + # REFILE: flow sensitive typing needs to handle 'return if' # @todo 4: flow sensitive typing needs to handle || within && # @todo 3: Need to handle implicit nil on else # @todo 3: flow sensitive typing needs better handling of ||= on lvars diff --git a/lib/solargraph/yard_map/mapper.rb b/lib/solargraph/yard_map/mapper.rb index 69a6c1b33..203a40b5a 100644 --- a/lib/solargraph/yard_map/mapper.rb +++ b/lib/solargraph/yard_map/mapper.rb @@ -39,13 +39,17 @@ def generate_pins code_object nspin = ToNamespace.make(code_object, @spec, @namespace_pins[code_object.namespace.to_s]) @namespace_pins[code_object.path] = nspin result.push nspin + # @sg-ignore Need to support nested flow sensitive types if code_object.is_a?(YARD::CodeObjects::ClassObject) and !code_object.superclass.nil? # This method of superclass detection is a bit of a hack. If # the superclass is a Proxy, it is assumed to be undefined in its # yardoc and converted to a fully qualified namespace. + # @sg-ignore Need to support nested flow sensitive types superclass = if code_object.superclass.is_a?(YARD::CodeObjects::Proxy) + # @sg-ignore Need to support nested flow sensitive types "::#{code_object.superclass}" else + # @sg-ignore Need to support nested flow sensitive types code_object.superclass.to_s end result.push Solargraph::Pin::Reference::Superclass.new(name: superclass, closure: nspin, source: :yard_map) From 608112c6392d3238da259e34ac80d0ffea6d5f89 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 20 Sep 2025 10:20:10 -0400 Subject: [PATCH 347/930] Annotation accounting --- lib/solargraph/api_map/source_to_yard.rb | 2 +- .../convention/struct_definition.rb | 2 +- lib/solargraph/pin/callable.rb | 2 +- lib/solargraph/pin/parameter.rb | 6 ++-- lib/solargraph/source/change.rb | 2 +- lib/solargraph/source_map/mapper.rb | 2 +- lib/solargraph/type_checker/rules.rb | 31 +++++++++---------- 7 files changed, 23 insertions(+), 24 deletions(-) diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index b48b2d791..b435f7c36 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -52,8 +52,8 @@ def rake_yard store code_object_map[pin.path].docstring = pin.docstring store.get_includes(pin.path).each do |ref| include_object = code_object_at(pin.path, YARD::CodeObjects::ClassObject) + # @sg-ignore flow sensitive typing needs to handle || with variables unless include_object.nil? || include_object.nil? - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" include_object.instance_mixins.push code_object_map[ref.parametrized_tag.to_s] end end diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index 532b04d77..20d2e22e8 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -17,7 +17,7 @@ def process type: :class, location: loc, closure: region.closure, - # @sg-ignore flow sensitive typing needs to handle 'return if' + # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" name: struct_definition_node.class_name, docstring: docstring, visibility: :public, diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 540a940f8..c03962698 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -115,7 +115,7 @@ def resolve_generics_from_context(generics_to_resolve, param.dup else param.resolve_generics_from_context(generics_to_resolve, - # @sg-ignore flow sensitive typing needs to handle "else" + # @sg-ignore flow sensitive typing needs to handle "if .nil? else" arg_types[i], resolved_generic_values: resolved_generic_values) end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 55aa437a0..a70ad799d 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -152,7 +152,7 @@ def return_type if @return_type.nil? @return_type = ComplexType::UNDEFINED found = param_tag - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs to handle || with variables @return_type = ComplexType.try_parse(*found.types) unless found.nil? or found.types.nil? # @sg-ignore Need to add nil check here if @return_type.undefined? @@ -250,7 +250,7 @@ def typify_method_param api_map if found.nil? and !index.nil? found = params[index] if params[index] && (params[index].name.nil? || params[index].name.empty?) end - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs to handle || with variables return ComplexType.try_parse(*found.types).qualify(api_map, meth.context.namespace) unless found.nil? || found.types.nil? end ComplexType::UNDEFINED @@ -261,7 +261,7 @@ def typify_method_param api_map # @param skip [::Array] # # @return [::Array] - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore Translate to something flow sensitive typing understands def see_reference heredoc, api_map, skip = [] heredoc.ref_tags.each do |ref| next unless ref.tag_name == 'param' && ref.owner diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index c9d197092..e8496555b 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -60,7 +60,7 @@ def repair text fixed else result = commit text, fixed - # @sg-ignore flow sensitive typing needs to handle "else" + # @sg-ignore flow sensitive typing needs to handle "if .nil? else" off = Position.to_offset(text, range.start) # @sg-ignore Need to add nil check here match = result[0, off].match(/[.:]+\z/) diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 89ba70e44..9e4e25a9b 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -249,7 +249,7 @@ def remove_inline_comment_hashes comment started = true elsif started && !p.strip.empty? cur = p.index(/[^ ]/) - # @sg-ignore flow sensitive typing needs to handle "else" + # @sg-ignore flow sensitive typing needs to handle "if .nil? else" num = cur if cur < num end ctxt += "#{p[num..-1]}" if started diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index ab29029f5..9ecc09faa 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,34 +58,33 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # Need to support nested flow sensitive types - # Need to add nil check here - # REFILE: flow sensitive typing needs to handle "return if foo.nil?" - # downcast output of Enumerable#select - # flow sensitive typing needs to handle && - # REFILE: flow sensitive typing needs to handle "unless foo.nil?" - # @todo 13: flow sensitive typing needs to handle || with variables - # @todo 12: need to be able to resolve same method signature on two different types + # @todo 241: Need to add nil check here + # @todo 16: flow sensitive typing needs to handle || with variables + # @todo 10: flow sensitive typing needs to handle "return if foo.nil?" + # @todo 8: need to be able to resolve same method signature on two different types # @todo 9: Need to validate config # @todo 8: Should better support meaning of '&' in RBS # @todo 8: flow sensitive typing needs to handle "if !foo" # @todo 7: literal arrays in this module turn into ::Solargraph::Source::Chain::Array - # REFILE: flow sensitive typing needs to handle "else" + # @todo 6: flow sensitive typing needs to handle && # @todo 5: flow sensitive typing needs to handle ternary operator - # flow sensitive typing needs to handle inner closures + # @todo 5: flow sensitive typing needs to handle inner closures # @todo 5: flow sensitive typing needs better handling of ||= on ivars # @todo 5: Translate to something flow sensitive typing understands - # @todo 4: Should handle redefinition of types in simple contexts - # need boolish support for ? methods + # @todo 5: Should handle redefinition of types in simple contexts + # @todo 3: flow sensitive typing needs to handle "if .nil? else" + # @todo 5: need boolish support for ? methods # @todo 4: should understand meaning of &. # @todo 4: Need support for reduce_class_type in UniqueType - # REFILE: flow sensitive typing needs to handle 'return if' + # @todo 4: Need to support nested flow sensitive types # @todo 4: flow sensitive typing needs to handle || within && - # @todo 3: Need to handle implicit nil on else + # @todo 3: downcast output of Enumerable#select + # @todo 4: Need to handle implicit nil on else # @todo 3: flow sensitive typing needs better handling of ||= on lvars - # @todo 2: flow sensitive typing needs to handle if on ivars - # @todo 2: flow sensitive typing needs to handle "while foo"" + # @todo 2: flow sensitive typing needs to handle "while foo" # @todo 1: flow sensitive typing needs to handle && with ivars + # @todo 1: flow sensitive typing needs to handle if on ivars + # @todo 1: flow sensitive typing needs to handle "unless foo.nil?" # @todo 1: investigate why Parser::AST#type isn't available here # @todo 1: flow sensitive typing needs to handle && with unrelated calls # @todo 1: flow sensitive typing needs to handle 'raise if' From 311133c20175d61ca287d25d03970357801e3948 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 20 Sep 2025 10:20:22 -0400 Subject: [PATCH 348/930] Specs --- spec/parser/flow_sensitive_typing_spec.rb | 285 +++++++++++++++++++++- 1 file changed, 274 insertions(+), 11 deletions(-) diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 84f4c07ab..1cb5c940b 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -580,17 +580,280 @@ def bar(baz: nil) # https://cse.buffalo.edu/~regan/cse305/RubyBNF.pdf # https://ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/syntax.html - it 'uses .nil? in a return if() in a method to refine types using nil checks' - it 'uses .nil? in a return if() in a block to refine types using nil checks' - it 'uses .nil? in a return if() in an unless to refine types using nil checks' - it 'uses .nil? in a return if() in a while to refine types using nil checks' - it 'uses .nil? in a return if() in an until to refine types using nil checks' - it 'uses .nil? in a return if() in a switch/case/else to refine types using nil checks' - it 'uses .nil? in a return if() in a ternary operator to refine types using nil checks' - it 'uses .nil? in a return if() in a begin/end to refine types using nil checks' - it 'uses .nil? in a return if() in a ||= to refine types using nil checks' - it 'uses .nil? in a return if() in a try / rescue / ensure to refine types using nil checks' - it 'uses .nil? in a return if() in top level namespace to refine types using nil checks' + it 'uses .nil? in a return if() in a method to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + return if baz.nil? + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + end + + it 'uses .nil? in a return if() in a block to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @param arr [Array] + # @return [void] + def bar(arr, baz: nil) + baz + arr.each do |item| + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + pending ('better scoping of return if in blocks') + + clip = api_map.clip_at('test.rb', [9, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [11, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in an unless to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + unless rand + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + pending('better scoping of return if in unless') + + clip = api_map.clip_at('test.rb', [10, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in a while to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + while rand do + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [9, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in an until to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + until rand do + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [9, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in a switch/case/else to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + case rand + when 0..0.5 + return if baz.nil? + baz + else + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + pending('better scoping of return if in case/when') + + clip = api_map.clip_at('test.rb', [10, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [12, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in a ternary operator to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + rand > 0.5 ? (return if baz.nil?; baz) : baz + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [6, 44]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + pending('better scoping of return if in ternary operator') + + clip = api_map.clip_at('test.rb', [6, 51]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [7, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in a begin/end to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + begin + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [10, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + end + + it 'uses .nil? in a return if() in a ||= to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + baz ||= begin + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [10, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + end + + it 'uses .nil? in a return if() in a try / rescue / ensure to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + begin + return if baz.nil? + baz + rescue StandardError + baz + ensure + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [10, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + pending('better scoping of return if in begin/rescue/ensure') + + clip = api_map.clip_at('test.rb', [12, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [14, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end it 'provides a useful pin after a return if .nil?' do source = Solargraph::Source.load_string(%( From aa799f4d2664535d706d639c11bfc81acabc2ac5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 20 Sep 2025 10:27:37 -0400 Subject: [PATCH 349/930] Linting fixes --- .rubocop_todo.yml | 9 +++++++-- spec/parser/flow_sensitive_typing_spec.rb | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index acd8e0bcd..78d094550 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -464,6 +464,7 @@ Metrics/ClassLength: Exclude: - 'lib/solargraph/api_map.rb' - 'lib/solargraph/language_server/host.rb' + - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/rbs_map/conversions.rb' - 'lib/solargraph/type_checker.rb' @@ -486,7 +487,6 @@ Metrics/ParameterLists: - 'lib/solargraph/pin/callable.rb' - 'lib/solargraph/type_checker.rb' - 'lib/solargraph/yard_map/mapper/to_method.rb' - - 'lib/solargraph/yard_map/to_method.rb' # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/PerceivedComplexity: @@ -1148,7 +1148,12 @@ Style/SlicingWithRange: # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowModifier. Style/SoleNestedConditional: - Enabled: false + Exclude: + - 'lib/solargraph/complex_type/unique_type.rb' + - 'lib/solargraph/pin/parameter.rb' + - 'lib/solargraph/source.rb' + - 'lib/solargraph/source/source_chainer.rb' + - 'lib/solargraph/type_checker.rb' # This cop supports safe autocorrection (--autocorrect). Style/StderrPuts: diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 1cb5c940b..70eaf112a 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -618,7 +618,7 @@ def bar(arr, baz: nil) clip = api_map.clip_at('test.rb', [6, 10]) expect(clip.infer.rooted_tags).to eq('::Boolean, nil') - pending ('better scoping of return if in blocks') + pending('better scoping of return if in blocks') clip = api_map.clip_at('test.rb', [9, 12]) expect(clip.infer.rooted_tags).to eq('::Boolean') From 31544a6a9207101bb499cfc52a2c1fe8cfa44dac Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 20 Sep 2025 10:31:01 -0400 Subject: [PATCH 350/930] Fix annotation --- lib/solargraph/api_map/source_to_yard.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index b435f7c36..64a9478f1 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -52,8 +52,8 @@ def rake_yard store code_object_map[pin.path].docstring = pin.docstring store.get_includes(pin.path).each do |ref| include_object = code_object_at(pin.path, YARD::CodeObjects::ClassObject) - # @sg-ignore flow sensitive typing needs to handle || with variables unless include_object.nil? || include_object.nil? + # @sg-ignore flow sensitive typing needs to handle || with variables include_object.instance_mixins.push code_object_map[ref.parametrized_tag.to_s] end end From cc9fdefc9af23f313905e757770eaa20937cdc7e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 20 Sep 2025 10:45:42 -0400 Subject: [PATCH 351/930] Add || to flow sensitive typing --- lib/solargraph/convention/data_definition.rb | 1 - .../parser/flow_sensitive_typing.rb | 30 +++++++++++++++++++ .../parser/parser_gem/node_processors.rb | 2 ++ .../parser_gem/node_processors/or_node.rb | 28 +++++++++++++++++ lib/solargraph/pin/parameter.rb | 1 - lib/solargraph/rbs_map.rb | 1 - spec/parser/flow_sensitive_typing_spec.rb | 20 +++++++++++++ 7 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 lib/solargraph/parser/parser_gem/node_processors/or_node.rb diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index 26f725641..f06346d61 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -96,7 +96,6 @@ def data_definition_node # @return [String, nil] def attribute_comments(attribute_node, attribute_name) data_comments = comments_for(attribute_node) - # @sg-ignore flow sensitive typing needs to handle || with variables return if data_comments.nil? || data_comments.empty? # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index dfb0af8b4..eb92f954c 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -38,6 +38,35 @@ def process_and(and_node, true_ranges = [], false_ranges = []) process_conditional(rhs, true_ranges, []) end + # @param or_node [Parser::AST::Node] + # @param true_ranges [Array] + # @param false_ranges [Array] + # + # @return [void] + def process_or(or_node, true_ranges = [], false_ranges = []) + return unless or_node.type == :or + + # @type [Parser::AST::Node] + lhs = or_node.children[0] + # @type [Parser::AST::Node] + rhs = or_node.children[1] + + before_rhs_loc = rhs.location.expression.adjust(begin_pos: -1) + before_rhs_pos = Position.new(before_rhs_loc.line, before_rhs_loc.column) + + rhs_presence = Range.new(before_rhs_pos, + get_node_end_position(rhs)) + + # can assume if an or is false that every single condition is + # false, so't provide false ranges to assert facts on + + # can't assume if an or is true that every single condition is + # true, so don't provide true ranges to assert facts on + + process_conditional(lhs, [], true_ranges + [rhs_presence]) + process_conditional(rhs, [], true_ranges) + end + # @param node [Parser::AST::Node] # @param true_presences [Array] # @param false_presences [Array] @@ -237,6 +266,7 @@ def process_facts(facts_by_pin, presences) def process_conditional(conditional_node, true_ranges, false_ranges) process_calls(conditional_node, true_ranges, false_ranges) process_and(conditional_node, true_ranges, false_ranges) + process_or(conditional_node, true_ranges, false_ranges) process_variable(conditional_node, true_ranges, false_ranges) end diff --git a/lib/solargraph/parser/parser_gem/node_processors.rb b/lib/solargraph/parser/parser_gem/node_processors.rb index e2cb828da..9e78c5e9a 100644 --- a/lib/solargraph/parser/parser_gem/node_processors.rb +++ b/lib/solargraph/parser/parser_gem/node_processors.rb @@ -29,6 +29,7 @@ module NodeProcessors autoload :UntilNode, 'solargraph/parser/parser_gem/node_processors/until_node' autoload :WhileNode, 'solargraph/parser/parser_gem/node_processors/while_node' autoload :AndNode, 'solargraph/parser/parser_gem/node_processors/and_node' + autoload :OrNode, 'solargraph/parser/parser_gem/node_processors/or_node' end end @@ -65,6 +66,7 @@ module NodeProcessor register :until, ParserGem::NodeProcessors::UntilNode register :while, ParserGem::NodeProcessors::WhileNode register :and, ParserGem::NodeProcessors::AndNode + register :or, ParserGem::NodeProcessors::OrNode end end end diff --git a/lib/solargraph/parser/parser_gem/node_processors/or_node.rb b/lib/solargraph/parser/parser_gem/node_processors/or_node.rb new file mode 100644 index 000000000..a4a8ad232 --- /dev/null +++ b/lib/solargraph/parser/parser_gem/node_processors/or_node.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Solargraph + module Parser + module ParserGem + module NodeProcessors + class OrNode < Parser::NodeProcessor::Base + include ParserGem::NodeMethods + + 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 + # @sg-ignore downcast output of Enumerable#select + # @type [Pin::CompoundStatementable] + enclosing_compound_statement_pin = pins.select{|pin| pin.is_a?(Solargraph::Pin::CompoundStatementable) && pin.location.range.contain?(position)}.last + FlowSensitiveTyping.new(locals, + enclosing_breakable_pin, + enclosing_compound_statement_pin).process_or(node) + end + end + end + end + end +end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index a70ad799d..04c503879 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -203,7 +203,6 @@ def compatible_arg?(atype, api_map) def documentation tag = param_tag - # @sg-ignore flow sensitive typing needs to handle || with variables return '' if tag.nil? || tag.text.nil? # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" tag.text diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index 4939ec19a..40e6fed0b 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -58,7 +58,6 @@ def cache_key data = gem_config&.to_s end end - # @sg-ignore flow sensitive typing needs to handle || with variables if data.nil? || data.empty? if resolved? # definitely came from the gem itself and not elsewhere - diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 70eaf112a..351afb5b9 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -459,6 +459,26 @@ def verify_repro(repr, throw_the_dice) expect(clip.infer.rooted_tags).to eq('nil') end + it 'uses .nil? and or in an unless' do + source = Solargraph::Source.load_string(%( + # @param repr [String, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr) + repr unless repr.nil? || repr.downcase + repr + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 33]) + expect(clip.infer.rooted_tags).to eq('::String') + + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::String, nil') + + clip = api_map.clip_at('test.rb', [5, 8]) + expect(clip.infer.rooted_tags).to eq('::String, nil') + end + it 'uses varname and && in a simple if() - varname first' do source = Solargraph::Source.load_string(%( # @param repr [Integer, nil] From 2be9a1ac65ce15335f1b234bf078821d0318779b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 20 Sep 2025 10:55:57 -0400 Subject: [PATCH 352/930] Fix specs --- lib/solargraph/parser/flow_sensitive_typing.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index eb92f954c..ae3d46455 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -63,8 +63,8 @@ def process_or(or_node, true_ranges = [], false_ranges = []) # can't assume if an or is true that every single condition is # true, so don't provide true ranges to assert facts on - process_conditional(lhs, [], true_ranges + [rhs_presence]) - process_conditional(rhs, [], true_ranges) + process_conditional(lhs, [], [rhs_presence]) + process_conditional(rhs, [], []) end # @param node [Parser::AST::Node] From 1343432717dd8ee32fd15eabc2a4470cc1ef3716 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 20 Sep 2025 10:59:52 -0400 Subject: [PATCH 353/930] Annotation accounting --- lib/solargraph.rb | 2 +- lib/solargraph/api_map/source_to_yard.rb | 14 +++++++------- lib/solargraph/convention/struct_definition.rb | 2 +- lib/solargraph/library.rb | 4 ++-- lib/solargraph/pin/local_variable.rb | 2 +- lib/solargraph/pin/parameter.rb | 4 ++-- lib/solargraph/source_map/data.rb | 4 ++-- lib/solargraph/type_checker/rules.rb | 11 +++++------ 8 files changed, 21 insertions(+), 22 deletions(-) diff --git a/lib/solargraph.rb b/lib/solargraph.rb index e76891080..2a313c5c1 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -57,7 +57,7 @@ class InvalidRubocopVersionError < RuntimeError; end # @param type [Symbol] Type of assert. def self.asserts_on?(type) - # @sg-ignore flow sensitive typing needs to handle || with variables + # @sg-ignore Translate to something flow sensitive typing understands if ENV['SOLARGRAPH_ASSERTS'].nil? || ENV['SOLARGRAPH_ASSERTS'].empty? false elsif ENV['SOLARGRAPH_ASSERTS'] == 'on' diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index 64a9478f1..b7858a964 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -36,16 +36,16 @@ def rake_yard store end if pin.type == :class code_object_map[pin.path] ||= YARD::CodeObjects::ClassObject.new(root_code_object, pin.path) { |obj| - # @sg-ignore flow sensitive typing needs to handle || with variables + # @sg-ignore Translate to something flow sensitive typing understands next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore flow sensitive typing needs to handle || with variables + # @sg-ignore Translate to something flow sensitive typing understands obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) } else code_object_map[pin.path] ||= YARD::CodeObjects::ModuleObject.new(root_code_object, pin.path) { |obj| - # @sg-ignore flow sensitive typing needs to handle || with variables + # @sg-ignore Translate to something flow sensitive typing understands next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore flow sensitive typing needs better handling of ||= on lvars + # @sg-ignore Translate to something flow sensitive typing understands obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) } end @@ -53,7 +53,7 @@ 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? - # @sg-ignore flow sensitive typing needs to handle || with variables + # @sg-ignore Translate to something flow sensitive typing understands include_object.instance_mixins.push code_object_map[ref.parametrized_tag.to_s] end end @@ -74,9 +74,9 @@ def rake_yard store # @sg-ignore Need to add nil check here code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope) { |obj| - # @sg-ignore flow sensitive typing needs to handle || with variables + # @sg-ignore Translate to something flow sensitive typing understands next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore flow sensitive typing needs to handle || with variables + # @sg-ignore Translate to something flow sensitive typing understands obj.add_file pin.location.filename, pin.location.range.start.line } method_object = code_object_at(pin.path, YARD::CodeObjects::MethodObject) diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index 20d2e22e8..ec38f8739 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -142,7 +142,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 || with variables + # @sg-ignore should understand meaning of &. # @return [String] def tag_string(tag) tag&.types&.join(',') || 'undefined' diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index f5b7b7af1..6ad65241a 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -87,7 +87,7 @@ def attached? filename # @param filename [String] # @return [Boolean] True if the specified file was detached def detach filename - # @sg-ignore flow sensitive typing needs to handle || with variables + # @sg-ignore flow sensitive typing needs to handle && with ivars return false if @current.nil? || @current.filename != filename attach nil true @@ -194,7 +194,7 @@ def definitions_at filename, line, column # @sg-ignore Need to add nil check here rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i) if lft && rgt - # @sg-ignore flow sensitive typing needs to handle && + # @sg-ignore flow sensitive typing needs to handle && on both sides tag = (lft[1] + rgt[1]).sub(/:+$/, '') clip = mutex.synchronize { api_map.clip(cursor) } clip.translate tag diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 687cd7898..81bbb2254 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -73,7 +73,7 @@ def return_type def return_type_minus_exclusions(raw_return_type) @return_type_minus_exclusions ||= if exclude_return_type && raw_return_type - # @sg-ignore flow sensitive typing needs to handle && + # @sg-ignore flow sensitive typing needs to handle && on both sides types = raw_return_type.items - exclude_return_type.items types = [ComplexType::UniqueType::UNDEFINED] if types.empty? ComplexType.new(types) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 04c503879..fd6de04e0 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -152,7 +152,7 @@ def return_type if @return_type.nil? @return_type = ComplexType::UNDEFINED found = param_tag - # @sg-ignore flow sensitive typing needs to handle || with variables + # @sg-ignore Translate to something flow sensitive typing understands @return_type = ComplexType.try_parse(*found.types) unless found.nil? or found.types.nil? # @sg-ignore Need to add nil check here if @return_type.undefined? @@ -249,7 +249,7 @@ def typify_method_param api_map if found.nil? and !index.nil? found = params[index] if params[index] && (params[index].name.nil? || params[index].name.empty?) end - # @sg-ignore flow sensitive typing needs to handle || with variables + # @sg-ignore Translate to something flow sensitive typing understands return ComplexType.try_parse(*found.types).qualify(api_map, meth.context.namespace) unless found.nil? || found.types.nil? end ComplexType::UNDEFINED diff --git a/lib/solargraph/source_map/data.rb b/lib/solargraph/source_map/data.rb index 1d00fda3f..b563acc5f 100644 --- a/lib/solargraph/source_map/data.rb +++ b/lib/solargraph/source_map/data.rb @@ -12,7 +12,7 @@ def initialize source @locals = nil end - # @sg-ignore flow sensitive typing needs to handle || with variables + # @sg-ignore flow sensitive typing needs to handle && with ivars # @return [Array] def pins generate @@ -21,7 +21,7 @@ def pins @pins || empty_pins end - # @sg-ignore flow sensitive typing needs to handle || with variables + # @sg-ignore flow sensitive typing needs to handle && with ivars # @return [Array] def locals generate diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 9ecc09faa..3e5f073a4 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -59,30 +59,29 @@ def require_inferred_type_params? end # @todo 241: Need to add nil check here - # @todo 16: flow sensitive typing needs to handle || with variables + # @todo 15: Translate to something flow sensitive typing understands # @todo 10: flow sensitive typing needs to handle "return if foo.nil?" # @todo 8: need to be able to resolve same method signature on two different types # @todo 9: Need to validate config # @todo 8: Should better support meaning of '&' in RBS # @todo 8: flow sensitive typing needs to handle "if !foo" # @todo 7: literal arrays in this module turn into ::Solargraph::Source::Chain::Array - # @todo 6: flow sensitive typing needs to handle && + # @todo 5: should understand meaning of &. # @todo 5: flow sensitive typing needs to handle ternary operator # @todo 5: flow sensitive typing needs to handle inner closures # @todo 5: flow sensitive typing needs better handling of ||= on ivars - # @todo 5: Translate to something flow sensitive typing understands # @todo 5: Should handle redefinition of types in simple contexts # @todo 3: flow sensitive typing needs to handle "if .nil? else" # @todo 5: need boolish support for ? methods - # @todo 4: should understand meaning of &. # @todo 4: Need support for reduce_class_type in UniqueType # @todo 4: Need to support nested flow sensitive types # @todo 4: flow sensitive typing needs to handle || within && - # @todo 3: downcast output of Enumerable#select + # @todo 4: flow sensitive typing needs to handle && with ivars # @todo 4: Need to handle implicit nil on else + # @todo 3: downcast output of Enumerable#select # @todo 3: flow sensitive typing needs better handling of ||= on lvars + # @todo 2: flow sensitive typing needs to handle && on both sides # @todo 2: flow sensitive typing needs to handle "while foo" - # @todo 1: flow sensitive typing needs to handle && with ivars # @todo 1: flow sensitive typing needs to handle if on ivars # @todo 1: flow sensitive typing needs to handle "unless foo.nil?" # @todo 1: investigate why Parser::AST#type isn't available here From d43196eb91c82c5f3286ac924f55558f4c978859 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 20 Sep 2025 14:11:41 -0400 Subject: [PATCH 354/930] More type accounting --- lib/solargraph/api_map.rb | 2 +- lib/solargraph/convention/data_definition.rb | 8 ++--- .../convention/struct_definition.rb | 8 ++--- lib/solargraph/library.rb | 15 +++++----- lib/solargraph/pin/base.rb | 6 ++-- lib/solargraph/pin/callable.rb | 2 +- lib/solargraph/pin/closure.rb | 4 +-- lib/solargraph/pin/local_variable.rb | 2 +- lib/solargraph/pin/method.rb | 4 +-- lib/solargraph/pin/parameter.rb | 4 +-- lib/solargraph/pin/signature.rb | 2 +- lib/solargraph/position.rb | 1 + lib/solargraph/source/chain/call.rb | 5 ++-- lib/solargraph/source/chain/literal.rb | 2 +- lib/solargraph/source/change.rb | 2 +- lib/solargraph/source/cursor.rb | 2 +- lib/solargraph/source/source_chainer.rb | 3 +- lib/solargraph/source_map/data.rb | 4 +-- lib/solargraph/source_map/mapper.rb | 6 ++-- lib/solargraph/type_checker.rb | 6 ++-- lib/solargraph/type_checker/rules.rb | 29 +++++++------------ spec/parser/flow_sensitive_typing_spec.rb | 16 ++++++++++ 22 files changed, 70 insertions(+), 63 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index f935a03f7..1ac36ef88 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -657,7 +657,7 @@ def super_and_sub?(sup, sub) # Cyclical inheritance is invalid return false if sc_new == sc_fqns sc_fqns = sc_new - # @sg-ignore need to be able to resolve same method signature on two different types + # @sg-ignore Should handle redefinition of types in simple contexts return true if sc_fqns == sup end false diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index f06346d61..315018b08 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -17,7 +17,7 @@ def process type: :class, location: loc, closure: region.closure, - # @sg-ignore need to be able to resolve same method signature on two different types + # @sg-ignore flow sensitive typing needs to handle ivars name: data_definition_node.class_name, comments: comments_for(node), visibility: :public, @@ -40,7 +40,7 @@ def process # Solargraph::SourceMap::Clip#complete_keyword_parameters does not seem to currently take into account [Pin::Method#signatures] hence we only one for :kwarg pins.push initialize_method_pin - # @sg-ignore need to be able to resolve same method signature on two different types + # @sg-ignore flow sensitive typing needs to handle ivars data_definition_node.attributes.map do |attribute_node, attribute_name| initialize_method_pin.parameters.push( Pin::Parameter.new( @@ -53,7 +53,7 @@ def process end # define attribute readers and instance variables - # @sg-ignore need to be able to resolve same method signature on two different types + # @sg-ignore flow sensitive typing needs to handle ivars data_definition_node.attributes.each do |attribute_node, attribute_name| name = attribute_name.to_s method_pin = Pin::Method.new( @@ -98,7 +98,7 @@ def attribute_comments(attribute_node, attribute_name) data_comments = comments_for(attribute_node) return if data_comments.nil? || data_comments.empty? - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" + # @sg-ignore Translate to something flow sensitive typing understands data_comments.split("\n").find do |row| row.include?(attribute_name) end&.gsub('@param', '@return')&.gsub(attribute_name, '') diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index ec38f8739..18f13f3ed 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -17,7 +17,7 @@ def process type: :class, location: loc, closure: region.closure, - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle ivars name: struct_definition_node.class_name, docstring: docstring, visibility: :public, @@ -40,7 +40,7 @@ def process pins.push initialize_method_pin - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle ivars struct_definition_node.attributes.map do |attribute_node, attribute_name| initialize_method_pin.parameters.push( Pin::Parameter.new( @@ -54,7 +54,7 @@ def process end # define attribute accessors and instance variables - # @sg-ignore need to be able to resolve same method signature on two different types + # @sg-ignore flow sensitive typing needs to handle ivars struct_definition_node.attributes.each do |attribute_node, attribute_name| [attribute_name, "#{attribute_name}="].each do |name| docs = docstring.tags.find { |t| t.tag_name == 'param' && t.name == attribute_name } @@ -125,7 +125,7 @@ def docstring # @return [YARD::Docstring] def parse_comments struct_comments = comments_for(node) || '' - # @sg-ignore need to be able to resolve same method signature on two different types + # @sg-ignore Need to add nil check here struct_definition_node.attributes.each do |attr_node, attr_name| comment = comments_for(attr_node) next if comment.nil? diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 6ad65241a..b14a3ba1b 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -87,7 +87,7 @@ def attached? filename # @param filename [String] # @return [Boolean] True if the specified file was detached def detach filename - # @sg-ignore flow sensitive typing needs to handle && with ivars + # @sg-ignore flow sensitive typing needs to handle ivars return false if @current.nil? || @current.filename != filename attach nil true @@ -194,7 +194,6 @@ def definitions_at filename, line, column # @sg-ignore Need to add nil check here rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i) if lft && rgt - # @sg-ignore flow sensitive typing needs to handle && on both sides tag = (lft[1] + rgt[1]).sub(/:+$/, '') clip = mutex.synchronize { api_map.clip(cursor) } clip.translate tag @@ -447,7 +446,7 @@ def bench source_maps: source_map_hash.values, workspace: workspace, external_requires: external_requires, - # @sg-ignore flow sensitive typing needs to handle ternary operator + # @sg-ignore flow sensitive typing needs to handle ivars live_map: @current ? source_map_hash[@current.filename] : nil ) end @@ -557,7 +556,7 @@ def api_map # @sg-ignore flow sensitive typing needs to handle if foo && ... # @return [Solargraph::Source] def read filename - # @sg-ignore flow sensitive typing needs to handle && with ivars + # @sg-ignore flow sensitive typing needs to handle ivars return @current if @current && @current.filename == filename raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename) workspace.source(filename) @@ -650,19 +649,19 @@ def queued_gemspec_cache def report_cache_progress gem_name, pending @total ||= pending @total = pending if pending > @total - # @sg-ignore flow sensitive typing needs better handling of ||= on ivars + # @sg-ignore flow sensitive typing needs to handle ivars finished = @total - pending - # @sg-ignore flow sensitive typing needs better handling of ||= on ivars + # @sg-ignore flow sensitive typing needs to handle ivars pct = if @total.zero? 0 else - # @sg-ignore flow sensitive typing needs better handling of ||= on ivars + # @sg-ignore flow sensitive typing needs to handle ivars ((finished.to_f / @total.to_f) * 100).to_i end message = "#{gem_name}#{pending > 0 ? " (+#{pending})" : ''}" # " if @cache_progress - # @sg-ignore oflow sensitive typing needs to handle if on ivars + # @sg-ignore flow sensitive typing needs to handle ivars @cache_progress.report(message, pct) else @cache_progress = LanguageServer::Progress.new('Caching gem') diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index d8faee795..c416491a5 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -426,7 +426,7 @@ def erase_generics(generics_to_erase) # @return [String, nil] def filename return nil if location.nil? - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" + # @sg-ignore flow sensitive typing needs to handle ivars location.filename end @@ -463,7 +463,7 @@ def best_location def nearly? other self.class == other.class && name == other.name && - # @sg-ignore flow sensitive typing needs to handle || within && + # @sg-ignore flow sensitive typing needs to handle ivars (closure == other.closure || (closure && closure.nearly?(other.closure))) && (comments == other.comments || (((maybe_directives? == false && other.maybe_directives? == false) || compare_directives(directives, other.directives)) && @@ -514,7 +514,7 @@ def macros # # @return [Boolean] def maybe_directives? - # @sg-ignore flow sensitive typing needs to handle && with unrelated calls + # @sg-ignore flow sensitive typing needs to handle && on both sides return !@directives.empty? if defined?(@directives) && @directives @maybe_directives ||= comments.include?('@!') end diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index c03962698..bbe660b51 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -115,7 +115,7 @@ def resolve_generics_from_context(generics_to_resolve, param.dup else param.resolve_generics_from_context(generics_to_resolve, - # @sg-ignore flow sensitive typing needs to handle "if .nil? else" + # @sg-ignore flow sensitive typing needs to handle inner closures arg_types[i], resolved_generic_values: resolved_generic_values) end diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index 4d030fde0..706a1c0b8 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -54,11 +54,11 @@ def binder def gates # @todo This check might not be necessary. There should always be a # root pin - # @sg-ignore flow sensitive typing needs to handle ternary operator + # @sg-ignore flow sensitive typing needs to handle ivars closure ? closure.gates : [''] end - # @sg-ignore flow sensitive typing needs better handling of ||= on ivars + # @sg-ignore flow sensitive typing needs to handle ivars # @return [::Array] def generics @generics ||= docstring.tags(:generic).map(&:name) diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 81bbb2254..15b656dfd 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -73,7 +73,7 @@ def return_type def return_type_minus_exclusions(raw_return_type) @return_type_minus_exclusions ||= if exclude_return_type && raw_return_type - # @sg-ignore flow sensitive typing needs to handle && on both sides + # @sg-ignore flow sensitive typing needs to handle ivars types = raw_return_type.items - exclude_return_type.items types = [ComplexType::UniqueType::UNDEFINED] if types.empty? ComplexType.new(types) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 90c792dc2..f90a62027 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -203,7 +203,7 @@ def generate_signature(parameters, return_type) comments: p.text, name: name, decl: decl, - # @sg-ignore flow sensitive typing needs to handle ternary operator + # @sg-ignore flow sensitive typing needs to handle ivars presence: location ? location.range : nil, return_type: ComplexType.try_parse(*p.types), source: source @@ -414,7 +414,7 @@ def overloads comments: tag.docstring.all.to_s, name: name, decl: decl, - # @sg-ignore flow sensitive typing needs to handle ternary operator + # @sg-ignore flow sensitive typing needs to handle ivars presence: location ? location.range : nil, return_type: param_type_from_name(tag, src.first), source: :overloads diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index fd6de04e0..8401a6b72 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -45,7 +45,7 @@ def keyword? end def kwrestarg? - # @sg-ignore flow sensitive typing needs to handle || within && + # @sg-ignore flow sensitive typing needs to handle ivars decl == :kwrestarg || (assignment && [:HASH, :hash].include?(assignment.type)) end @@ -204,7 +204,7 @@ def compatible_arg?(atype, api_map) def documentation tag = param_tag return '' if tag.nil? || tag.text.nil? - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" + # @sg-ignore Translate to something flow sensitive typing understands tag.text end diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 05aa6a66b..63b2e4001 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -9,7 +9,7 @@ def initialize **splat super(**splat) end - # @sg-ignore flow sensitive typing needs better handling of ||= on ivars + # @sg-ignore flow sensitive typing needs to handle ivars def generics # @type [Array<::String, nil>] @generics ||= [].freeze diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 45d404ba4..53c7b61ba 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -79,6 +79,7 @@ def self.line_char_to_offset text, line, character def self.from_offset text, offset cursor = 0 line = 0 + # @type [Integer, nil] character = nil text.lines.each do |l| line_length = l.length diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index bdd2bc3a8..1da9357ac 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -290,8 +290,7 @@ def super_pins api_map, name_pin def yield_pins api_map, name_pin method_pin = find_method_pin(name_pin) return [] unless method_pin - - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?"" + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" method_pin.signatures.map(&:block).compact.map do |signature_pin| return_type = signature_pin.return_type.qualify(api_map, name_pin.namespace) signature_pin.proxy(return_type) @@ -341,7 +340,7 @@ def find_block_pin(api_map) node_location = Solargraph::Location.from_node(block.node) return if node_location.nil? block_pins = api_map.get_block_pins - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" + # @sg-ignore flow sensitive typing needs to handle inner closures block_pins.find { |pin| pin.location.contain?(node_location) } end diff --git a/lib/solargraph/source/chain/literal.rb b/lib/solargraph/source/chain/literal.rb index 3e4c3bd54..bf197386d 100644 --- a/lib/solargraph/source/chain/literal.rb +++ b/lib/solargraph/source/chain/literal.rb @@ -25,7 +25,7 @@ def initialize type, node end end @type = type - # @sg-ignore need to be able to resolve same method signature on two different types + # @sg-ignore Translate to something flow sensitive typing understands @literal_type = ComplexType.try_parse(@value.inspect) @complex_type = ComplexType.try_parse(type) end diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index e8496555b..0a3a38b6c 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -60,7 +60,7 @@ def repair text fixed else result = commit text, fixed - # @sg-ignore flow sensitive typing needs to handle "if .nil? else" + # @sg-ignore flow sensitive typing needs to handle ivars off = Position.to_offset(text, range.start) # @sg-ignore Need to add nil check here match = result[0, off].match(/[.:]+\z/) diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 48848d5c2..db6772595 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -113,7 +113,7 @@ def string? def recipient @recipient ||= begin node = recipient_node - # @sg-ignore flow sensitive typing needs to handle ternary operator + # @sg-ignore Need to add nil check here node ? Cursor.new(source, Range.from_node(node).ending) : nil end end diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index 8ffe9e63c..fa0cd7e4a 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -38,7 +38,9 @@ def chain return SourceChainer.chain(source, Position.new(position.line, position.character + 1)) if end_of_phrase.strip == '::' && source.code[Position.to_offset(source.code, position)].to_s.match?(/[a-z]/i) begin return Chain.new([]) if phrase.end_with?('..') + # @type [::Parser::AST::Node, nil] node = nil + # @type [::Parser::AST::Node, nil] parent = nil if !source.repaired? && source.parsed? && source.synchronized? tree = source.tree_at(position.line, position.column) @@ -57,7 +59,6 @@ def chain rescue Parser::SyntaxError return Chain.new([Chain::UNDEFINED_CALL]) end - # @sg-ignore flow sensitive typing needs to handle || within && return Chain.new([Chain::UNDEFINED_CALL]) if node.nil? || (node.type == :sym && !phrase.start_with?(':')) # chain = NodeChainer.chain(node, source.filename, parent && parent.type == :block) chain = Parser.chain(node, source.filename, parent) diff --git a/lib/solargraph/source_map/data.rb b/lib/solargraph/source_map/data.rb index b563acc5f..f96d2c6b2 100644 --- a/lib/solargraph/source_map/data.rb +++ b/lib/solargraph/source_map/data.rb @@ -12,7 +12,7 @@ def initialize source @locals = nil end - # @sg-ignore flow sensitive typing needs to handle && with ivars + # @sg-ignore flow sensitive typing needs to handle ivars # @return [Array] def pins generate @@ -21,7 +21,7 @@ def pins @pins || empty_pins end - # @sg-ignore flow sensitive typing needs to handle && with ivars + # @sg-ignore flow sensitive typing needs to handle ivars # @return [Array] def locals generate diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 9e4e25a9b..e5d72dd97 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -136,8 +136,8 @@ def process_directive source_position, comment_position, directive when 'attribute' return if directive.tag.name.nil? namespace = closure_at(source_position) + # @type [String, nil] t = (directive.tag.types.nil? || directive.tag.types.empty?) ? nil : directive.tag.types.flatten.join('') - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" if t.nil? || t.include?('r') pins.push Solargraph::Pin::Method.new( location: location, @@ -151,7 +151,6 @@ def process_directive source_position, comment_position, directive source: :source_map ) end - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" if t.nil? || t.include?('w') method_pin = Solargraph::Pin::Method.new( location: location, @@ -239,6 +238,7 @@ def no_empty_lines?(line1, line2) # @return [String] def remove_inline_comment_hashes comment ctxt = '' + # @type [Integer, nil] num = nil started = false comment.lines.each { |l| @@ -249,7 +249,7 @@ def remove_inline_comment_hashes comment started = true elsif started && !p.strip.empty? cur = p.index(/[^ ]/) - # @sg-ignore flow sensitive typing needs to handle "if .nil? else" + # @sg-ignore Need to add nil check here num = cur if cur < num end ctxt += "#{p[num..-1]}" if started diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index c760c9c74..218243d37 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -606,7 +606,7 @@ def first_param_hash(pins) # @param pin [Pin::Base] def internal? pin return false if pin.nil? - # @sg-ignore flow sensitive typing needs to handle "return if foo.nil?" + # @sg-ignore Translate to something flow sensitive typing understands pin.location && api_map.bundled?(pin.location.filename) end @@ -754,7 +754,7 @@ def optional_param_count(parameters) # @sg-ignore need boolish support for ? methods def abstract? pin pin.docstring.has_tag?('abstract') || - # @sg-ignore flow sensitive typing needs to handle || within && + # @sg-ignore flow sensitive typing needs to handle ivars (pin.closure && pin.closure.docstring.has_tag?('abstract')) end @@ -815,7 +815,7 @@ def without_ignored problems problems.reject do |problem| node = source.node_at(problem.location.range.start.line, problem.location.range.start.column) ignored = node && source.comments_for(node)&.include?('@sg-ignore') - # @sg-ignore need to be able to resolve same method signature on two different types + # @sg-ignore flow sensitive typing needs to handle "if !foo" 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}" } diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 3e5f073a4..16cfaf5aa 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,35 +58,26 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo 241: Need to add nil check here - # @todo 15: Translate to something flow sensitive typing understands - # @todo 10: flow sensitive typing needs to handle "return if foo.nil?" - # @todo 8: need to be able to resolve same method signature on two different types + # @todo 244: Need to add nil check here + # @todo 26: flow sensitive typing needs to handle ivars + # @todo 19: Translate to something flow sensitive typing understands + # @todo 12: flow sensitive typing needs to handle "if !foo" # @todo 9: Need to validate config # @todo 8: Should better support meaning of '&' in RBS - # @todo 8: flow sensitive typing needs to handle "if !foo" # @todo 7: literal arrays in this module turn into ::Solargraph::Source::Chain::Array + # @todo 7: flow sensitive typing needs to handle inner closures # @todo 5: should understand meaning of &. - # @todo 5: flow sensitive typing needs to handle ternary operator - # @todo 5: flow sensitive typing needs to handle inner closures - # @todo 5: flow sensitive typing needs better handling of ||= on ivars # @todo 5: Should handle redefinition of types in simple contexts - # @todo 3: flow sensitive typing needs to handle "if .nil? else" # @todo 5: need boolish support for ? methods # @todo 4: Need support for reduce_class_type in UniqueType # @todo 4: Need to support nested flow sensitive types - # @todo 4: flow sensitive typing needs to handle || within && - # @todo 4: flow sensitive typing needs to handle && with ivars # @todo 4: Need to handle implicit nil on else # @todo 3: downcast output of Enumerable#select - # @todo 3: flow sensitive typing needs better handling of ||= on lvars - # @todo 2: flow sensitive typing needs to handle && on both sides - # @todo 2: flow sensitive typing needs to handle "while foo" - # @todo 1: flow sensitive typing needs to handle if on ivars - # @todo 1: flow sensitive typing needs to handle "unless foo.nil?" - # @todo 1: investigate why Parser::AST#type isn't available here - # @todo 1: flow sensitive typing needs to handle && with unrelated calls - # @todo 1: flow sensitive typing needs to handle 'raise if' + # @todo 3: EASY: flow sensitive typing needs better handling of ||= on lvars + # @todo 2: EASY: flow sensitive typing needs to handle "while foo" + # @todo 2: EASY: flow sensitive typing needs to handle "unless foo.nil?" + # @todo 1: EASY: flow sensitive typing needs to handle && on both sides + # @todo 1: EASY: flow sensitive typing needs to handle 'raise if' # @todo 1: To make JSON strongly typed we'll need a record syntax # @todo 1: Untyped method Solargraph::Pin::Base#assert_same could not be inferred # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 351afb5b9..374609f62 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -900,4 +900,20 @@ def a b clip = api_map.clip_at('test.rb', [8, 10]) expect(clip.infer.to_s).to eq('String') end + + it 'uses ! to detect nilness' do + source = Solargraph::Source.load_string(%( + class ReproBase; end + class Repro < ReproBase; end + # @type [ReproBase] + value = bar + while !is_done() + break unless value.is_a? Repro + value + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 8]) + expect(clip.infer.to_s).to eq('Repro') + end end From 6fd2c4e15f7c821b92f979ef92a372d500bf117b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 20 Sep 2025 15:01:09 -0400 Subject: [PATCH 355/930] Add ! support to flow-sensitive typing --- lib/solargraph/doc_map.rb | 1 - lib/solargraph/library.rb | 13 ++-- .../parser/flow_sensitive_typing.rb | 61 ++++++++++++++----- lib/solargraph/source.rb | 4 +- lib/solargraph/source/change.rb | 4 +- lib/solargraph/type_checker.rb | 8 +-- lib/solargraph/type_checker/rules.rb | 12 ++-- spec/parser/flow_sensitive_typing_spec.rb | 18 +++--- 8 files changed, 76 insertions(+), 45 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 0669503bb..9612335e3 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -253,7 +253,6 @@ def deserialize_combined_pin_cache(gemspec) if !rbs_collection_pins.nil? && !yard_pins.nil? logger.debug { "Combining pins for #{gemspec.name}:#{gemspec.version}" } - # @sg-ignore sensitive typing needs to handle "unless foo.nil?" 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 diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index b14a3ba1b..4bbf84e2a 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -57,11 +57,11 @@ def synchronized? # @param source [Source, nil] # @return [void] def attach source - # @sg-ignore flow sensitive typing needs to handle "if !foo" + # @sg-ignore flow sensitive typing needs to handle ivars if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename) - # @sg-ignore flow sensitive typing needs to handle "if !foo" + # @sg-ignore flow sensitive typing needs to handle ivars source_map_hash.delete @current.filename - # @sg-ignore flow sensitive typing needs to handle "if !foo" + # @sg-ignore flow sensitive typing needs to handle ivars source_map_external_require_hash.delete @current.filename @external_requires = nil end @@ -75,9 +75,9 @@ def attach source # # @param filename [String] # @return [Boolean] - # @sg-ignore flow sensitive typing needs to handle "if !foo" + # @sg-ignore flow sensitive typing needs to handle ivars def attached? filename - # @sg-ignore flow sensitive typing needs to handle "if !foo" + # @sg-ignore flow sensitive typing needs to handle ivars !@current.nil? && @current.filename == filename end alias open? attached? @@ -190,10 +190,13 @@ def definitions_at filename, line, column source = read(filename) offset = Solargraph::Position.to_offset(source.code, Solargraph::Position.new(line, column)) # @sg-ignore Need to add nil check here + # @type [MatchData, nil] lft = source.code[0..offset-1].match(/\[[a-z0-9_:<, ]*?([a-z0-9_:]*)\z/i) # @sg-ignore Need to add nil check here + # @type [MatchData, nil] rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i) if lft && rgt + # @sg-ignore Need to add nil check here tag = (lft[1] + rgt[1]).sub(/:+$/, '') clip = mutex.synchronize { api_map.clip(cursor) } clip.translate tag diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index ae3d46455..c0c41f2a9 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -34,8 +34,8 @@ def process_and(and_node, true_ranges = [], false_ranges = []) # can't assume if an and is false that every single condition # is false, so don't provide any false ranges to assert facts # on - process_conditional(lhs, true_ranges + [rhs_presence], []) - process_conditional(rhs, true_ranges, []) + process_expression(lhs, true_ranges + [rhs_presence], []) + process_expression(rhs, true_ranges, []) end # @param or_node [Parser::AST::Node] @@ -63,8 +63,8 @@ def process_or(or_node, true_ranges = [], false_ranges = []) # can't assume if an or is true that every single condition is # true, so don't provide true ranges to assert facts on - process_conditional(lhs, [], [rhs_presence]) - process_conditional(rhs, [], []) + process_expression(lhs, [], [rhs_presence]) + process_expression(rhs, [], []) end # @param node [Parser::AST::Node] @@ -77,12 +77,17 @@ def process_calls(node, true_presences, false_presences) process_isa(node, true_presences, false_presences) process_nilp(node, true_presences, false_presences) + process_bang(node, true_presences, false_presences) end # @param if_node [Parser::AST::Node] + # @param true_ranges [Array] + # @param false_ranges [Array] # # @return [void] - def process_if(if_node) + def process_if(if_node, true_ranges = [], false_ranges = []) + return if if_node.type != :if + # # See if we can refine a type based on the result of 'if foo.nil?' # @@ -100,9 +105,6 @@ def process_if(if_node) # @type [Parser::AST::Node, nil] else_clause = if_node.children[2] - true_ranges = [] - false_ranges = [] - unless enclosing_breakable_pin.nil? rest_of_breakable_body = Range.new(get_node_end_position(if_node), get_node_end_position(enclosing_breakable_pin.node)) @@ -154,7 +156,7 @@ def process_if(if_node) get_node_end_position(else_clause)) end - process_conditional(conditional_node, true_ranges, false_ranges) + process_expression(conditional_node, true_ranges, false_ranges) end class << self @@ -258,21 +260,24 @@ def process_facts(facts_by_pin, presences) end end - # @param conditional_node [Parser::AST::Node] + # @param expression_node [Parser::AST::Node] # @param true_ranges [Array] # @param false_ranges [Array] # # @return [void] - def process_conditional(conditional_node, true_ranges, false_ranges) - process_calls(conditional_node, true_ranges, false_ranges) - process_and(conditional_node, true_ranges, false_ranges) - process_or(conditional_node, true_ranges, false_ranges) - process_variable(conditional_node, true_ranges, false_ranges) + def process_expression(expression_node, true_ranges, false_ranges) + process_calls(expression_node, true_ranges, false_ranges) + process_and(expression_node, true_ranges, false_ranges) + process_or(expression_node, true_ranges, false_ranges) + process_variable(expression_node, true_ranges, false_ranges) + process_if(expression_node, true_ranges, false_ranges) end # @param call_node [Parser::AST::Node] # @param method_name [Symbol] - # @return [Array(String, String), nil] Type name and variable name + # @return [Array(String, String), nil] Tuple of rgument to + # function, then receiver of function if it's a variable, + # otherwise nil if no simple variable receiver def parse_call(call_node, method_name) return unless call_node&.type == :send && call_node.children[1] == method_name # Check if conditional node follows this pattern: @@ -378,6 +383,30 @@ def process_nilp(nilp_node, true_presences, false_presences) process_facts(if_false, false_presences) end + # @param bang_node [Parser::AST::Node] + # @return [Array(String, String), nil] + def parse_bang(bang_node) + parse_call(bang_node, :!) + end + + # @param bang_node [Parser::AST::Node] + # @param true_presences [Array] + # @param false_presences [Array] + # + # @return [void] + def process_bang(bang_node, true_presences, false_presences) + # pry(main)> require 'parser/current'; Parser::CurrentRuby.parse("!2") + # => s(:send, + # s(:int, 2), :!) + # end + return unless bang_node.type == :send && bang_node.children[1] == :! + + receiver = bang_node.children[0] + + # swap the two presences + process_expression(receiver, false_presences, true_presences) + end + # @param var_node [Parser::AST::Node] # # @return [String, nil] Variable name referenced diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 11cd7c0d4..fc7b92720 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -258,11 +258,11 @@ def associated_comments # @type [Integer, nil] last = nil comments.each_pair do |num, snip| - # @sg-ignore flow sensitive typing needs to handle "if !foo" + # @sg-ignore Translate to something flow sensitive typing understands if !last || num == last + 1 buffer.concat "#{snip.text}\n" else - # @sg-ignore flow sensitive typing needs to handle "if !foo" + # @sg-ignore Translate to something flow sensitive typing understands result[first_not_empty_from(last + 1)] = buffer.clone buffer.replace "#{snip.text}\n" end diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index 0a3a38b6c..0bbf90972 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -31,11 +31,11 @@ def write text, nullable = false if nullable and !range.nil? and new_text.match(/[.\[{(@$:]$/) [':', '@'].each do |dupable| next unless new_text == dupable - # @sg-ignore flow sensitive typing needs to handle "if !foo" + # @sg-ignore flow sensitive typing needs to handle && on both sides offset = Position.to_offset(text, range.start) if text[offset - 1] == dupable p = Position.from_offset(text, offset - 1) - # @sg-ignore flow sensitive typing needs to handle "if !foo" + # @sg-ignore flow sensitive typing needs to handle && on both sides r = Change.new(Range.new(p, range.start), ' ') text = r.write(text) end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 218243d37..0936c03ab 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -327,7 +327,7 @@ def call_problems 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 - # @sg-ignore flow sensitive typing needs to handle "if !foo" + # @sg-ignore Need to support nested flow sensitive types if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) unless closest.generic? || ignored_pins.include?(found) if closest.defined? @@ -650,7 +650,7 @@ def declared_externally? pin end all_closest = all_found.map { |pin| pin.typify(api_map) } closest = ComplexType.new(all_closest.flat_map(&:items).uniq) - # @sg-ignore flow sensitive typing needs to handle "if !foo" + # @sg-ignore Need to support nested flow sensitive types if !found || closest.defined? || internal?(found) return false end @@ -754,7 +754,7 @@ def optional_param_count(parameters) # @sg-ignore need boolish support for ? methods def abstract? pin pin.docstring.has_tag?('abstract') || - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore of low sensitive typing needs to handle ivars (pin.closure && pin.closure.docstring.has_tag?('abstract')) end @@ -815,7 +815,7 @@ def without_ignored problems problems.reject do |problem| node = source.node_at(problem.location.range.start.line, problem.location.range.start.column) ignored = node && source.comments_for(node)&.include?('@sg-ignore') - # @sg-ignore flow sensitive typing needs to handle "if !foo" + # @sg-ignore Unresolved call to ! 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}" } diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 16cfaf5aa..48fef365c 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,29 +58,29 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo 244: Need to add nil check here - # @todo 26: flow sensitive typing needs to handle ivars - # @todo 19: Translate to something flow sensitive typing understands - # @todo 12: flow sensitive typing needs to handle "if !foo" + # @todo 245: Need to add nil check here + # @todo 30: flow sensitive typing needs to handle ivars + # @todo 21: Translate to something flow sensitive typing understands # @todo 9: Need to validate config # @todo 8: Should better support meaning of '&' in RBS # @todo 7: literal arrays in this module turn into ::Solargraph::Source::Chain::Array # @todo 7: flow sensitive typing needs to handle inner closures + # @todo 6: Need to support nested flow sensitive types # @todo 5: should understand meaning of &. # @todo 5: Should handle redefinition of types in simple contexts # @todo 5: need boolish support for ? methods # @todo 4: Need support for reduce_class_type in UniqueType - # @todo 4: Need to support nested flow sensitive types # @todo 4: Need to handle implicit nil on else # @todo 3: downcast output of Enumerable#select # @todo 3: EASY: flow sensitive typing needs better handling of ||= on lvars # @todo 2: EASY: flow sensitive typing needs to handle "while foo" # @todo 2: EASY: flow sensitive typing needs to handle "unless foo.nil?" - # @todo 1: EASY: flow sensitive typing needs to handle && on both sides + # @todo 2: EASY: flow sensitive typing needs to handle && on both sides # @todo 1: EASY: flow sensitive typing needs to handle 'raise if' # @todo 1: To make JSON strongly typed we'll need a record syntax # @todo 1: Untyped method Solargraph::Pin::Base#assert_same could not be inferred # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' + # @todo 1: Unresolved call to ! def require_all_unique_types_match_expected? rank >= LEVELS[:strong] end diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 374609f62..b680b54ab 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -903,17 +903,17 @@ def a b it 'uses ! to detect nilness' do source = Solargraph::Source.load_string(%( - class ReproBase; end - class Repro < ReproBase; end - # @type [ReproBase] - value = bar - while !is_done() - break unless value.is_a? Repro - value + class A + # @param a [Integer, nil] + # @return [Integer] + def foo a + return a unless !a + 123 + end end ), 'test.rb') api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [7, 8]) - expect(clip.infer.to_s).to eq('Repro') + clip = api_map.clip_at('test.rb', [5, 17]) + expect(clip.infer.to_s).to eq('Integer') end end From 62a1c97ee5b495ad0dba89d2841679d36d4e3580 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 21 Sep 2025 12:32:53 -0400 Subject: [PATCH 356/930] Less-rigorous assertions merging assignment attribute of parameters Default values don't exist in RBS; it just tells you if the arg is optinal or not - this was a problem in solargraph-rails specs while assertions were on. --- lib/solargraph/pin/local_variable.rb | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 78e0b287d..512231a8a 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -23,14 +23,22 @@ def initialize assignment: nil, presence: nil, presence_certain: false, **splat def combine_with(other, attrs={}) new_attrs = { - assignment: assert_same(other, :assignment), + # default values don't exist in RBS; it just tells you if + # the arg is optinal or not; prefer a provided value if we + # have one here, but don't be too picky otherwise, as it's + # ultimately just documentation, not something we use for + # inference or typechecking + # + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 + assignment: choose(other, :assignment), + + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 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 + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 new_attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence) + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 super(other, new_attrs) end From 85be17da9c63cb69f88320959aa0793bfd9cab05 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 22 Sep 2025 10:05:02 -0400 Subject: [PATCH 357/930] Fix bad sexpr generation in op_asgn I'm not sure I could come up with a case where this matters in practice, but this was spotted in the solargraph-rails specs when I turned on assertions. It was trying to parse: ``` Rails.application.config.filter_parameters += [ :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc ] ``` from Rails' filter_parameter_logging.rb https://github.com/iftheshoefritz/solargraph-rails/actions/runs/17915995139/job/50938306870?pr=173 ``` Expected method name to be a Symbol, got Parser::AST::Node for node s(:send, s(:send, s(:send, s(:const, nil, :Rails), :application), :config), s(:send, s(:send, s(:send, s(:send, s(:const, nil, :Rails), :application), :config), :filter_parameters), :+, s(:array, s(:sym, :passw), s(:sym, :email), s(:sym, :secret), s(:sym, :token), s(:sym, :_key), s(:sym, :crypt), s(:sym, :salt), s(:sym, :certificate), s(:sym, :otp), s(:sym, :ssn), s(:sym, :cvv), s(:sym, :cvc)))) ``` Blocks https://github.com/iftheshoefritz/solargraph-rails/pull/173 --- .../parser_gem/node_processors/opasgn_node.rb | 75 +++++++++++++++++-- spec/source_map_spec.rb | 51 +++++++++++++ 2 files changed, 118 insertions(+), 8 deletions(-) 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 151f79928..d9a54508b 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb @@ -9,14 +9,71 @@ module NodeProcessors class OpasgnNode < Parser::NodeProcessor::Base # @return [void] def process - # Parser::CurrentRuby.parse("a += 2") - # => s(:op_asgn, - # s(:lvasgn, :a), :+, - # s(:int, 2)) - asgn = node.children[0] - variable_name = asgn.children[0] + target = node.children[0] operator = node.children[1] argument = node.children[2] + if target.type == :send + process_send_target(target, operator, argument) + elsif target.type.to_s.end_with?('vasgn') + process_vasgn_target(target, operator, argument) + else + Solargraph.assert_or_log(:opasgn_unknown_target, + "Unexpected op_asgn target type: #{target.type}") + end + end + + # @param call [Parser::AST::Node] the target of the assignment + # @param operator [Symbol] the operator, e.g. :+ + # @param argument [Parser::AST::Node] the argument of the operation + # + # @return [void] + def process_send_target(call, operator, argument) + # if target is a call: + # [10] pry(main)> Parser::CurrentRuby.parse("Foo.bar += baz") + # => s(:op_asgn, + # s(:send, # call + # s(:const, nil, :Foo), # calee + # :bar), # call_method + # :+, # operator + # s(:send, nil, :baz)) # argument + # [11] pry(main)> + callee = call.children[0] + call_method = call.children[1] + asgn_method = "#{call_method}=".to_sym + + # [8] pry(main)> Parser::CurrentRuby.parse("Foo.bar = Foo.bar + baz") + # => s(:send, + # s(:const, nil, :Foo), # callee + # :bar=, # asgn_method + # s(:send, + # s(:send, + # s(:const, nil, :Foo), # callee + # :bar), # call_method + # :+, # operator + # s(:send, nil, :baz))) # argument + new_send = node.updated(:send, + [callee, + asgn_method, + node.updated(:send, [call, operator, argument])]) + NodeProcessor.process(new_send, region, pins, locals) + end + + # @param asgn [Parser::AST::Node] the target of the assignment + # @param operator [Symbol] the operator, e.g. :+ + # @param argument [Parser::AST::Node] the argument of the operation + # + # @return [void] + def process_vasgn_target(asgn, operator, argument) + # => s(:op_asgn, + # s(:lvasgn, :a), # asgn + # :+, # operator + # s(:int, 2)) # argument + + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 + # @type [Parser::AST::Node] + variable_name = asgn.children[0] + # @sg-ignore https://github.com/castwide/solargraph/pull/1005 + STDERR.puts("variable_name is #{variable_name.inspect}") # for lvasgn, gvasgn, cvasgn, convert to lvar, gvar, cvar # [6] pry(main)> Parser::CurrentRuby.parse("a = a + 1") # => s(:lvasgn, :a, @@ -25,14 +82,16 @@ def process # s(:int, 1))) # [7] pry(main)> variable_reference_type = asgn.type.to_s.sub(/vasgn$/, 'var').to_sym - variable_reference = node.updated(variable_reference_type, asgn.children) + target_reference = node.updated(variable_reference_type, asgn.children) send_children = [ - variable_reference, + target_reference, operator, argument ] + STDERR.puts("send_children is #{send_children.inspect}") send_node = node.updated(:send, send_children) new_asgn = node.updated(asgn.type, [variable_name, send_node]) + STDERR.puts("new_asgn: #{new_asgn}") NodeProcessor.process(new_asgn, region, pins, locals) end end diff --git a/spec/source_map_spec.rb b/spec/source_map_spec.rb index ef6eddebd..5cc9ea737 100644 --- a/spec/source_map_spec.rb +++ b/spec/source_map_spec.rb @@ -88,6 +88,57 @@ def foo expect(locals).to be_empty end + it 'handles op_asgn case with assertions on' do + # set SOLARGRAPH_ASSERTS=onto test this + old_asserts = ENV.fetch('SOLARGRAPH_ASSERTS', nil) + ENV['SOLARGRAPH_ASSERTS'] = 'on' + + expect do + map = Solargraph::SourceMap.load_string(%( + Foo.bar += baz + ), 'test.rb') + loc = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(3, 9, 3, 9)) + locals = map.locals_at(loc) + expect(locals).to be_empty + end.not_to raise_error + ensure + ENV['SOLARGRAPH_ASSERTS'] = old_asserts + end + + it 'handles or_asgn case with assertions on' do + # set SOLARGRAPH_ASSERTS=onto test this + old_asserts = ENV.fetch('SOLARGRAPH_ASSERTS', nil) + ENV['SOLARGRAPH_ASSERTS'] = 'on' + + expect do + map = Solargraph::SourceMap.load_string(%( + Foo.bar ||= baz + ), 'test.rb') + loc = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(3, 9, 3, 9)) + locals = map.locals_at(loc) + expect(locals).to be_empty + end.not_to raise_error + ensure + ENV['SOLARGRAPH_ASSERTS'] = old_asserts + end + + it 'handles lvasgn case with assertions on' do + # set SOLARGRAPH_ASSERTS=onto test this + old_asserts = ENV.fetch('SOLARGRAPH_ASSERTS', nil) + ENV['SOLARGRAPH_ASSERTS'] = 'on' + + expect do + map = Solargraph::SourceMap.load_string(%( + Foo.bar = baz + ), 'test.rb') + loc = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(3, 9, 3, 9)) + locals = map.locals_at(loc) + expect(locals).to be_empty + end.not_to raise_error + ensure + ENV['SOLARGRAPH_ASSERTS'] = old_asserts + end + it 'scopes local variables correctly in class_eval blocks' do map = Solargraph::SourceMap.load_string(%( class Foo; end From d2a0ca94c8e9d9fbe0d9f42ad78aaa8826491159 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 22 Sep 2025 10:10:36 -0400 Subject: [PATCH 358/930] Drop logging --- lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb | 2 -- 1 file changed, 2 deletions(-) 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 d9a54508b..11ed93e51 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb @@ -88,10 +88,8 @@ def process_vasgn_target(asgn, operator, argument) operator, argument ] - STDERR.puts("send_children is #{send_children.inspect}") send_node = node.updated(:send, send_children) new_asgn = node.updated(asgn.type, [variable_name, send_node]) - STDERR.puts("new_asgn: #{new_asgn}") NodeProcessor.process(new_asgn, region, pins, locals) end end From 42fa59fe7486411d9b14a4dbf9223f8b7625c454 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 22 Sep 2025 10:28:23 -0400 Subject: [PATCH 359/930] Drop logging --- lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb | 2 -- 1 file changed, 2 deletions(-) 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 11ed93e51..9020532e5 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb @@ -72,8 +72,6 @@ def process_vasgn_target(asgn, operator, argument) # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] variable_name = asgn.children[0] - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 - STDERR.puts("variable_name is #{variable_name.inspect}") # for lvasgn, gvasgn, cvasgn, convert to lvar, gvar, cvar # [6] pry(main)> Parser::CurrentRuby.parse("a = a + 1") # => s(:lvasgn, :a, From 82adf3e664dab9ef6118b2e8ee1b5ad0061080d6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 22 Sep 2025 10:32:26 -0400 Subject: [PATCH 360/930] rubocop fixes --- .../parser/parser_gem/node_processors/opasgn_node.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 9020532e5..1500a4f72 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb @@ -27,7 +27,7 @@ def process # @param argument [Parser::AST::Node] the argument of the operation # # @return [void] - def process_send_target(call, operator, argument) + def process_send_target call, operator, argument # if target is a call: # [10] pry(main)> Parser::CurrentRuby.parse("Foo.bar += baz") # => s(:op_asgn, @@ -39,7 +39,7 @@ def process_send_target(call, operator, argument) # [11] pry(main)> callee = call.children[0] call_method = call.children[1] - asgn_method = "#{call_method}=".to_sym + asgn_method = :"#{call_method}=" # [8] pry(main)> Parser::CurrentRuby.parse("Foo.bar = Foo.bar + baz") # => s(:send, @@ -63,7 +63,7 @@ def process_send_target(call, operator, argument) # @param argument [Parser::AST::Node] the argument of the operation # # @return [void] - def process_vasgn_target(asgn, operator, argument) + def process_vasgn_target asgn, operator, argument # => s(:op_asgn, # s(:lvasgn, :a), # asgn # :+, # operator From 225c2dd8203575a743216c2277a9dcc0d624b0a6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 22 Sep 2025 10:42:52 -0400 Subject: [PATCH 361/930] Drop @sg-ignore --- lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb | 1 - 1 file changed, 1 deletion(-) 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 1500a4f72..0e4d7b26a 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb @@ -69,7 +69,6 @@ def process_vasgn_target asgn, operator, argument # :+, # operator # s(:int, 2)) # argument - # @sg-ignore https://github.com/castwide/solargraph/pull/1005 # @type [Parser::AST::Node] variable_name = asgn.children[0] # for lvasgn, gvasgn, cvasgn, convert to lvar, gvar, cvar From 982ad1a74096ec5f5cf817d9b715ffeda7837faf Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 22 Sep 2025 11:07:47 -0400 Subject: [PATCH 362/930] Remove duplicate assignment handling --- lib/solargraph/pin/base_variable.rb | 8 +++++++- lib/solargraph/pin/local_variable.rb | 13 +------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 764c1fb39..93845b585 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -23,7 +23,13 @@ def initialize assignment: nil, return_type: nil, **splat def combine_with(other, attrs={}) attrs.merge({ - assignment: assert_same(other, :assignment), + # default values don't exist in RBS parameters; it just + # tells you if the arg is optional or not. Prefer a + # provided value if we have one here since we can't rely on + # it from RBS so we can infer from it and typecheck on it. + # + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 + assignment: choose(other, :assignment), mass_assignment: assert_same(other, :mass_assignment), return_type: combine_return_type(other), }) diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 512231a8a..db6dcfe6a 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -10,28 +10,17 @@ def presence_certain? @presence_certain end - # @param assignment [AST::Node, nil] # @param presence [Range, nil] # @param presence_certain [Boolean] # @param splat [Hash] - def initialize assignment: nil, presence: nil, presence_certain: false, **splat + def initialize presence: nil, presence_certain: false, **splat super(**splat) - @assignment = assignment @presence = presence @presence_certain = presence_certain end def combine_with(other, attrs={}) new_attrs = { - # default values don't exist in RBS; it just tells you if - # the arg is optinal or not; prefer a provided value if we - # have one here, but don't be too picky otherwise, as it's - # ultimately just documentation, not something we use for - # inference or typechecking - # - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 - assignment: choose(other, :assignment), - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 presence_certain: assert_same(other, :presence_certain?), }.merge(attrs) From 3399448f739d09b0a1297e878fac5499ff9bb816 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 23 Sep 2025 20:59:58 -0400 Subject: [PATCH 363/930] 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 bd4e4ae19d10c580aa052572bfa08facb51b0dcd Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 13:46:58 -0400 Subject: [PATCH 364/930] Modernize qualify call --- lib/solargraph/pin/base_variable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index be05d0c7f..487f50916 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -89,7 +89,7 @@ def exclude_return_type def probe api_map if presence_certain? && return_type.defined? # flow sensitive typing has already figured out this type - return return_type.qualify(api_map, namespace) + return return_type.qualify(api_map, *gates) end unless @assignment.nil? From 4eda431ce26211bf4d6955cbae997b0e79ffe4d4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 24 Sep 2025 14:56:12 -0400 Subject: [PATCH 365/930] 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/930] 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/930] 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/930] 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 a8602dc74ec51cccc9d511d79c340d3ab1594c22 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 25 Sep 2025 07:38:24 -0400 Subject: [PATCH 369/930] 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 f4399eba824fdd5aa8d605b2143ddef20c5617d8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 25 Sep 2025 11:15:45 -0400 Subject: [PATCH 370/930] [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 371/930] 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 372/930] 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 373/930] [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 374/930] [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 375/930] 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 376/930] 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 99cf71d31770203c7a48685f87f1e79eb5511234 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 25 Sep 2025 18:18:47 -0400 Subject: [PATCH 377/930] 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 dfdb952a5ac56a38adfd34f913073dfb8cee1cc2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 08:58:35 -0400 Subject: [PATCH 378/930] 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 f8f7ca27b1dc40864a4ac49e3c26caa1fcb33b84 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 09:02:13 -0400 Subject: [PATCH 379/930] 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 8ce4bb91e5f2f7a57c8125150427e67b8e465ee7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 26 Sep 2025 09:26:26 -0400 Subject: [PATCH 380/930] 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 381/930] 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 382/930] 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 383/930] 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 384/930] 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 385/930] 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 386/930] 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 387/930] 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 388/930] 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 389/930] 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 7546f7355534c70d3ae9ddbb863304d5be557d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lek=C3=AB=20Mula?= Date: Sun, 21 Sep 2025 17:41:33 +0200 Subject: [PATCH 390/930] Enable solargraph-rspec tests --- .github/workflows/plugins.yml | 55 ++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index b5984f3cb..ded3be5a0 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -105,30 +105,37 @@ jobs: - name: Ensure specs still run run: bundle exec rake spec - # run_solargraph_rspec_specs: - # # check out solargraph-rspec as well as this project, and point the former to use the latter as a local gem - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v3 - # - name: clone https://github.com/lekemula/solargraph-rspec/ - # run: | - # cd .. - # git clone https://github.com/lekemula/solargraph-rspec.git - # cd solargraph-rspec - # - name: Set up Ruby - # uses: ruby/setup-ruby@v1 - # with: - # ruby-version: '3.0' - # bundler-cache: false - # - name: Install gems - # run: | - # cd ../solargraph-rspec - # echo "gem 'solargraph', path: '../solargraph'" >> Gemfile - # bundle install - # - name: Run specs - # run: | - # cd ../solargraph-rspec - # bundle exec rake spec + run_solargraph_rspec_specs: + # check out solargraph-rspec as well as this project, and point the former to use the latter as a local gem + runs-on: ubuntu-latest + env: + SOLARGRAPH_CACHE: ${{ github.workspace }}/solargraph-rspec/vendor/solargraph/cache + steps: + - uses: actions/checkout@v3 + - name: clone https://github.com/lekemula/solargraph-rspec/ + run: | + cd .. + git clone https://github.com/lekemula/solargraph-rspec.git + cd solargraph-rspec + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.4 + bundler-cache: true + - name: Install gems + run: | + cd ../solargraph-rspec + echo "gem 'solargraph', path: '../solargraph'" >> Gemfile + bundle install + - name: Solargraph generate RSpec gems YARD and RBS pins + run: | + cd ../solargraph-rspec + rspec_gems=$(bundle exec ruby -r './lib/solargraph-rspec' -e 'puts Solargraph::Rspec::Gems.gem_names.join(" ")' 2>/dev/null | tail -n1) + bundle exec solargraph gems $rspec_gems + - name: Run specs + run: | + cd ../solargraph-rspec + bundle exec rspec run_solargraph_rails_specs: # check out solargraph-rails as well as this project, and point the former to use the latter as a local gem From 9c79e5b3a01ac3fd8d84b35b94e4cb4110f91b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lek=C3=AB=20Mula?= Date: Sun, 21 Sep 2025 19:40:15 +0200 Subject: [PATCH 391/930] Fix rspec gems specs --- .github/workflows/plugins.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index ded3be5a0..1c633fda0 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -109,7 +109,8 @@ jobs: # check out solargraph-rspec as well as this project, and point the former to use the latter as a local gem runs-on: ubuntu-latest env: - SOLARGRAPH_CACHE: ${{ github.workspace }}/solargraph-rspec/vendor/solargraph/cache + SOLARGRAPH_CACHE: ${{ github.workspace }}/../solargraph-rspec/vendor/solargraph/cache + BUNDLE_PATH: ${{ github.workspace }}/../solargraph-rspec/vendor/bundle steps: - uses: actions/checkout@v3 - name: clone https://github.com/lekemula/solargraph-rspec/ @@ -121,12 +122,17 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: 3.4 - bundler-cache: true + bundler-cache: false - name: Install gems run: | cd ../solargraph-rspec echo "gem 'solargraph', path: '../solargraph'" >> Gemfile - bundle install + bundle config path ${{ env.BUNDLE_PATH }} + bundle install --jobs 4 --retry 3 + - name: Configure .solargraph.yml + run: | + cd ../solargraph-rspec + cp .solargraph.yml.example .solargraph.yml - name: Solargraph generate RSpec gems YARD and RBS pins run: | cd ../solargraph-rspec @@ -135,7 +141,7 @@ jobs: - name: Run specs run: | cd ../solargraph-rspec - bundle exec rspec + bundle exec rspec --format progress run_solargraph_rails_specs: # check out solargraph-rails as well as this project, and point the former to use the latter as a local gem From b4c8b9975d168824b7eec72d7865de4ac78070d9 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 392/930] 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 d67d580d84bc73728245afb3d319b0d7c57e98bc 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 393/930] 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 c0455fae9be835a4eb288eb626f92b058be75cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lek=C3=AB=20Mula?= Date: Thu, 11 Sep 2025 23:16:10 +0200 Subject: [PATCH 394/930] Fix CI (typechecking) and pin the gem version Co-authored-by: Vince Broz --- lib/solargraph/parser/parser_gem/node_processors/defs_node.rb | 3 ++- solargraph.gemspec | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb b/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb index 5f40457e9..ce807cd47 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb @@ -15,7 +15,8 @@ def process closure = region.closure else closure = Solargraph::Pin::Namespace.new( - name: unpack_name(node.children[0]) + name: unpack_name(node.children[0]), + source: :parser, ) end pins.push Solargraph::Pin::Method.new( diff --git a/solargraph.gemspec b/solargraph.gemspec index 1c694034b..091daa093 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -59,7 +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' + s.add_development_dependency 'vernier', '< 2' # work around missing yard dependency needed as of Ruby 3.5 s.add_development_dependency 'irb', '~> 1.15' end From 431dd3263b2e9449b7fa1a0a133fe2e8b38cd2b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lek=C3=AB=20Mula?= Date: Fri, 12 Sep 2025 00:02:18 +0200 Subject: [PATCH 395/930] Make overcommit happy? --- lib/solargraph/shell.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 2c07a94e0..6a335bc9a 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -265,6 +265,9 @@ def profile(file = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength host = Solargraph::LanguageServer::Host.new host.client_capabilities.merge!({ 'window' => { 'workDoneProgress' => true } }) + # @param method [String] The message method + # @param params [Hash] The method parameters + # @return [void] def host.send_notification method, params puts "Notification: #{method} - #{params}" end From 55cf9c32da1375e9213d656e83d865b320131c69 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 27 Sep 2025 10:12:21 -0400 Subject: [PATCH 396/930] 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 397/930] 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 398/930] 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 ddbb459df1afd54b3082bb92909a3e0bb9267512 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 11:49:08 -0400 Subject: [PATCH 399/930] Add annotations and such --- lib/solargraph/api_map/constants.rb | 12 +++++++++--- lib/solargraph/api_map/source_to_yard.rb | 1 - lib/solargraph/api_map/store.rb | 5 ----- lib/solargraph/type_checker/rules.rb | 6 ++++-- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 00b00ab9f..cd526b7e5 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -19,6 +19,8 @@ def initialize store # # @param name [String] # @param gates [Array, String>] + # + # @sg-ignore flow sensitive typing needs to eliminate literal from union with return if foo == :bar # @return [String, nil] def resolve(name, *gates) # @sg-ignore Need to add nil check here @@ -63,10 +65,12 @@ def qualify name, *gates return name if ['Boolean', 'self', nil].include?(name) gates.push '' unless gates.include?('') + # @sg-ignore flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) fqns = resolve(name, gates) return unless fqns pin = store.get_path_pins(fqns).first if pin.is_a?(Pin::Constant) + # @sg-ignore Need to add nil check here const = Solargraph::Parser::NodeMethods.unpack_name(pin.assignment) return unless const resolve(const, pin.gates) @@ -87,6 +91,7 @@ def clear # @param name [String] # @param gates [Array] + # @sg-ignore Should handle redefinition of types in simple contexts # @return [String, nil] def resolve_and_cache name, gates cached_resolve[[name, gates]] = :in_process @@ -113,13 +118,13 @@ 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 + # @sg-ignore 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. # @return [Array(Object, Array)] def complex_resolve name, gates, internal resolved = nil @@ -147,6 +152,7 @@ def simple_resolve name, gate, internal here = "#{gate}::#{name}".sub(/^::/, '').sub(/::$/, '') pin = store.get_path_pins(here).first if pin.is_a?(Pin::Constant) && internal + # @sg-ignore Need to add nil check here const = Solargraph::Parser::NodeMethods.unpack_name(pin.assignment) return unless const resolve(const, pin.gates) diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index b7858a964..f6f5bd6ed 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -72,7 +72,6 @@ def rake_yard store next end - # @sg-ignore Need to add nil check here code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope) { |obj| # @sg-ignore Translate to something flow sensitive typing understands next if pin.location.nil? || pin.location.filename.nil? diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index f67a14424..e23609365 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -153,11 +153,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) diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 48fef365c..1e82011ff 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,7 +58,7 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo 245: Need to add nil check here + # Need to add nil check here # @todo 30: flow sensitive typing needs to handle ivars # @todo 21: Translate to something flow sensitive typing understands # @todo 9: Need to validate config @@ -66,8 +66,8 @@ def require_inferred_type_params? # @todo 7: literal arrays in this module turn into ::Solargraph::Source::Chain::Array # @todo 7: flow sensitive typing needs to handle inner closures # @todo 6: Need to support nested flow sensitive types + # @todo 6: Should handle redefinition of types in simple contexts # @todo 5: should understand meaning of &. - # @todo 5: Should handle redefinition of types in simple contexts # @todo 5: need boolish support for ? methods # @todo 4: Need support for reduce_class_type in UniqueType # @todo 4: Need to handle implicit nil on else @@ -81,6 +81,8 @@ def require_inferred_type_params? # @todo 1: Untyped method Solargraph::Pin::Base#assert_same could not be inferred # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' # @todo 1: Unresolved call to ! + # @todo 1: EASY: flow sensitive typing needs to eliminate literal from union with return if foo == :bar + # @todo 1: EASY: flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) def require_all_unique_types_match_expected? rank >= LEVELS[:strong] end From 59c8f99e26636d9722d6c810f36bee36100c8ca4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 11:51:48 -0400 Subject: [PATCH 400/930] Fix annotations --- lib/solargraph/page.rb | 2 -- lib/solargraph/source_map/mapper.rb | 3 --- 2 files changed, 5 deletions(-) diff --git a/lib/solargraph/page.rb b/lib/solargraph/page.rb index 312c243d4..5d879bbe1 100644 --- a/lib/solargraph/page.rb +++ b/lib/solargraph/page.rb @@ -13,7 +13,6 @@ class Page # @param locals[Hash] # @param render_method [Proc] # @return [Binder] - # @sg-ignore https://github.com/castwide/solargraph/issues/1082 class Binder < OpenStruct # @param locals [Hash] # @param render_method [Proc] @@ -59,7 +58,6 @@ def initialize directory = VIEWS_PATH # @param layout [Boolean] # @param locals [Hash] @render_method = proc { |template, layout: false, locals: {}| - # @sg-ignore https://github.com/castwide/solargraph/issues/1082 binder = Binder.new(locals, @render_method) if layout Tilt::ERBTemplate.new(Page.select_template(directories, 'layout')).render(binder) do diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index e5d72dd97..2dc14869b 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -106,7 +106,6 @@ def find_directive_line_number comment, tag, start # @param directive [YARD::Tags::Directive] # @return [void] def process_directive source_position, comment_position, directive - # @sg-ignore Need to add nil check here docstring = Solargraph::Source.parse_docstring(directive.tag.text).to_docstring location = Location.new(@filename, Range.new(comment_position, comment_position)) case directive.tag.tag_name @@ -193,7 +192,6 @@ def process_directive source_position, comment_position, directive when 'parse' begin ns = closure_at(source_position) - # @sg-ignore Need to add nil check here src = Solargraph::Source.load_string(directive.tag.text, @source.filename) region = Parser::Region.new(source: src, closure: ns) # @todo These pins may need to be marked not explicit @@ -218,7 +216,6 @@ def process_directive source_position, comment_position, directive # @sg-ignore Need to add nil check here namespace.domains.concat directive.tag.types unless directive.tag.types.nil? when 'override' - # @sg-ignore Need to add nil check here pins.push Pin::Reference::Override.new(location, directive.tag.name, docstring.tags, source: :source_map) when 'macro' From 294f0b8ad598d056c413ead00db01b1a6d4e69fc Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 12:01:15 -0400 Subject: [PATCH 401/930] More annotations --- lib/solargraph/api_map.rb | 8 +------- lib/solargraph/api_map/constants.rb | 6 +++--- lib/solargraph/api_map/source_to_yard.rb | 1 + lib/solargraph/pin/parameter.rb | 1 + lib/solargraph/source/chain/constant.rb | 2 ++ lib/solargraph/type_checker/rules.rb | 2 +- 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 9f71622b7..eb5c16e84 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -252,13 +252,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 @@ -877,6 +870,7 @@ def prefer_non_nil_variables pins # @param alias_pin [Pin::MethodAlias] # @return [Pin::Method, nil] def resolve_method_alias(alias_pin) + # @sg-ignore Need support for reduce_class_type in UniqueType ancestors = store.get_ancestors(alias_pin.full_context.reduce_class_type.tag) # @type [Pin::Method, nil] original = nil diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index cd526b7e5..0492ed787 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -118,13 +118,13 @@ 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 - # @sg-ignore 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. # @return [Array(Object, Array)] def complex_resolve name, gates, internal resolved = nil diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index f6f5bd6ed..b7858a964 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -72,6 +72,7 @@ def rake_yard store next end + # @sg-ignore Need to add nil check here code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope) { |obj| # @sg-ignore Translate to something flow sensitive typing understands next if pin.location.nil? || pin.location.filename.nil? diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 1a2af4054..433ec730f 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -181,6 +181,7 @@ def index # @param api_map [ApiMap] def typify api_map + # @sg-ignore Need to add nil check here return return_type.qualify(api_map, *closure.gates) unless return_type.undefined? closure.is_a?(Pin::Block) ? typify_block_param(api_map) : typify_method_param(api_map) end diff --git a/lib/solargraph/source/chain/constant.rb b/lib/solargraph/source/chain/constant.rb index 2752ec136..b1c25fab9 100644 --- a/lib/solargraph/source/chain/constant.rb +++ b/lib/solargraph/source/chain/constant.rb @@ -17,7 +17,9 @@ def resolve api_map, name_pin, locals base = word gates = name_pin.gates end + # @sg-ignore Need to add nil check here fqns = api_map.resolve(base, gates) + # @sg-ignore Need to add nil check here api_map.get_path_pins(fqns) end end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 1e82011ff..2c918090f 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -69,7 +69,7 @@ def require_inferred_type_params? # @todo 6: Should handle redefinition of types in simple contexts # @todo 5: should understand meaning of &. # @todo 5: need boolish support for ? methods - # @todo 4: Need support for reduce_class_type in UniqueType + # Need support for reduce_class_type in UniqueType # @todo 4: Need to handle implicit nil on else # @todo 3: downcast output of Enumerable#select # @todo 3: EASY: flow sensitive typing needs better handling of ||= on lvars From 1416e1d1e3f51c5c44dea3807796cbccccd870d3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 12:08:05 -0400 Subject: [PATCH 402/930] 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 403/930] 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 d8c85f876dba6a143bb052533250ecbce55bfa6e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 12:36:26 -0400 Subject: [PATCH 404/930] Fix annotations --- lib/solargraph/api_map/store.rb | 4 ---- .../parser/parser_gem/node_processors/opasgn_node.rb | 6 ++++++ lib/solargraph/pin/closure.rb | 2 +- lib/solargraph/pin/method.rb | 1 + lib/solargraph/source_map/mapper.rb | 1 + lib/solargraph/type_checker.rb | 1 + lib/solargraph/yard_map/mapper/to_namespace.rb | 1 + 7 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 7a1db1d5d..16b00de04 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -299,10 +299,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/parser/parser_gem/node_processors/opasgn_node.rb b/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb index 0e4d7b26a..b023623f0 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 Parser::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_vasgn_target: + # operator expected Symbol, received Parser::AST::Node process_vasgn_target(target, operator, argument) else Solargraph.assert_or_log(:opasgn_unknown_target, diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index e509195d7..78f826587 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -7,7 +7,7 @@ class Closure < Base attr_reader :scope # @param scope [::Symbol] :class or :instance - # @param generics [::Array, nil] + # @param generics [::Array, nil] # @param generic_defaults [Hash{String => ComplexType}] def initialize scope: :class, generics: nil, generic_defaults: {}, **splat super(**splat) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index bea49dc74..0b1388cf5 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -309,6 +309,7 @@ def typify api_map # @sg-ignore Need to add nil check here logger.debug { "Method#typify(self=#{self}) - type=#{type&.rooted_tags.inspect}" } unless type.nil? + # @sg-ignore Need to add nil check here qualified = type.qualify(api_map, *closure.gates) logger.debug { "Method#typify(self=#{self}) => #{qualified.rooted_tags.inspect}" } return qualified diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 2dc14869b..fad28128c 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -216,6 +216,7 @@ def process_directive source_position, comment_position, directive # @sg-ignore Need to add nil check here namespace.domains.concat directive.tag.types unless directive.tag.types.nil? when 'override' + # @sg-ignore Need to add a nil check here pins.push Pin::Reference::Override.new(location, directive.tag.name, docstring.tags, source: :source_map) when 'macro' diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index fae728c03..d53a78f35 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -568,6 +568,7 @@ def param_hash(pin) next if tag.types.nil? result[tag.name.to_s] = { tagged: tag.types.join(', '), + # @sg-ignore need to add a nil check here qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, *pin.closure.gates) } end diff --git a/lib/solargraph/yard_map/mapper/to_namespace.rb b/lib/solargraph/yard_map/mapper/to_namespace.rb index f7063e3d6..7d1d3ce6e 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, + # @sg-ignore need to add a nil check here gates: closure.gates, source: :yardoc, ) From 8069c3d8117eb13c96e1748da8436edcaca350d6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 13:23:59 -0400 Subject: [PATCH 405/930] string -> String --- 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 1efacd45b..d7593d2cd 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -581,7 +581,7 @@ def identity # Example: Given the name 'Bar' and the gates ['Foo', ''], # the fully qualified namespace should be 'Foo::Bar' or 'Bar'. # - # @return [Array] + # @return [Array] def gates @gates ||= closure&.gates || [''] end From be3f07757d95832d7cab99b9bf7d835ba895453b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 13:24:23 -0400 Subject: [PATCH 406/930] Drop an @sg-ignore --- lib/solargraph/pin/signature.rb | 1 - lib/solargraph/type_checker/rules.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 63b2e4001..004f9bb87 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -9,7 +9,6 @@ def initialize **splat super(**splat) end - # @sg-ignore flow sensitive typing needs to handle ivars def generics # @type [Array<::String, nil>] @generics ||= [].freeze diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 2c918090f..3dfc6d399 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -59,7 +59,7 @@ def require_inferred_type_params? end # Need to add nil check here - # @todo 30: flow sensitive typing needs to handle ivars + # @todo flow sensitive typing needs to handle ivars # @todo 21: Translate to something flow sensitive typing understands # @todo 9: Need to validate config # @todo 8: Should better support meaning of '&' in RBS From 12e45630867530a19b8c839c162c2ada88d03511 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 14:19:54 -0400 Subject: [PATCH 407/930] [regression] Fix resolution of a nested type case Found in the Solargraph::Pin::DelegatedMethod class in strong typechecking in https://github.com/apiology/solargraph/pull/12 --- lib/solargraph/api_map/constants.rb | 3 ++- spec/pin/parameter_spec.rb | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 430303ae1..3e846e0b1 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| - return ref.name.sub(/^::/, '') if ref.name.end_with?("::#{name}") + return ref.name.sub(/^::/, '') if ref.name.end_with?("::#{name}") && ref.name.start_with?('::') + mixin = resolve(ref.name, ref.reference_gates - gates) next unless mixin resolved = simple_resolve(name, mixin, internal) diff --git a/spec/pin/parameter_spec.rb b/spec/pin/parameter_spec.rb index 082ec54c6..81d40e5db 100644 --- a/spec/pin/parameter_spec.rb +++ b/spec/pin/parameter_spec.rb @@ -473,5 +473,33 @@ def self.foo bar: 'bar' type = pin.probe(api_map) expect(type.simple_tags).to eq('String') end + + it 'infers types from kwoptarg values' do + source = Solargraph::Source.load_string(%( + module A + module B + class Method + end + end + end + + module A + module B + class C < B::Method + # @param alt [Method] + # @return [B::Method, nil] + def resolve_method alt + alt + end + end + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new + api_map.map(source) + + clip = api_map.clip_at('test.rb', [14, 16]) + expect(clip.infer.rooted_tags).to eq('::A::B::Method') + end end end From b8174ddb186418dc0ef2feec67c514da1e977d31 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 14:36:41 -0400 Subject: [PATCH 408/930] Fix example name --- spec/pin/parameter_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/pin/parameter_spec.rb b/spec/pin/parameter_spec.rb index 81d40e5db..14c39f3fe 100644 --- a/spec/pin/parameter_spec.rb +++ b/spec/pin/parameter_spec.rb @@ -474,7 +474,7 @@ def self.foo bar: 'bar' expect(type.simple_tags).to eq('String') end - it 'infers types from kwoptarg values' do + it 'handles a relative type name case' do source = Solargraph::Source.load_string(%( module A module B From 0e380d0421f4880db2489b951c9037f48010cd69 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 20:08:00 -0400 Subject: [PATCH 409/930] Annotation --- lib/solargraph/source_map/mapper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index fad28128c..e221b7028 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -106,6 +106,7 @@ def find_directive_line_number comment, tag, start # @param directive [YARD::Tags::Directive] # @return [void] def process_directive source_position, comment_position, directive + # @sg-ignore Need to add nil check here docstring = Solargraph::Source.parse_docstring(directive.tag.text).to_docstring location = Location.new(@filename, Range.new(comment_position, comment_position)) case directive.tag.tag_name @@ -192,6 +193,7 @@ def process_directive source_position, comment_position, directive when 'parse' begin ns = closure_at(source_position) + # @sg-ignore Need to add nil check here src = Solargraph::Source.load_string(directive.tag.text, @source.filename) region = Parser::Region.new(source: src, closure: ns) # @todo These pins may need to be marked not explicit From 5762c0d5806760dcc1688e9dcdb064b25c6b26cd Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 20:08:25 -0400 Subject: [PATCH 410/930] Add @sg-ignores --- lib/solargraph/pin/base.rb | 1 + lib/solargraph/pin/namespace.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index bce2cb119..ee2513170 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -610,6 +610,7 @@ def identity # the fully qualified namespace should be 'Foo::Bar' or 'Bar'. # # @return [Array] + # @sg-ignore Solargraph::Pin::Base#gates return type could not be inferred def gates @gates ||= closure&.gates || [''] end diff --git a/lib/solargraph/pin/namespace.rb b/lib/solargraph/pin/namespace.rb index 8799e970a..7ab09b1bf 100644 --- a/lib/solargraph/pin/namespace.rb +++ b/lib/solargraph/pin/namespace.rb @@ -103,6 +103,7 @@ def typify api_map return_type end + # @sg-ignore Solargraph::Pin::Namespace#gates return type could not be inferred def gates @gates ||= if path.empty? @open_gates From b319c013e222a9088018e9321b1fb8e92060026c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 20:46:50 -0400 Subject: [PATCH 411/930] Update Rubocop todo --- .rubocop_todo.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 78d094550..998ff6ba8 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -453,7 +453,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: @@ -518,7 +518,12 @@ Naming/MemoizedInstanceVariableName: # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to Naming/MethodParameterName: - Enabled: false + 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' # Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates. # AllowedMethods: call From f35ff995dabb78124ad8ef1689a123d56a6dd6ff Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 28 Sep 2025 22:11:33 -0400 Subject: [PATCH 412/930] Update @sg-ignores --- lib/solargraph/convention/data_definition.rb | 2 +- lib/solargraph/convention/struct_definition.rb | 2 +- lib/solargraph/library.rb | 4 ++-- lib/solargraph/parser/parser_gem/node_chainer.rb | 2 +- lib/solargraph/pin/base_variable.rb | 2 +- lib/solargraph/pin/delegated_method.rb | 2 +- lib/solargraph/pin/parameter.rb | 10 +++++----- lib/solargraph/pin/signature.rb | 6 +++--- lib/solargraph/source.rb | 4 ++-- lib/solargraph/source/chain/literal.rb | 2 +- lib/solargraph/type_checker/rules.rb | 16 ++++++++++------ 11 files changed, 28 insertions(+), 24 deletions(-) diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index 315018b08..c94cd2c6a 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -98,7 +98,7 @@ def attribute_comments(attribute_node, attribute_name) data_comments = comments_for(attribute_node) return if data_comments.nil? || data_comments.empty? - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore flow sensitive typing needs to handle return if foo.nil? || bar data_comments.split("\n").find do |row| row.include?(attribute_name) end&.gsub('@param', '@return')&.gsub(attribute_name, '') diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index 18f13f3ed..142b8daf7 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -142,8 +142,8 @@ def parse_comments # @param tag [YARD::Tags::Tag, nil] The param tag for this attribute.xtract_ # - # @sg-ignore should understand meaning of &. # @return [String] + # @sg-ignore need to improve nil-removal of || def tag_string(tag) tag&.types&.join(',') || 'undefined' end diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 4bbf84e2a..4baeb1d99 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -151,7 +151,7 @@ def delete *filenames # @param filename [String] # @return [void] def close filename - # @sg-ignore should understand meaning of &. + # @sg-ignore need to improve handling of &. return unless @current&.filename == filename @current = nil @@ -269,7 +269,7 @@ def references_from filename, line, column, strip: false, only: false found.select! do |loc| # @sg-ignore Need to add nil check here referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character).first - # @sg-ignore should understand meaning of &. + # @sg-ignore need to improve handling of &. referenced&.path == pin.path end if pin.path == 'Class#new' diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index 55dedcb10..f2a318e6d 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -157,7 +157,7 @@ def hash_is_splatted? node # @param node [Parser::AST::Node] # @return [Source::Chain, nil] def passed_block node - # @sg-ignore Should better support meaning of '&' in RBS + # @sg-ignore need to improve handling of &. return unless node == @node && @parent&.type == :block # @sg-ignore Need to add nil check here diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 487f50916..27abe9ad7 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -122,7 +122,7 @@ def == other end def type_desc - # @sg-ignore should understand meaning of &. + # @sg-ignore need to improve handling of &. "#{super} = #{assignment&.type.inspect}" end diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index d56818bda..8c22599e5 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -15,7 +15,7 @@ class DelegatedMethod < Pin::Method # @param receiver [Source::Chain, nil] the source code used to resolve the receiver for this delegated method. # @param name [String, nil] # @param receiver_method_name [String, nil] the method name that will be called on the receiver (defaults to :name). - # @sg-ignore should understand meaning of &. + # @sg-ignore need to improve handling of &. 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) super(name: name, **splat) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 433ec730f..980c7f13c 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -21,12 +21,12 @@ def initialize decl: :arg, asgn_code: nil, **splat @decl = decl end - # @sg-ignore Should better support meaning of '&' in RBS + # @sg-ignore need to improve nil-removal of || def type_location super || closure&.type_location end - # @sg-ignore Should better support meaning of '&' in RBS + # @sg-ignore need to improve nil-removal of || def location super || closure&.type_location end @@ -152,7 +152,7 @@ def return_type if @return_type.nil? @return_type = ComplexType::UNDEFINED found = param_tag - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" @return_type = ComplexType.try_parse(*found.types) unless found.nil? or found.types.nil? # @sg-ignore Need to add nil check here if @return_type.undefined? @@ -249,7 +249,7 @@ def typify_method_param api_map if found.nil? and !index.nil? found = params[index] if params[index] && (params[index].name.nil? || params[index].name.empty?) end - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore Need to add nil check here return ComplexType.try_parse(*found.types).qualify(api_map, *meth.closure.gates) unless found.nil? || found.types.nil? end ComplexType::UNDEFINED @@ -259,8 +259,8 @@ def typify_method_param api_map # @param api_map [ApiMap] # @param skip [::Array] # + # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" # @return [::Array] - # @sg-ignore Translate to something flow sensitive typing understands def see_reference heredoc, api_map, skip = [] heredoc.ref_tags.each do |ref| next unless ref.tag_name == 'param' && ref.owner diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 004f9bb87..5e70432f9 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -20,17 +20,17 @@ def identity attr_writer :closure - # @sg-ignore Should better support meaning of '&' in RBS + # @sg-ignore need boolish support for ? methods def dodgy_return_type_source? super || closure&.dodgy_return_type_source? end - # @sg-ignore Should better support meaning of '&' in RBS + # @sg-ignore need to improve nil-removal of || def type_location super || closure&.type_location end - # @sg-ignore Should better support meaning of '&' in RBS + # @sg-ignore need to improve nil-removal of || def location super || closure&.location end diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index fc7b92720..c8b523b1b 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -258,11 +258,11 @@ def associated_comments # @type [Integer, nil] last = nil comments.each_pair do |num, snip| - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore need to improve nil-removal of || if !last || num == last + 1 buffer.concat "#{snip.text}\n" else - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore flow sensitive typing needs to handle if !foo result[first_not_empty_from(last + 1)] = buffer.clone buffer.replace "#{snip.text}\n" end diff --git a/lib/solargraph/source/chain/literal.rb b/lib/solargraph/source/chain/literal.rb index bf197386d..a6716ee98 100644 --- a/lib/solargraph/source/chain/literal.rb +++ b/lib/solargraph/source/chain/literal.rb @@ -25,7 +25,7 @@ def initialize type, node end end @type = type - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore flow sensitive typing needs to handle ivars @literal_type = ComplexType.try_parse(@value.inspect) @complex_type = ComplexType.try_parse(type) end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 3dfc6d399..a2ac2b824 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,24 +58,28 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # Need to add nil check here - # @todo flow sensitive typing needs to handle ivars + # @todo 246: Need to add nil check here + # @todo 28: flow sensitive typing needs to handle ivars # @todo 21: Translate to something flow sensitive typing understands # @todo 9: Need to validate config - # @todo 8: Should better support meaning of '&' in RBS # @todo 7: literal arrays in this module turn into ::Solargraph::Source::Chain::Array + # https://github.com/castwide/solargraph/pull/1097 # @todo 7: flow sensitive typing needs to handle inner closures # @todo 6: Need to support nested flow sensitive types # @todo 6: Should handle redefinition of types in simple contexts - # @todo 5: should understand meaning of &. # @todo 5: need boolish support for ? methods - # Need support for reduce_class_type in UniqueType + # @todo 5: need to improve handling of &. + # @todo 5: Need support for reduce_class_type in UniqueType # @todo 4: Need to handle implicit nil on else + # @todo 4: EASY: flow sensitive typing needs to handle "unless foo.nil?" # @todo 3: downcast output of Enumerable#select # @todo 3: EASY: flow sensitive typing needs better handling of ||= on lvars + # @todo 2: Should better support meaning of '&' in RBS # @todo 2: EASY: flow sensitive typing needs to handle "while foo" - # @todo 2: EASY: flow sensitive typing needs to handle "unless foo.nil?" # @todo 2: EASY: flow sensitive typing needs to handle && on both sides + # @todo 1: flow sensitive typing needs to handle if !foo + # @todo 1: need to improve nil-removal of || + # @todo 1: flow sensitive typing needs to handle return if foo.nil? || bar # @todo 1: EASY: flow sensitive typing needs to handle 'raise if' # @todo 1: To make JSON strongly typed we'll need a record syntax # @todo 1: Untyped method Solargraph::Pin::Base#assert_same could not be inferred From 9f9b0500aae1a5caf3e2668387be58e3a3bcf68a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 29 Sep 2025 10:19:01 -0400 Subject: [PATCH 413/930] Add spec for QCall --- spec/source/chain/q_call_spec.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 spec/source/chain/q_call_spec.rb diff --git a/spec/source/chain/q_call_spec.rb b/spec/source/chain/q_call_spec.rb new file mode 100644 index 000000000..a63568358 --- /dev/null +++ b/spec/source/chain/q_call_spec.rb @@ -0,0 +1,23 @@ +describe Solargraph::Source::Chain::QCall do + it 'understands &. in chains' do + source = Solargraph::Source.load_string(%( + # @param a [String, nil] + # @return [String, nil] + def foo a + b = a&.upcase + b + end + + b = foo 123 + b + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + + clip = api_map.clip_at('test.rb', [5, 8]) + expect(clip.infer.to_s).to eq('String, nil') + + clip = api_map.clip_at('test.rb', [9, 6]) + expect(clip.infer.to_s).to eq('String, nil') + end +end From fed75e6ea4603382bb4d6ee95d3c6ee1d403ae59 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 29 Sep 2025 10:28:38 -0400 Subject: [PATCH 414/930] Add spec for ivars --- spec/type_checker/levels/alpha_spec.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 611031d6d..3711c0b37 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -71,5 +71,30 @@ def catalog expect(checker.problems.map(&:message)).to eq([]) end + + it 'knows that ivar references with intermediate calls are not safe' do + checker = type_checker(%( + class Foo + def initialize + # @type [Integer, nil] + @foo = nil + end + + # @return [void] + def twiddle + @foo = nil if rand if rand > 0.5 + end + + # @return [Integer] + def bar + @foo = 123 + twiddle + @foo.round + end + end + )) + + expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round"]) + end end end From 6ec44499426c9eeea2c5030ad00fec529c8f6244 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 29 Sep 2025 10:31:41 -0400 Subject: [PATCH 415/930] Add another spec --- spec/type_checker/levels/alpha_spec.rb | 25 ------------ spec/type_checker/levels/strong_spec.rb | 53 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 3711c0b37..611031d6d 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -71,30 +71,5 @@ def catalog expect(checker.problems.map(&:message)).to eq([]) end - - it 'knows that ivar references with intermediate calls are not safe' do - checker = type_checker(%( - class Foo - def initialize - # @type [Integer, nil] - @foo = nil - end - - # @return [void] - def twiddle - @foo = nil if rand if rand > 0.5 - end - - # @return [Integer] - def bar - @foo = 123 - twiddle - @foo.round - end - end - )) - - expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round"]) - end end end diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 462bc2906..d93a6f4fe 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -599,5 +599,58 @@ def objects_by_class klass expect(checker.problems.map(&:message)).to be_empty end end + + it 'accepts ivar assignments and references with no intermediate calls as safe' do + pending('flow sensitive typing needs to handle ivars correctly') + + checker = type_checker(%( + class Foo + def initialize + # @type [Integer, nil] + @foo = nil + end + + # @return [void] + def twiddle + @foo = nil if rand if rand > 0.5 + end + + # @return [Integer] + def bar + @foo = 123 + out = @foo.round + twiddle + out + end + end + )) + + expect(checker.problems.map(&:message)).to be_empty + end + + it 'knows that ivar references with intermediate calls are not safe' do + checker = type_checker(%( + class Foo + def initialize + # @type [Integer, nil] + @foo = nil + end + + # @return [void] + def twiddle + @foo = nil if rand if rand > 0.5 + end + + # @return [Integer] + def bar + @foo = 123 + twiddle + @foo.round + end + end + )) + + expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round"]) + end end end From 9d1d2d96d06d19ba3af430b6518b1ea01848d7d1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 29 Sep 2025 12:22:58 -0400 Subject: [PATCH 416/930] Handle implicit nil in if/unless --- lib/solargraph/convention/data_definition.rb | 1 - lib/solargraph/convention/struct_definition.rb | 1 - .../parser/parser_gem/node_chainer.rb | 12 +++++++++++- lib/solargraph/type_checker/rules.rb | 1 - lib/solargraph/workspace.rb | 1 - spec/type_checker/levels/strict_spec.rb | 18 ++++++++++++++++-- 6 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index c94cd2c6a..eb4517506 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -81,7 +81,6 @@ def process private - # @sg-ignore Need to handle implicit nil on else # @return [DataDefinition::DataDefintionNode, DataDefinition::DataAssignmentNode, nil] def data_definition_node @data_definition_node ||= if DataDefintionNode.match?(node) diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index 142b8daf7..064d40677 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -105,7 +105,6 @@ def process private - # @sg-ignore Need to handle implicit nil on else # @return [StructDefinition::StructDefintionNode, StructDefinition::StructAssignmentNode, nil] def struct_definition_node @struct_definition_node ||= if StructDefintionNode.match?(node) diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index f2a318e6d..0d8f853b3 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -118,7 +118,17 @@ def generate_links n elsif n.type == :or result.push Chain::Or.new([NodeChainer.chain(n.children[0], @filename), NodeChainer.chain(n.children[1], @filename, n)]) elsif n.type == :if - result.push Chain::If.new([NodeChainer.chain(n.children[1], @filename), NodeChainer.chain(n.children[2], @filename, n)]) + then_clause = if n.children[1] + NodeChainer.chain(n.children[1], @filename, n) + else + Source::Chain.new([Source::Chain::Literal.new('nil', nil)], n) + end + else_clause = if n.children[2] + NodeChainer.chain(n.children[2], @filename, n) + else + Source::Chain.new([Source::Chain::Literal.new('nil', nil)], n) + end + result.push Chain::If.new([then_clause, else_clause]) elsif [:begin, :kwbegin].include?(n.type) result.concat generate_links(n.children.last) elsif n.type == :block_pass diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index a2ac2b824..fd2570e36 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -70,7 +70,6 @@ def require_inferred_type_params? # @todo 5: need boolish support for ? methods # @todo 5: need to improve handling of &. # @todo 5: Need support for reduce_class_type in UniqueType - # @todo 4: Need to handle implicit nil on else # @todo 4: EASY: flow sensitive typing needs to handle "unless foo.nil?" # @todo 3: downcast output of Enumerable#select # @todo 3: EASY: flow sensitive typing needs better handling of ||= on lvars diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 0b39ef89d..63eff654b 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -136,7 +136,6 @@ def rbs_collection_path @gem_rbs_collection ||= read_rbs_collection_path end - # @sg-ignore Need to handle implicit nil on else # @return [String, nil] def rbs_collection_config_path @rbs_collection_config_path ||= begin diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index cb0dd9b49..0951f475c 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -1066,7 +1066,7 @@ def bar expect(checker.problems.map(&:message)).to eq([]) end - it 'does not complain on defaulted reader with detailed expression' do + it 'does not complain on defaulted reader with un-elsed if' do checker = type_checker(%( class Foo # @return [Integer, nil] @@ -1081,7 +1081,21 @@ def bar end )) - pending('Need to handle implicit nil on else') + expect(checker.problems.map(&:message)).to eq([]) + end + + it 'does not complain on defaulted reader with with un-elsed unless' do + checker = type_checker(%( + class Foo + # @return [Integer, nil] + def bar + @bar ||= + unless rand + 123 + end + end + end + )) expect(checker.problems.map(&:message)).to eq([]) end From f440b712be27f80f4ed29f1def65eeb69882ede8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 29 Sep 2025 12:41:18 -0400 Subject: [PATCH 417/930] Add while support to flow sensitive typing --- .../parser/flow_sensitive_typing.rb | 35 +++++++++++++++++++ .../parser_gem/node_processors/while_node.rb | 11 ++++++ lib/solargraph/type_checker/rules.rb | 2 +- spec/type_checker/levels/strong_spec.rb | 15 ++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index c0c41f2a9..c2e490716 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -159,6 +159,41 @@ def process_if(if_node, true_ranges = [], false_ranges = []) process_expression(conditional_node, true_ranges, false_ranges) end + # @param while_node [Parser::AST::Node] + # @param true_ranges [Array] + # @param false_ranges [Array] + # + # @return [void] + def process_while(while_node, true_ranges = [], false_ranges = []) + return if while_node.type != :while + + # + # See if we can refine a type based on the result of 'if foo.nil?' + # + # [3] pry(main)> Parser::CurrentRuby.parse("while a; b; c; end") + # => s(:while, + # s(:send, nil, :a), + # s(:begin, + # s(:send, nil, :b), + # s(:send, nil, :c))) + # [4] pry(main)> + conditional_node = while_node.children[0] + # @type [Parser::AST::Node, nil] + do_clause = while_node.children[1] + + unless do_clause.nil? + # + # If the condition is true we can assume things about the do clause + # + before_do_clause_loc = do_clause.location.expression.adjust(begin_pos: -1) + before_do_clause_pos = Position.new(before_do_clause_loc.line, before_do_clause_loc.column) + true_ranges << Range.new(before_do_clause_pos, + get_node_end_position(do_clause)) + end + + process_expression(conditional_node, true_ranges, false_ranges) + end + class << self include Logging end diff --git a/lib/solargraph/parser/parser_gem/node_processors/while_node.rb b/lib/solargraph/parser/parser_gem/node_processors/while_node.rb index c9211448e..7870fd16c 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/while_node.rb @@ -9,6 +9,17 @@ class WhileNode < Parser::NodeProcessor::Base def process location = get_node_location(node) + 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 + # @sg-ignore downcast output of Enumerable#select + # @type [Solargraph::Pin::CompoundStatementable, nil] + enclosing_compound_statement_pin = pins.select{|pin| pin.is_a?(Pin::CompoundStatementable) && pin.location.range.contain?(position)}.last + FlowSensitiveTyping.new(locals, + enclosing_breakable_pin, + enclosing_compound_statement_pin).process_while(node) + # Note - this should not be considered a block, as the # while statement doesn't create a closure - e.g., # variables created inside can be seen from outside as diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index fd2570e36..cdcdf9c4c 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -74,7 +74,7 @@ def require_inferred_type_params? # @todo 3: downcast output of Enumerable#select # @todo 3: EASY: flow sensitive typing needs better handling of ||= on lvars # @todo 2: Should better support meaning of '&' in RBS - # @todo 2: EASY: flow sensitive typing needs to handle "while foo" + # @todo 2: flow sensitive typing needs to handle "if foo = bar" # @todo 2: EASY: flow sensitive typing needs to handle && on both sides # @todo 1: flow sensitive typing needs to handle if !foo # @todo 1: need to improve nil-removal of || diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index d93a6f4fe..b1568e997 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -600,6 +600,21 @@ def objects_by_class klass end end + it 'handles "while foo" flow sensitive typing correctly' do + checker = type_checker(%( + # @param a [String, nil] + # @return [void] + def foo a = nil + b = a + while b + b.upcase + b = nil if rand > 0.5 + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'accepts ivar assignments and references with no intermediate calls as safe' do pending('flow sensitive typing needs to handle ivars correctly') From 3e6bfbca3be6ccd16e10ee1b76af5791c62909ed Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 29 Sep 2025 12:42:12 -0400 Subject: [PATCH 418/930] Add while support to flow sensitive typing --- lib/solargraph/api_map.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index eb5c16e84..6ff78395f 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -345,7 +345,7 @@ def get_instance_variable_pins(namespace, scope = :instance) result.concat store.get_instance_variables(namespace, scope) sc_fqns = namespace while (sc = store.get_superclass(sc_fqns)) - # @sg-ignore flow sensitive typing needs to handle "while foo"" + # @sg-ignore flow sensitive typing needs to handle "if foo = bar" sc_fqns = store.constants.dereference(sc) result.concat store.get_instance_variables(sc_fqns, scope) end @@ -654,7 +654,7 @@ def super_and_sub?(sup, sub) return true if sup == sub sc_fqns = sub while (sc = store.get_superclass(sc_fqns)) - # @sg-ignore flow sensitive typing needs to handle "while foo"" + # @sg-ignore flow sensitive typing needs to handle "if foo = bar" sc_new = store.constants.dereference(sc) # Cyclical inheritance is invalid return false if sc_new == sc_fqns From b3ab24b60adeb23be5b757cc8516ceeafae747f8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 29 Sep 2025 15:22:02 -0400 Subject: [PATCH 419/930] Relax pin checks in flow sensitive typing --- lib/solargraph/api_map/index.rb | 2 -- .../parser/flow_sensitive_typing.rb | 13 ++++--------- lib/solargraph/pin/base_variable.rb | 2 +- lib/solargraph/pin/parameter.rb | 3 +-- lib/solargraph/source/chain/call.rb | 3 ++- lib/solargraph/type_checker/rules.rb | 5 ++--- spec/type_checker/levels/strong_spec.rb | 19 ++++++++++++++++++- 7 files changed, 28 insertions(+), 19 deletions(-) diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 58f18ac6d..bdad2354c 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -151,9 +151,7 @@ def map_overrides pin.docstring.add_tag(tag) redefine_return_type pin, tag if new_pin - # @sg-ignore flow sensitive typing needs to handle inner closures new_pin.docstring.add_tag(tag) - # @sg-ignore need to do a downcast check on new_pi here redefine_return_type new_pin, tag end end diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index c2e490716..31715fe8f 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -222,18 +222,14 @@ def self.visible_pins(pins, name, closure, location) logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{pins_with_name} - no pins with specific visibility" } return pins_with_name end - visible_pins_specific_to_this_closure = pins_with_specific_visibility.select { |p| p.closure == closure } - if visible_pins_specific_to_this_closure.empty? - logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{pins_with_specific_visibility} - no visible pins specific to this closure (#{closure})}" } - return pins_with_specific_visibility - end + flow_defined_pins = pins_with_specific_visibility.select { |p| p.presence_certain? } if flow_defined_pins.empty? - logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{visible_pins_specific_to_this_closure} - no flow-defined pins" } - return visible_pins_specific_to_this_closure + logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{pins_with_specific_visibility} - no flow-defined pins" } + return pins_with_specific_visibility end - logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{flow_defined_pins}" } + logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{pins_with_specific_visibility}" } flow_defined_pins end @@ -286,7 +282,6 @@ def process_facts(facts_by_pin, presences) nilp = fact.fetch(:nil, nil) not_nilp = fact.fetch(:not_nil, nil) presences.each do |presence| - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" add_downcast_local(pin, downcast_type_name, presence) unless downcast_type_name.nil? add_downcast_local(pin, 'nil', presence) if nilp == true add_downcast_local(pin, :not_nil, presence) if not_nilp == true diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 27abe9ad7..a5fd54ae6 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -122,7 +122,7 @@ def == other end def type_desc - # @sg-ignore need to improve handling of &. + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array "#{super} = #{assignment&.type.inspect}" end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 980c7f13c..fdbd14887 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -152,7 +152,7 @@ def return_type if @return_type.nil? @return_type = ComplexType::UNDEFINED found = param_tag - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs to handle ivars @return_type = ComplexType.try_parse(*found.types) unless found.nil? or found.types.nil? # @sg-ignore Need to add nil check here if @return_type.undefined? @@ -259,7 +259,6 @@ def typify_method_param api_map # @param api_map [ApiMap] # @param skip [::Array] # - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" # @return [::Array] def see_reference heredoc, api_map, skip = [] heredoc.ref_tags.each do |ref| diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 732455376..791a8723f 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -136,6 +136,7 @@ def inferred_pins pins, api_map, name_pin, locals end # @type new_signature_pin [Pin::Signature] new_signature_pin = ol.resolve_generics_from_context_until_complete(ol.generics, atypes, nil, nil, blocktype) + # @sg-ignore Should handle redefinition of types in simple contexts new_return_type = new_signature_pin.return_type if head? # If we're at the head of the chain, we called a @@ -293,7 +294,7 @@ def super_pins api_map, name_pin def yield_pins api_map, name_pin method_pin = find_method_pin(name_pin) return [] unless method_pin - # @sg-ignore flow sensitive typing needs to handle "unless foo.nil?" + # @sg-ignore flow sensitive typing needs to handle inner closures method_pin.signatures.map(&:block).compact.map do |signature_pin| return_type = signature_pin.return_type.qualify(api_map, *name_pin.gates) signature_pin.proxy(return_type) diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index cdcdf9c4c..341388242 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -62,15 +62,14 @@ def require_inferred_type_params? # @todo 28: flow sensitive typing needs to handle ivars # @todo 21: Translate to something flow sensitive typing understands # @todo 9: Need to validate config - # @todo 7: literal arrays in this module turn into ::Solargraph::Source::Chain::Array + # @todo 8: Should handle redefinition of types in simple contexts + # @todo 8: literal arrays in this module turn into ::Solargraph::Source::Chain::Array # https://github.com/castwide/solargraph/pull/1097 # @todo 7: flow sensitive typing needs to handle inner closures # @todo 6: Need to support nested flow sensitive types - # @todo 6: Should handle redefinition of types in simple contexts # @todo 5: need boolish support for ? methods # @todo 5: need to improve handling of &. # @todo 5: Need support for reduce_class_type in UniqueType - # @todo 4: EASY: flow sensitive typing needs to handle "unless foo.nil?" # @todo 3: downcast output of Enumerable#select # @todo 3: EASY: flow sensitive typing needs better handling of ||= on lvars # @todo 2: Should better support meaning of '&' in RBS diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index b1568e997..c06443505 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -611,10 +611,27 @@ def foo a = nil b = nil if rand > 0.5 end end - )) + )) expect(checker.problems.map(&:message)).to be_empty end + it 'does flow sensitive typing even inside a block' do + checker = type_checker(%( + class Quux + # @param foo [String, nil] + # + # @return [void] + def baz(foo) + bar = foo + [].each do + bar.upcase unless bar.nil? + end + end + end)) + + expect(checker.problems.map(&:location).map(&:range).map(&:start)).to be_empty + end + it 'accepts ivar assignments and references with no intermediate calls as safe' do pending('flow sensitive typing needs to handle ivars correctly') From 49072a74efa5019fc5fbb4836eb0a5ca505de2be Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 29 Sep 2025 15:30:04 -0400 Subject: [PATCH 420/930] Set a pending on spec --- spec/type_checker/levels/strong_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index c06443505..a1ad85fbb 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -629,6 +629,8 @@ def baz(foo) end end)) + pending 'flow sensitive typing needs to handle inner closures' + expect(checker.problems.map(&:location).map(&:range).map(&:start)).to be_empty end From 07398d43f2baf1e5090d1715500b3303bbbc3665 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 07:27:13 -0400 Subject: [PATCH 421/930] Exempt a file hit by the unmerged YARD fix --- .rubocop_todo.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ecc699d97..321f8ae52 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -89,13 +89,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 @@ -1290,6 +1283,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 faa57aa5b71898b899033ac1e49263fcecdb920b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 08:32:26 -0400 Subject: [PATCH 422/930] Add another unmerged-Yard-PR issue --- .rubocop_todo.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4745544d0..83339e756 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -89,13 +89,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 @@ -1286,6 +1279,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/pin/method.rb' From b9f29005c3e270a009688e72b68ae8005e99ba33 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 09:37:05 -0400 Subject: [PATCH 423/930] 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 dfabbec1a..d8bdb4424 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -462,7 +462,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 && !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 6ed1150933f25ba8ba6e129dd5a187e7df5abbf9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 10:36:12 -0400 Subject: [PATCH 424/930] Fix merge --- .rubocop_todo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c31316fdd..3377e2f51 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' @@ -1269,6 +1268,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 eabe151b3dc4fce0600d0e5c62850907b74101c2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 10:41:55 -0400 Subject: [PATCH 425/930] 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 426/930] 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 427/930] 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 3df4406de77a159e1997c10a4b7d3d7532745ab2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 11:13:47 -0400 Subject: [PATCH 428/930] Ratchet RuboCop --- .rubocop_todo.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4745544d0..3d48a2371 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -89,13 +89,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 94de7b5c34c1da22e694289ef4f2cb2c4ef7064e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 11:19:06 -0400 Subject: [PATCH 429/930] 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 430/930] 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 2e6aa3f9344c299c22223265bb6d3fd7de4ba123 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 12:25:15 -0400 Subject: [PATCH 431/930] 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 645baf00f..3b7ec74b1 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 66abe83bd0d6d3759aa5e9bac70eff86009f098c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 12:26:53 -0400 Subject: [PATCH 432/930] Drop unneeded @sg-ignore --- 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 c35d6a7e74831f7c3562600b111a5c861658725d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 12:32:45 -0400 Subject: [PATCH 433/930] Drop another @sg-ignore --- lib/solargraph/pin/delegated_method.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index 9483fb058..bcf5b5912 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 7cba7ca66ac466b71f0fb939b7fa8b1ac6d23a70 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 13:46:53 -0400 Subject: [PATCH 434/930] Bump for call.rb complexity --- .rubocop_todo.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 83339e756..35deed756 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' @@ -458,7 +457,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 470c26e1c755eb5c0ebca75c2ead76c8651342e9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 14:20:32 -0400 Subject: [PATCH 435/930] Mark spec as now working --- 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 06eade69c5654f7b20b739d4e781f0c78b2800e5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 16:49:13 -0400 Subject: [PATCH 436/930] Drop sg-ignores --- lib/solargraph/source/chain/call.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index b42d76cc0..22e1a8043 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -56,19 +56,15 @@ def resolve api_map, name_pin, locals [] end return inferred_pins(found, api_map, name_pin, locals) unless found.empty? - # @sg-ignore Unresolved call to map on void, ::Enumerator<::Solargraph::ComplexType::UniqueType> pin_groups = name_pin.binder.each_unique_type.map do |context| ns_tag = context.namespace == '' ? '' : context.namespace_type.tag stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) [stack.first].compact end - # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array if !api_map.loose_unions && pin_groups.any? { |pins| pins.empty? } pin_groups = [] end - # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array pins = pin_groups.flatten.uniq(&:path) - # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array return [] if pins.empty? inferred_pins(pins, api_map, name_pin, locals) end From 94b62539e8515269d580b8b87de12ffac8dc064b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 20:20:43 -0400 Subject: [PATCH 437/930] Recategorize some things --- lib/solargraph/api_map/source_to_yard.rb | 14 +++++++------- lib/solargraph/complex_type/unique_type.rb | 2 +- lib/solargraph/parser/node_processor/base.rb | 2 +- lib/solargraph/pin/parameter.rb | 2 +- lib/solargraph/type_checker.rb | 2 +- lib/solargraph/type_checker/rules.rb | 6 +++--- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index b7858a964..76aa3ca8f 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -36,16 +36,16 @@ def rake_yard store end if pin.type == :class code_object_map[pin.path] ||= YARD::CodeObjects::ClassObject.new(root_code_object, pin.path) { |obj| - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore flow sensitive typing needs to handle ivars next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore flow sensitive typing needs to handle ivars obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) } else code_object_map[pin.path] ||= YARD::CodeObjects::ModuleObject.new(root_code_object, pin.path) { |obj| - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore flow sensitive typing needs to handle ivars next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore flow sensitive typing needs to handle ivars obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) } end @@ -53,7 +53,7 @@ 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? - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore flow sensitive typing needs to handle return if foo.nil? || bar include_object.instance_mixins.push code_object_map[ref.parametrized_tag.to_s] end end @@ -74,9 +74,9 @@ def rake_yard store # @sg-ignore Need to add nil check here code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope) { |obj| - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore flow sensitive typing needs to handle ivars next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore flow sensitive typing needs to handle ivars obj.add_file pin.location.filename, pin.location.range.start.line } method_object = code_object_at(pin.path, YARD::CodeObjects::MethodObject) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 270cdbaaa..e2700e4ee 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -409,7 +409,7 @@ def resolve_generics definitions, context_type ComplexType::UNDEFINED end else - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore Need to add nil check here context_type.all_params[idx] || definitions.generic_defaults[generic_name] || ComplexType::UNDEFINED end else diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index b7fb230cd..967f91fdb 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -68,7 +68,7 @@ def comments_for(node) # @return [Pin::Closure, nil] def named_path_pin position pins.select do |pin| - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore Need to add nil check here pin.is_a?(Pin::Closure) && pin.path && !pin.path.empty? && pin.location.range.contain?(position) end.last end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 39d9e0211..f0f1fa1e2 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -204,7 +204,7 @@ def compatible_arg?(atype, api_map) def documentation tag = param_tag return '' if tag.nil? || tag.text.nil? - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore flow sensitive typing needs to handle ivars tag.text end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 11b1b7c39..813b3906c 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -655,7 +655,7 @@ def param_details_from_stack(signature, method_pin_stack) # @param pin [Pin::Base] def internal? pin return false if pin.nil? - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore flow sensitive typing needs to handle ivars pin.location && api_map.bundled?(pin.location.filename) end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 44d49b237..ffce8a5d8 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,9 +58,8 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo 258: Need to add nil check here - # @todo 28: flow sensitive typing needs to handle ivars - # @todo 15: Translate to something flow sensitive typing understands + # @todo 260: Need to add nil check here + # @todo 39: flow sensitive typing needs to handle ivars # @todo 9: Need to validate config # @todo 8: Should handle redefinition of types in simple contexts # @todo 7: Need support for reduce_class_type in UniqueType @@ -69,6 +68,7 @@ def require_inferred_type_params? # @todo 5: need boolish support for ? methods # @todo 5: need to improve handling of &. # @todo 5: flow sensitive typing needs to handle return if foo.nil? || bar + # @todo 4: Translate to something flow sensitive typing understands # @todo 3: downcast output of Enumerable#select # @todo 3: EASY: flow sensitive typing needs better handling of ||= on lvars # @todo 3: EASY: flow sensitive typing needs to handle 'raise if' From d486e6485d4cce9d182da079c588c3821c69cfa6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Sep 2025 22:36:37 -0400 Subject: [PATCH 438/930] 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 439/930] 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 188d4457fe6d9b0be8e31e945c4203cfbbfa88e8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 09:48:46 -0400 Subject: [PATCH 440/930] Refactor some flow sensitive typing boilerplate --- lib/solargraph/parser/node_processor/base.rb | 22 +++++++++++++++++++ .../parser_gem/node_processors/and_node.rb | 7 ------ .../parser_gem/node_processors/if_node.rb | 7 ------ .../parser_gem/node_processors/or_node.rb | 7 ------ .../parser_gem/node_processors/while_node.rb | 8 ------- 5 files changed, 22 insertions(+), 29 deletions(-) diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index 967f91fdb..6ef1dfc96 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -40,6 +40,28 @@ def process private + # @return [Solargraph::Location] + def location + get_node_location(node) + end + + # @return [Solargraph::Position] + def position + Position.new(node.loc.line, node.loc.column) + end + + # @sg-ignore downcast output of Enumerable#select + # @return [Solargraph::Pin::Breakable, nil] + def enclosing_breakable_pin + pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location.range.contain?(position)}.last + end + + # @sg-ignore downcast output of Enumerable#select + # @return [Solargraph::Pin::CompoundStatementable, nil] + def enclosing_compound_statement_pin + pins.select{|pin| pin.is_a?(Pin::CompoundStatementable) && pin.location.range.contain?(position)}.last + end + # @param subregion [Region] # @return [void] def process_children subregion = region diff --git a/lib/solargraph/parser/parser_gem/node_processors/and_node.rb b/lib/solargraph/parser/parser_gem/node_processors/and_node.rb index bce5c06f5..085c0c68a 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/and_node.rb @@ -10,13 +10,6 @@ class AndNode < Parser::NodeProcessor::Base 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 - # @sg-ignore downcast output of Enumerable#select - # @type [Pin::CompoundStatementable] - enclosing_compound_statement_pin = pins.select{|pin| pin.is_a?(Solargraph::Pin::CompoundStatementable) && pin.location.range.contain?(position)}.last FlowSensitiveTyping.new(locals, enclosing_breakable_pin, enclosing_compound_statement_pin).process_and(node) 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 ba65a0388..7d346ffed 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb @@ -10,13 +10,6 @@ class IfNode < Parser::NodeProcessor::Base 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 - # @sg-ignore downcast output of Enumerable#select - # @type [Solargraph::Pin::CompoundStatementable, nil] - enclosing_compound_statement_pin = pins.select{|pin| pin.is_a?(Pin::CompoundStatementable) && pin.location.range.contain?(position)}.last FlowSensitiveTyping.new(locals, enclosing_breakable_pin, enclosing_compound_statement_pin).process_if(node) diff --git a/lib/solargraph/parser/parser_gem/node_processors/or_node.rb b/lib/solargraph/parser/parser_gem/node_processors/or_node.rb index a4a8ad232..c77bad1d6 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/or_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/or_node.rb @@ -10,13 +10,6 @@ class OrNode < Parser::NodeProcessor::Base 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 - # @sg-ignore downcast output of Enumerable#select - # @type [Pin::CompoundStatementable] - enclosing_compound_statement_pin = pins.select{|pin| pin.is_a?(Solargraph::Pin::CompoundStatementable) && pin.location.range.contain?(position)}.last FlowSensitiveTyping.new(locals, enclosing_breakable_pin, enclosing_compound_statement_pin).process_or(node) diff --git a/lib/solargraph/parser/parser_gem/node_processors/while_node.rb b/lib/solargraph/parser/parser_gem/node_processors/while_node.rb index 7870fd16c..4b025121e 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/while_node.rb @@ -8,14 +8,6 @@ class WhileNode < Parser::NodeProcessor::Base include ParserGem::NodeMethods def process - location = get_node_location(node) - 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 - # @sg-ignore downcast output of Enumerable#select - # @type [Solargraph::Pin::CompoundStatementable, nil] - enclosing_compound_statement_pin = pins.select{|pin| pin.is_a?(Pin::CompoundStatementable) && pin.location.range.contain?(position)}.last FlowSensitiveTyping.new(locals, enclosing_breakable_pin, enclosing_compound_statement_pin).process_while(node) From 06fe6f1f648e27e97d27437839c3ccab7cefba90 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 09:52:53 -0400 Subject: [PATCH 441/930] Pass location to InstanceVariable links for future use --- lib/solargraph/parser/parser_gem/node_chainer.rb | 2 +- lib/solargraph/source/chain/instance_variable.rb | 9 ++++++++- spec/source/chain/instance_variable_spec.rb | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index 0d8f853b3..7121b145d 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -94,7 +94,7 @@ def generate_links n elsif [:lvar, :lvasgn].include?(n.type) result.push Chain::Call.new(n.children[0].to_s, Location.from_node(n)) elsif [:ivar, :ivasgn].include?(n.type) - result.push Chain::InstanceVariable.new(n.children[0].to_s, n) + result.push Chain::InstanceVariable.new(n.children[0].to_s, n, Location.from_node(n)) elsif [:cvar, :cvasgn].include?(n.type) result.push Chain::ClassVariable.new(n.children[0].to_s) elsif [:gvar, :gvasgn].include?(n.type) diff --git a/lib/solargraph/source/chain/instance_variable.rb b/lib/solargraph/source/chain/instance_variable.rb index 75c31f2ce..f6d80f359 100644 --- a/lib/solargraph/source/chain/instance_variable.rb +++ b/lib/solargraph/source/chain/instance_variable.rb @@ -6,14 +6,21 @@ class Chain class InstanceVariable < Link # @param word [String] # @param node [Parser::AST::Node, nil] The node representing the variable - def initialize word, node + # @param location [Location, nil] The location of the variable reference in the source + def initialize word, node, location super(word) @node = node + @location = location end def resolve api_map, name_pin, locals api_map.get_instance_variable_pins(name_pin.binder.namespace, name_pin.binder.scope).select{|p| p.name == word} end + private + + # TODO: This should fail typechecking - ivar is nullable + # @return [Location] + attr_reader :location end end end diff --git a/spec/source/chain/instance_variable_spec.rb b/spec/source/chain/instance_variable_spec.rb index d24fbb235..8581cfda8 100644 --- a/spec/source/chain/instance_variable_spec.rb +++ b/spec/source/chain/instance_variable_spec.rb @@ -6,7 +6,7 @@ bar_pin = Solargraph::Pin::InstanceVariable.new(closure: closure, name: '@foo') api_map = Solargraph::ApiMap.new api_map.index [closure, methpin, foo_pin, bar_pin] - link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil) + link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil, nil) pins = link.resolve(api_map, methpin, []) expect(pins.length).to eq(1) expect(pins.first.name).to eq('@foo') From f8f0496dcadd956995031b831143be92f7aceebc Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 10:09:41 -0400 Subject: [PATCH 442/930] Fix typechecking issues --- lib/solargraph/parser/node_processor/base.rb | 4 ++-- lib/solargraph/pin/compound_statementable.rb | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index 6ef1dfc96..109f31fde 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -53,13 +53,13 @@ def position # @sg-ignore downcast output of Enumerable#select # @return [Solargraph::Pin::Breakable, nil] def enclosing_breakable_pin - pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location.range.contain?(position)}.last + pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location&.range&.contain?(position)}.last end # @sg-ignore downcast output of Enumerable#select # @return [Solargraph::Pin::CompoundStatementable, nil] def enclosing_compound_statement_pin - pins.select{|pin| pin.is_a?(Pin::CompoundStatementable) && pin.location.range.contain?(position)}.last + pins.select{|pin| pin.is_a?(Pin::CompoundStatementable) && pin.location&.range&.contain?(position)}.last end # @param subregion [Region] diff --git a/lib/solargraph/pin/compound_statementable.rb b/lib/solargraph/pin/compound_statementable.rb index e952ca2b6..debad6615 100644 --- a/lib/solargraph/pin/compound_statementable.rb +++ b/lib/solargraph/pin/compound_statementable.rb @@ -42,6 +42,9 @@ module Pin module CompoundStatementable # @return [Parser::AST::Node] attr_reader :node + + # @return [Location, nil] + attr_reader :location end end end From 5308390bbcf4cc7e671a1b8ddfbfd21b3e86345b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 10:10:50 -0400 Subject: [PATCH 443/930] Linting fix --- lib/solargraph/source/chain/instance_variable.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/source/chain/instance_variable.rb b/lib/solargraph/source/chain/instance_variable.rb index f6d80f359..a67be12bc 100644 --- a/lib/solargraph/source/chain/instance_variable.rb +++ b/lib/solargraph/source/chain/instance_variable.rb @@ -16,6 +16,7 @@ def initialize word, node, location def resolve api_map, name_pin, locals api_map.get_instance_variable_pins(name_pin.binder.namespace, name_pin.binder.scope).select{|p| p.name == word} end + private # TODO: This should fail typechecking - ivar is nullable From dd6a6d9b9c28a3ea39bd8756b922988d189ce0ab Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 11:33:59 -0400 Subject: [PATCH 444/930] Add a TODO --- lib/solargraph/pin/base_variable.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index a5fd54ae6..99de58330 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -131,6 +131,7 @@ def type_desc # @return [ComplexType] def generate_complex_type tag = docstring.tag(:type) + # TODO this should be complaining about returning nil return ComplexType.try_parse(*tag.types) unless tag.nil? || tag.types.nil? || tag.types.empty? ComplexType.new end From 157b5dfdc00910544fb3fc151430a3650d43d12b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 11:34:46 -0400 Subject: [PATCH 445/930] Never mind --- lib/solargraph/pin/base_variable.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 99de58330..a5fd54ae6 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -131,7 +131,6 @@ def type_desc # @return [ComplexType] def generate_complex_type tag = docstring.tag(:type) - # TODO this should be complaining about returning nil return ComplexType.try_parse(*tag.types) unless tag.nil? || tag.types.nil? || tag.types.empty? ComplexType.new end From fa3ac41bfccd04e1555ce36a8acab9cc9d26bfe3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 11:53:14 -0400 Subject: [PATCH 446/930] Start moving presence info into BaseVariable pin --- lib/solargraph/pin/base_variable.rb | 20 +++++++++++++++++++- lib/solargraph/pin/local_variable.rb | 26 -------------------------- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index a5fd54ae6..06e2b464f 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -11,14 +11,32 @@ class BaseVariable < Base attr_accessor :mass_assignment + # @return [Range, nil] + attr_reader :presence + + # @return [Boolean] + attr_reader :presence_certain + + def presence_certain? + @presence_certain + end + + # @param presence [Range, nil] + # @param presence_certain [Boolean] # @param return_type [ComplexType, nil] + # @param exclude_return_type [ComplexType, nil] Ensure any return + # type returned will never include these unique types in the + # unique types of its complex type # @param assignment [Parser::AST::Node, nil] - def initialize assignment: nil, return_type: nil, **splat + def initialize assignment: nil, presence: nil, presence_certain: false, return_type: nil, exclude_return_type: nil, **splat super(**splat) @assignment = assignment # @type [nil, ::Array(Parser::AST::Node, Integer)] @mass_assignment = nil @return_type = return_type + @presence = presence + @presence_certain = presence_certain + @exclude_return_type = exclude_return_type end def combine_with(other, attrs={}) diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 15b656dfd..04c3e9b4b 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -3,32 +3,6 @@ module Solargraph module Pin class LocalVariable < BaseVariable - # @return [Range] - attr_reader :presence - - # @return [Boolean] - attr_reader :presence_certain - - def presence_certain? - @presence_certain - end - - # @param assignment [AST::Node, nil] - # @param presence [Range, nil] - # @param presence_certain [Boolean] - # @param exclude_return_type [ComplexType, nil] Ensure any return - # type returned will never include these unique types in the - # unique types of its complex type - # @param splat [Hash] - def initialize assignment: nil, presence: nil, presence_certain: false, - exclude_return_type: nil, **splat - super(**splat) - @assignment = assignment - @presence = presence - @presence_certain = presence_certain - @exclude_return_type = exclude_return_type - end - def combine_with(other, attrs={}) new_attrs = { assignment: assert_same(other, :assignment), From 7e7e2b0d8a2bcbb507bdf26b008ac8d06e8885b1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 12:11:23 -0400 Subject: [PATCH 447/930] Move up some private methods --- lib/solargraph/pin/base_variable.rb | 47 ++++++++++++++++++++++++++++ lib/solargraph/pin/local_variable.rb | 44 -------------------------- 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 06e2b464f..0d794aba1 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -146,6 +146,53 @@ def type_desc private + # @param needle [Pin::Base] + # @param haystack [Pin::Base] + # @return [Boolean] + def match_named_closure needle, haystack + return true if needle == haystack || haystack.is_a?(Pin::Block) + cursor = haystack + until cursor.nil? + return true if needle.path == cursor.path + return false if cursor.path && !cursor.path.empty? + # @sg-ignore Need to add nil check here + cursor = cursor.closure + end + false + end + + # @param raw_return_type [ComplexType, nil] + # @return [ComplexType, nil] + def return_type_minus_exclusions(raw_return_type) + @return_type_minus_exclusions ||= + if exclude_return_type && raw_return_type + # @sg-ignore flow sensitive typing needs to handle ivars + types = raw_return_type.items - exclude_return_type.items + types = [ComplexType::UniqueType::UNDEFINED] if types.empty? + ComplexType.new(types) + else + raw_return_type + end + @return_type_minus_exclusions + end + + # @param other [self] + # @param attr [::Symbol] + # + # @return [ComplexType, nil] + def combine_types(other, attr) + # @type [ComplexType, nil] + type1 = send(attr) + # @type [ComplexType, nil] + type2 = other.send(attr) + if type1 && type2 + types = (type1.items + type2.items).uniq + ComplexType.new(types) + else + type1 || type2 + end + end + # @return [ComplexType] def generate_complex_type tag = docstring.tag(:type) diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 04c3e9b4b..3391caf01 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -15,23 +15,6 @@ def combine_with(other, attrs={}) super(other, new_attrs) end - # @param other [self] - # @param attr [::Symbol] - # - # @return [ComplexType, nil] - def combine_types(other, attr) - # @type [ComplexType, nil] - type1 = send(attr) - # @type [ComplexType, nil] - type2 = other.send(attr) - if type1 && type2 - types = (type1.items + type2.items).uniq - ComplexType.new(types) - else - type1 || type2 - end - end - def reset_generated! @return_type_minus_exclusions = nil super @@ -74,33 +57,6 @@ def to_rbs private attr_reader :exclude_return_type - - # @param tag1 [String] - # @param tag2 [String] - # @return [Boolean] - def match_tags tag1, tag2 - # @todo This is an unfortunate hack made necessary by a discrepancy in - # how tags indicate the root namespace. The long-term solution is to - # standardize it, whether it's `Class<>`, an empty string, or - # something else. - tag1 == tag2 || - (['', 'Class<>'].include?(tag1) && ['', 'Class<>'].include?(tag2)) - end - - # @param needle [Pin::Base] - # @param haystack [Pin::Base] - # @return [Boolean] - def match_named_closure needle, haystack - return true if needle == haystack || haystack.is_a?(Pin::Block) - cursor = haystack - until cursor.nil? - return true if needle.path == cursor.path - return false if cursor.path && !cursor.path.empty? - # @sg-ignore Need to add nil check here - cursor = cursor.closure - end - false - end end end end From 641b35312993e9e34887f0b1332406360de9fac7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 12:22:40 -0400 Subject: [PATCH 448/930] Fix typechecking issues --- lib/solargraph/parser/flow_sensitive_typing.rb | 2 +- lib/solargraph/pin/local_variable.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 31715fe8f..4ce86eb05 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -347,7 +347,7 @@ def parse_isa(isa_node) # # @return [Solargraph::Pin::LocalVariable, nil] def find_local(variable_name, position) - pins = locals.select { |pin| pin.name == variable_name && pin.presence.include?(position) } + pins = locals.select { |pin| pin.name == variable_name && pin.presence&.include?(position) } # return unless pins.length == 1 pins.first end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 3391caf01..9d84ef41d 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -45,7 +45,7 @@ def return_type_minus_exclusions(raw_return_type) def visible_at?(other_closure, other_loc) # @sg-ignore Need to add nil check here location.filename == other_loc.filename && - presence.include?(other_loc.range.start) && + presence&.include?(other_loc.range.start) && # @sg-ignore Need to add nil check here match_named_closure(other_closure, closure) end From 711a78d69ea3d2e207d4cd72cead205fe1d929da Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 13:16:33 -0400 Subject: [PATCH 449/930] Add a little more to base_variable.rb --- lib/solargraph/pin/base_variable.rb | 14 +++++++++----- lib/solargraph/type_checker/rules.rb | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 0d794aba1..3e44fd58a 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -57,10 +57,6 @@ def symbol_kind Solargraph::LanguageServer::SymbolKinds::VARIABLE end - def return_type - @return_type ||= generate_complex_type - end - def nil_assignment? # this will always be false - should it be return_type == # ComplexType::NIL or somesuch? @@ -105,8 +101,9 @@ def exclude_return_type # @param api_map [ApiMap] # @return [ComplexType] def probe api_map - if presence_certain? && return_type.defined? + if presence_certain? && return_type&.defined? # flow sensitive typing has already figured out this type + # @sg-ignore need to improve handling of &. return return_type.qualify(api_map, *gates) end @@ -144,8 +141,15 @@ def type_desc "#{super} = #{assignment&.type.inspect}" end + # @return [ComplexType, nil] + def return_type + return_type_minus_exclusions(@return_type || generate_complex_type) + end + private + attr_reader :exclude_return_type + # @param needle [Pin::Base] # @param haystack [Pin::Base] # @return [Boolean] diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index ffce8a5d8..4b8c75bdf 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -66,7 +66,7 @@ def require_inferred_type_params? # @todo 7: flow sensitive typing needs to handle inner closures # @todo 6: Need to support nested flow sensitive types # @todo 5: need boolish support for ? methods - # @todo 5: need to improve handling of &. + # need to improve handling of &. # @todo 5: flow sensitive typing needs to handle return if foo.nil? || bar # @todo 4: Translate to something flow sensitive typing understands # @todo 3: downcast output of Enumerable#select From 91510c62b58eca97d4ae6981b09722528efeb10c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 13:24:35 -0400 Subject: [PATCH 450/930] Add a little more to base_variable.rb --- lib/solargraph/pin/base_variable.rb | 15 ++++++++++++ lib/solargraph/pin/local_variable.rb | 35 ---------------------------- 2 files changed, 15 insertions(+), 35 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 3e44fd58a..e16688f72 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -39,6 +39,11 @@ def initialize assignment: nil, presence: nil, presence_certain: false, return_t @exclude_return_type = exclude_return_type end + def reset_generated! + @return_type_minus_exclusions = nil + super + end + def combine_with(other, attrs={}) attrs.merge({ assignment: assert_same(other, :assignment), @@ -146,6 +151,16 @@ def return_type return_type_minus_exclusions(@return_type || generate_complex_type) end + # @param other_closure [Pin::Closure] + # @param other_loc [Location] + def visible_at?(other_closure, other_loc) + # @sg-ignore Need to add nil check here + location.filename == other_loc.filename && + presence&.include?(other_loc.range.start) && + # @sg-ignore Need to add nil check here + match_named_closure(other_closure, closure) + end + private attr_reader :exclude_return_type diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 9d84ef41d..9085767ad 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -15,41 +15,6 @@ def combine_with(other, attrs={}) super(other, new_attrs) end - def reset_generated! - @return_type_minus_exclusions = nil - super - end - - # @return [ComplexType, nil] - def return_type - @return_type = return_type_minus_exclusions(super) - end - - # @param raw_return_type [ComplexType, nil] - # @return [ComplexType, nil] - def return_type_minus_exclusions(raw_return_type) - @return_type_minus_exclusions ||= - if exclude_return_type && raw_return_type - # @sg-ignore flow sensitive typing needs to handle ivars - types = raw_return_type.items - exclude_return_type.items - types = [ComplexType::UniqueType::UNDEFINED] if types.empty? - ComplexType.new(types) - else - raw_return_type - end - @return_type_minus_exclusions - end - - # @param other_closure [Pin::Closure] - # @param other_loc [Location] - def visible_at?(other_closure, other_loc) - # @sg-ignore Need to add nil check here - location.filename == other_loc.filename && - presence&.include?(other_loc.range.start) && - # @sg-ignore Need to add nil check here - match_named_closure(other_closure, closure) - end - def to_rbs (name || '(anon)') + ' ' + (return_type&.to_rbs || 'untyped') end From ed0e064f7d9a5bb4cdf04fba1d6420fae15129f0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 14:33:11 -0400 Subject: [PATCH 451/930] Fix typechecking issues --- 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 813b3906c..ee32f9d8c 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -234,7 +234,7 @@ def ignored_pins def variable_type_tag_problems result = [] all_variables.each do |pin| - if pin.return_type.defined? + if pin.return_type&.defined? declared = pin.typify(api_map) next if declared.duck_type? if declared.defined? From 15ec0cde143091bbfbd0c66e309dfaca70041e1a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 14:39:09 -0400 Subject: [PATCH 452/930] Move final bits to BaseVariable --- lib/solargraph/pin/base_variable.rb | 18 +++++++++++++----- lib/solargraph/pin/local_variable.rb | 16 ---------------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index e16688f72..75b46d989 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -53,6 +53,18 @@ def combine_with(other, attrs={}) super(other, attrs) end + def combine_with(other, attrs={}) + new_attrs = { + assignment: assert_same(other, :assignment), + presence_certain: assert_same(other, :presence_certain?), + exclude_return_type: combine_types(other, :exclude_return_type), + }.merge(attrs) + new_attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence) + new_attrs[:presence_certain] = assert_same(other, :presence_certain) unless attrs.key?(:presence_certain) + + super(other, new_attrs) + end + def completion_item_kind Solargraph::LanguageServer::CompletionItemKinds::VARIABLE end @@ -98,10 +110,7 @@ def return_types_from_node(parent_node, api_map) types end - # @return [ComplexType, nil] - def exclude_return_type - nil - end + attr_reader :exclude_return_type # @param api_map [ApiMap] # @return [ComplexType] @@ -185,7 +194,6 @@ def match_named_closure needle, haystack def return_type_minus_exclusions(raw_return_type) @return_type_minus_exclusions ||= if exclude_return_type && raw_return_type - # @sg-ignore flow sensitive typing needs to handle ivars types = raw_return_type.items - exclude_return_type.items types = [ComplexType::UniqueType::UNDEFINED] if types.empty? ComplexType.new(types) diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 9085767ad..193964e01 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -3,25 +3,9 @@ module Solargraph module Pin class LocalVariable < BaseVariable - def combine_with(other, attrs={}) - new_attrs = { - assignment: assert_same(other, :assignment), - presence_certain: assert_same(other, :presence_certain?), - exclude_return_type: combine_types(other, :exclude_return_type), - }.merge(attrs) - new_attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence) - new_attrs[:presence_certain] = assert_same(other, :presence_certain) unless attrs.key?(:presence_certain) - - super(other, new_attrs) - end - def to_rbs (name || '(anon)') + ' ' + (return_type&.to_rbs || 'untyped') end - - private - - attr_reader :exclude_return_type end end end From 97c93be26b8c78dc093a9be6cf0cd7684520e234 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 1 Oct 2025 14:53:59 -0400 Subject: [PATCH 453/930] Merge functions --- lib/solargraph/pin/base_variable.rb | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 75b46d989..e74a74d9a 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -50,21 +50,11 @@ def combine_with(other, attrs={}) mass_assignment: assert_same(other, :mass_assignment), return_type: combine_return_type(other), }) + attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence) + attrs[:presence_certain] = assert_same(other, :presence_certain) unless attrs.key?(:presence_certain) super(other, attrs) end - def combine_with(other, attrs={}) - new_attrs = { - assignment: assert_same(other, :assignment), - presence_certain: assert_same(other, :presence_certain?), - exclude_return_type: combine_types(other, :exclude_return_type), - }.merge(attrs) - new_attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence) - new_attrs[:presence_certain] = assert_same(other, :presence_certain) unless attrs.key?(:presence_certain) - - super(other, new_attrs) - end - def completion_item_kind Solargraph::LanguageServer::CompletionItemKinds::VARIABLE end From 152dbdfe446088f8c997c55d663321e264bf5ed2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 5 Oct 2025 17:43:51 -0400 Subject: [PATCH 454/930] Annotation fixes for strong typechecking --- lib/solargraph/api_map/index.rb | 6 ++++-- lib/solargraph/doc_map.rb | 3 +-- .../parser/parser_gem/node_processors/opasgn_node.rb | 2 ++ lib/solargraph/pin/local_variable.rb | 3 --- lib/solargraph/position.rb | 1 - lib/solargraph/source_map.rb | 4 +++- lib/solargraph/source_map/mapper.rb | 2 -- lib/solargraph/workspace/config.rb | 6 ++++++ 8 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 35d86446a..cb61a28eb 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -114,8 +114,10 @@ def catalog new_pins self end - # @param klass [Class] - # @param hash [Hash{String => Array}] + # @generic T + # @param klass [Class] + # @param hash [Hash{String => T}] + # # @return [void] def map_references klass, hash pins_by_class(klass).each do |pin| diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 5966717f4..05f2f1647 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -128,7 +128,7 @@ 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 + # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version def self.all_yard_gems_in_memory @yard_gems_in_memory ||= {} end @@ -180,7 +180,6 @@ def load_serialized_gem_pins # @sg-ignore Need support for RBS duck interfaces like _ToHash # @type [Array] 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 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..a4359af9d 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,10 @@ def process operator = node.children[1] argument = node.children[2] if target.type == :send + # @sg-ignore Need a downcast here process_send_target(target, operator, argument) elsif target.type.to_s.end_with?('vasgn') + # @sg-ignore Need a downcast here process_vasgn_target(target, operator, argument) else Solargraph.assert_or_log(:opasgn_unknown_target, diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 9eae6cc6f..cb2dda140 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) diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 2faa0a99b..1197038ef 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -58,7 +58,6 @@ 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.rb b/lib/solargraph/source_map.rb index d7b6fb4fc..15b747760 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -41,11 +41,13 @@ def initialize source # solargraph-rails is known to use this method to get the document symbols. It should probably be removed. @document_symbols = nil self.convention_pins = conventions_environ.pins + # @type [Hash{Class => Array}] @pin_select_cache = {} end # @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 @@ -171,10 +173,10 @@ def map source private - # @return [Hash{Class => Array}] # @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 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| diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index d1e6c27b5..3e30e5d74 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -63,6 +63,7 @@ def calculated # namespace. It's typically used to identify available DSLs. # # @return [Array] + # @sg-ignore Need to validate config def domains raw_data['domains'] end @@ -70,6 +71,7 @@ def domains # An array of required paths to add to the workspace. # # @return [Array] + # @sg-ignore Need to validate config def required raw_data['require'] end @@ -83,6 +85,7 @@ def require_paths # An array of reporters to use for diagnostics. # + # @sg-ignore Need to validate 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 to validate config # @return [Hash] def formatter raw_data['formatter'] @@ -97,6 +101,7 @@ def formatter # An array of plugins to require. # + # @sg-ignore Need to validate 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 to validate config # @return [Integer] def max_files raw_data['max_files'] From 94e650bf298482a79e6139eedd50a21916fd0c14 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 5 Oct 2025 18:01:26 -0400 Subject: [PATCH 455/930] Annotation fixes --- lib/solargraph/doc_map.rb | 4 ++-- lib/solargraph/pin/base.rb | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 322ab8970..5dcf28552 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -128,7 +128,7 @@ 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 + # @return [Hash{Array(String, String) => Array}] Indexed by gemspec name and version def self.all_yard_gems_in_memory @yard_gems_in_memory ||= {} end @@ -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 diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index d55ba79d8..f71d93be3 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -309,6 +309,7 @@ def assert_same_count(other, attr) # @param other [self] # @param attr [::Symbol] # + # @sg-ignore # @return [undefined] def assert_same(other, attr) if other.nil? From ac05669348b9e5518a2defd0c4fc207393567bf5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 5 Oct 2025 18:57:44 -0400 Subject: [PATCH 456/930] 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 457/930] 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 458/930] 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 459/930] 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 460/930] 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 461/930] 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 462/930] 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 463/930] 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 464/930] 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 465/930] 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 466/930] 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 467/930] 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 468/930] 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 9365ab7ebab39e078e001982c053a50666f3a74e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 16:26:03 -0400 Subject: [PATCH 469/930] Update rules --- lib/solargraph/type_checker/rules.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 4b8c75bdf..96eebf3e5 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -66,16 +66,16 @@ def require_inferred_type_params? # @todo 7: flow sensitive typing needs to handle inner closures # @todo 6: Need to support nested flow sensitive types # @todo 5: need boolish support for ? methods - # need to improve handling of &. + # @todo 5: need to improve handling of &. # @todo 5: flow sensitive typing needs to handle return if foo.nil? || bar # @todo 4: Translate to something flow sensitive typing understands # @todo 3: downcast output of Enumerable#select - # @todo 3: EASY: flow sensitive typing needs better handling of ||= on lvars - # @todo 3: EASY: flow sensitive typing needs to handle 'raise if' + # @todo 3: flow sensitive typing needs better handling of ||= on lvars + # @todo 3: flow sensitive typing needs to handle 'raise if' # @todo 2: Need to look at Tuple#include? handling # @todo 2: Should better support meaning of '&' in RBS # @todo 2: flow sensitive typing needs to handle "if foo = bar" - # @todo 2: EASY: flow sensitive typing needs to handle && on both sides + # @todo 2: flow sensitive typing needs to handle && on both sides # @todo 2: Need a downcast here # @todo 1: Need to look at infer handling of recursive methods # @todo 1: flow sensitive typing needs to handle if !foo From 81ae528a633f654b67414eaea87b56ab8b796203 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 6 Oct 2025 17:49:55 -0400 Subject: [PATCH 470/930] 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 471/930] 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 472/930] 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 7b4982392773415b9e25bafddebeefedad81e61e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 07:29:09 -0400 Subject: [PATCH 473/930] 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 b2a562afd0d282a8eb9282783b994d16b6186fa0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 07:41:51 -0400 Subject: [PATCH 474/930] 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 e2d3d7495..dfe27dee8 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -162,6 +162,11 @@ def rbs_collection_config_path end 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 6e8339946db3b0a21592ece09bcd9f8e33777186 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 07:50:40 -0400 Subject: [PATCH 475/930] Revert --- lib/solargraph/workspace.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index dfe27dee8..e2d3d7495 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -162,11 +162,6 @@ def rbs_collection_config_path end 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 71ac7b56c7fabdde2f5eb7bbdb7a1f54a3edf4fe Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 07:52:07 -0400 Subject: [PATCH 476/930] 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 477/930] 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 478/930] 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 479/930] 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 480/930] 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 481/930] 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 7d8b5a36aecb8103c7cb353f132fad99dd49b7ff Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 13:53:08 -0400 Subject: [PATCH 482/930] 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 483/930] 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 d9fa32f6bd89dceefb1eafc423a3dafaefa97eaf Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 15:54:51 -0400 Subject: [PATCH 484/930] 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 485/930] 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 bf950718d1180b6aabe12a35b90c82f88fc34a6a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 8 Oct 2025 19:12:11 -0400 Subject: [PATCH 486/930] Restore actions config --- .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 6262dd494..b4ef26bfe 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -8,7 +8,7 @@ name: Linting on: workflow_dispatch: {} pull_request: - branches: ['*'] + branches: [ master ] push: branches: - 'main' diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 871252fcc..b5984f3cb 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -9,7 +9,7 @@ on: push: branches: [master] pull_request: - branches: ['*'] + branches: [master] permissions: contents: read diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 12399f50b..6fe05a9b9 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -11,7 +11,7 @@ on: push: branches: [ master ] pull_request: - branches: ['*'] + branches: [ master ] permissions: contents: read diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 9f0020065..0ae8a3d8a 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -11,7 +11,7 @@ on: push: branches: [ master ] pull_request: - branches: ['*'] + branches: [ master ] permissions: contents: read From 5ea669e6b34d9f54ec3b4a275388997a83ec47c4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 9 Oct 2025 07:17:23 -0400 Subject: [PATCH 487/930] Remove temporary changes --- .github/workflows/linting.yml | 5 ++--- .github/workflows/plugins.yml | 4 +--- .github/workflows/rspec.yml | 4 +--- .github/workflows/typecheck.yml | 3 +-- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index aaefe0c16..ae885b4db 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -8,11 +8,10 @@ name: Linting on: workflow_dispatch: {} pull_request: - branches: - - '*' + branches: [ master ] push: branches: - - 'main' + - 'master' tags: - 'v*' diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 01169ce7f..b862c8343 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -8,9 +8,7 @@ name: Plugin on: push: branches: [master] - pull_request: - branches: - - '*' + pull_request: [master] permissions: contents: read diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 8b7e87f79..fc4eee47a 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -10,9 +10,7 @@ name: RSpec on: push: branches: [ master ] - pull_request: - branches: - - '*' + pull_request: [ master ] permissions: contents: read diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 26eb75a17..0ae8a3d8a 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -11,8 +11,7 @@ on: push: branches: [ master ] pull_request: - branches: - - '*' + branches: [ master ] permissions: contents: read From 642163756e9a5fe3f34857b513fc8f299ecd3e4c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 9 Oct 2025 07:20:14 -0400 Subject: [PATCH 488/930] Remove temporary changes --- Rakefile | 2 +- spec/api_map_method_spec.rb | 6 +++--- spec/workspace/gemspecs_fetch_dependencies_spec.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Rakefile b/Rakefile index 793831b14..d731fc786 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/extract_gemspecs_logic_from_doc_map' + '--compare origin/master' output, status = Bundler.with_unbundled_env do Open3.capture2e(cmd) end diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index ce7c42a62..610ab5484 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', time_limit_seconds: 240 do + describe '#get_method_stack' do let(:out) { StringIO.new } let(:api_map) { Solargraph::ApiMap.load_with_cache(Dir.pwd, out) } - context 'with stdlib that has vital dependencies', time_limit_seconds: 240 do + 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', time_limit_seconds: 240 do + it 'handles the YAML gem aliased to Psych' do expect(method_stack).not_to be_empty end end diff --git a/spec/workspace/gemspecs_fetch_dependencies_spec.rb b/spec/workspace/gemspecs_fetch_dependencies_spec.rb index c1911ad42..56504e7dd 100644 --- a/spec/workspace/gemspecs_fetch_dependencies_spec.rb +++ b/spec/workspace/gemspecs_fetch_dependencies_spec.rb @@ -77,7 +77,7 @@ context 'with gem that exists in our bundle' do let(:gem_name) { 'undercover' } - it 'finds dependencies', time_limit_seconds: 120 do + it 'finds dependencies' do expect(deps.map(&:name)).to include('ast') end end @@ -85,7 +85,7 @@ context 'with gem does not exist in our bundle' do let(:gem_name) { 'activerecord' } - it 'gives a useful message', time_limit_seconds: 120 do + 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') From d45652b82abdead4dd9b954e10f4e7fcec605e67 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 9 Oct 2025 07:21:11 -0400 Subject: [PATCH 489/930] Remove temporary changes --- .github/workflows/plugins.yml | 4 ++-- .github/workflows/rspec.yml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index b862c8343..142b3d5b0 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -8,8 +8,8 @@ name: Plugin on: push: branches: [master] - pull_request: [master] - + pull_request: + branches: [master] permissions: contents: read diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index fc4eee47a..bf812da99 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -10,7 +10,8 @@ name: RSpec on: push: branches: [ master ] - pull_request: [ master ] + pull_request: + branches: [ master ] permissions: contents: read From f3ca90cf3ab7c5991905646767e6714f06f8f5b8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 9 Oct 2025 07:21:40 -0400 Subject: [PATCH 490/930] Remove temporary changes --- .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 bf812da99..cc4efda4b 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -86,4 +86,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 d6aacef9439fb15dd25bae49ff824993860eef3a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 9 Oct 2025 07:29:06 -0400 Subject: [PATCH 491/930] Remove temporary changes --- .github/workflows/plugins.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 142b3d5b0..b5984f3cb 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -10,6 +10,7 @@ on: branches: [master] pull_request: branches: [master] + permissions: contents: read From 2240ad7aeb03bfc30cbda1b724b4e9616a3ba90f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 9 Oct 2025 07:56:22 -0400 Subject: [PATCH 492/930] Add @sg-ignores for issues covered by future PRs --- lib/solargraph/doc_map.rb | 1 + lib/solargraph/position.rb | 1 + lib/solargraph/shell.rb | 1 + lib/solargraph/source_map/mapper.rb | 6 ++++++ lib/solargraph/type_checker.rb | 1 + 5 files changed, 10 insertions(+) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 05f2f1647..5dcf28552 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -180,6 +180,7 @@ def load_serialized_gem_pins # @sg-ignore Need support for RBS duck interfaces like _ToHash # @type [Array] 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 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/shell.rb b/lib/solargraph/shell.rb index a005f600b..1284e16de 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -80,6 +80,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/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 5fdcb9fe6..871f3ed5f 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -70,6 +70,9 @@ 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 +247,9 @@ 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| diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index d8bdb4424..36263a0ce 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -463,6 +463,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 && !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 b0fb367852f9d4d919abb17db6fb38803cc75eb1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 9 Oct 2025 08:07:55 -0400 Subject: [PATCH 493/930] Add @sg-ignores for issues covered by future PRs --- lib/solargraph/shell.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 1284e16de..a005f600b 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -80,7 +80,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." From 1a2a546f0b47549089fec4fc5d3190d2819ed150 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 9 Oct 2025 08:58:00 -0400 Subject: [PATCH 494/930] Add future specs --- spec/parser/flow_sensitive_typing_spec.rb | 709 +++++++++++++++++++++- 1 file changed, 707 insertions(+), 2 deletions(-) diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index bf747fc76..30b813d85 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -3,7 +3,7 @@ # @todo These tests depend on `Clip`, but we're putting the tests here to # avoid overloading clip_spec.rb. describe Solargraph::Parser::FlowSensitiveTyping do - it 'uses is_a? in a simple if() to refine types on a simple class' do + it 'uses is_a? in a simple if() to refine types' do source = Solargraph::Source.load_string(%( class ReproBase; end class Repro < ReproBase; end @@ -72,7 +72,7 @@ def verify_repro(repr) expect(clip.infer.to_s).to eq('ReproBase') end - it 'uses is_a? in a simple unless statement to refine types on a simple class' do + it 'uses is_a? in a simple unless statement to refine types' do source = Solargraph::Source.load_string(%( class ReproBase; end class Repro < ReproBase; end @@ -212,6 +212,42 @@ class Repro < ReproBase; end expect(clip.infer.to_s).to eq('Float') end + it 'uses varname in a "break unless" statement in a while to refine types' do + pending 'lib/solargraph/pin/breakable.rb' + + source = Solargraph::Source.load_string(%( + class ReproBase; end + class Repro < ReproBase; end + # @type [ReproBase, nil] + value = bar + while !is_done() + break unless value + value + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 8]) + expect(clip.infer.to_s).to eq('ReproBase') + end + + it 'uses varname in a "break if" statement in a while to refine types' do + pending 'lib/solargraph/pin/breakable.rb' + + source = Solargraph::Source.load_string(%( + class ReproBase; end + class Repro < ReproBase; end + # @type [ReproBase, nil] + value = bar + while !is_done() + break if value.nil? + value + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 8]) + expect(clip.infer.to_s).to eq('ReproBase') + end + it 'understands compatible reassignments' do source = Solargraph::Source.load_string(%( class Foo @@ -253,4 +289,673 @@ def baz; end clip = api_map.clip_at('test.rb', [3, 6]) expect { clip.infer.to_s }.not_to raise_error end + + it 'uses nil? in a simple if() to refine nilness' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + def verify_repro(repr) + repr = 10 if floop + repr + if repr.nil? + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, 10, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + # the 10 here is arguably a bug + expect(clip.infer.rooted_tags).to eq('nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + end + + it 'uses nil? and && in a simple if() to refine nilness - nil? first' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr.nil? && throw_the_dice + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses nil? and && in a simple if() to refine nilness - nil? second' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if throw_the_dice && repr.nil? + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses nil? and || in a simple if() - nil? first' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr.nil? || throw_the_dice + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses nil? and || in a simple if() - nil? second' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if throw_the_dice || repr.nil? + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses varname and || in a simple if() - varname first' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr || throw_the_dice + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending('supporting else after || on varname') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses varname and || in a simple if() - varname second' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if throw_the_dice || repr + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending('supporting else after || on varname') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses .nil? and or in an unless' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + # @param repr [String, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr) + repr unless repr.nil? || repr.downcase + repr + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 33]) + expect(clip.infer.rooted_tags).to eq('::String') + + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::String, nil') + + clip = api_map.clip_at('test.rb', [5, 8]) + expect(clip.infer.rooted_tags).to eq('::String, nil') + end + + it 'uses varname and && in a simple if() - varname first' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr && throw_the_dice + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses varname and && in a simple if() - varname second' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if throw_the_dice && repr + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + pending('supporting else after && on varname') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses variable in a simple if() to refine types' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + def verify_repro(repr) + repr = 10 if floop + repr + if repr + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, 10, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses variable in a simple if() to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + def verify_repro(repr = nil) + repr = 10 if floop + repr + if repr + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [3, 8]) + expect(clip.infer.rooted_tags).to eq('10, nil') + + pending('deferring nil removal in flow senstive typing') + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + pending('supporting else in flow senstiive typing') + clip = api_map.clip_at('test.rb', [7, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses .nil? in a return if() in an if to refine types using nil checks' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + if rand + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + end + + # https://cse.buffalo.edu/~regan/cse305/RubyBNF.pdf + # https://ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/syntax.html + it 'uses .nil? in a return if() in a method to refine types using nil checks' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + return if baz.nil? + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + end + + it 'uses .nil? in a return if() in a block to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @param arr [Array] + # @return [void] + def bar(arr, baz: nil) + baz + arr.each do |item| + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + pending('better scoping of return if in blocks') + + clip = api_map.clip_at('test.rb', [9, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [11, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in an unless to refine types using nil checks' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + unless rand + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + pending('better scoping of return if in unless') + + clip = api_map.clip_at('test.rb', [10, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in a while to refine types using nil checks' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + while rand do + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [9, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in an until to refine types using nil checks' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + until rand do + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [9, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in a switch/case/else to refine types using nil checks' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + case rand + when 0..0.5 + return if baz.nil? + baz + else + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + pending('better scoping of return if in case/when') + + clip = api_map.clip_at('test.rb', [10, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [12, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in a ternary operator to refine types using nil checks' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + rand > 0.5 ? (return if baz.nil?; baz) : baz + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [6, 44]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + pending('better scoping of return if in ternary operator') + + clip = api_map.clip_at('test.rb', [6, 51]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [7, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in a begin/end to refine types using nil checks' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + begin + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [10, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + end + + it 'uses .nil? in a return if() in a ||= to refine types using nil checks' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + baz ||= begin + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [10, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + end + + it 'uses .nil? in a return if() in a try / rescue / ensure to refine types using nil checks' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + begin + return if baz.nil? + baz + rescue StandardError + baz + ensure + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [10, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + pending('better scoping of return if in begin/rescue/ensure') + + clip = api_map.clip_at('test.rb', [12, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [14, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'provides a useful pin after a return if .nil?' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' + + source = Solargraph::Source.load_string(%( + class A + # @param b [Hash{String => String}] + # @return [void] + def a b + c = b["123"] + c + return c if c.nil? + c + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.to_s).to eq('String') + + clip = api_map.clip_at('test.rb', [7, 17]) + expect(clip.infer.to_s).to eq('nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.to_s).to eq('String') + end + + it 'uses ! to detect nilness' do + pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_bang' + + source = Solargraph::Source.load_string(%( + class A + # @param a [Integer, nil] + # @return [Integer] + def foo a + return a unless !a + 123 + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 17]) + expect(clip.infer.to_s).to eq('Integer') + end end From 6a95643d39e385e2510df1b9bd87123dc21dafab Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 9 Oct 2025 09:00:23 -0400 Subject: [PATCH 495/930] Rename method --- lib/solargraph/parser/flow_sensitive_typing.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 41ce6eeaf..d9f556e25 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -68,7 +68,7 @@ def process_if(if_node) get_node_end_position(then_clause)) end - process_conditional(conditional_node, true_ranges) + process_expression(conditional_node, true_ranges) end class << self @@ -162,7 +162,7 @@ def process_facts(facts_by_pin, presences) # @param true_ranges [Array] # # @return [void] - def process_conditional(conditional_node, true_ranges) + def process_expression(conditional_node, true_ranges) if conditional_node.type == :send process_isa(conditional_node, true_ranges) elsif conditional_node.type == :and From 84ac4edf9adea5a0cd6e5f3e42d6dee38a8aa830 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 9 Oct 2025 09:26:01 -0400 Subject: [PATCH 496/930] Add false_presences arguments --- lib/solargraph/parser/flow_sensitive_typing.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index d9f556e25..f727dc0db 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -12,9 +12,10 @@ def initialize(locals, enclosing_breakable_pin = nil) # @param and_node [Parser::AST::Node] # @param true_ranges [Array] + # @param false_ranges [Array] # # @return [void] - def process_and(and_node, true_ranges = []) + def process_and(and_node, true_ranges = [], false_ranges = []) # @type [Parser::AST::Node] lhs = and_node.children[0] # @type [Parser::AST::Node] @@ -25,7 +26,7 @@ def process_and(and_node, true_ranges = []) rhs_presence = Range.new(before_rhs_pos, get_node_end_position(rhs)) - process_isa(lhs, true_ranges + [rhs_presence]) + process_isa(lhs, true_ranges + [rhs_presence], []) end # @param if_node [Parser::AST::Node] @@ -50,6 +51,7 @@ def process_if(if_node) else_clause = if_node.children[2] true_ranges = [] + false_ranges = [] if always_breaks?(else_clause) unless enclosing_breakable_pin.nil? rest_of_breakable_body = Range.new(get_node_end_position(if_node), @@ -68,7 +70,7 @@ def process_if(if_node) get_node_end_position(then_clause)) end - process_expression(conditional_node, true_ranges) + process_expression(conditional_node, true_ranges, false_ranges) end class << self @@ -160,13 +162,14 @@ def process_facts(facts_by_pin, presences) # @param conditional_node [Parser::AST::Node] # @param true_ranges [Array] + # @param false_ranges [Array] # # @return [void] - def process_expression(conditional_node, true_ranges) + def process_expression(conditional_node, true_ranges, false_ranges) if conditional_node.type == :send - process_isa(conditional_node, true_ranges) + process_isa(conditional_node, true_ranges, false_ranges) elsif conditional_node.type == :and - process_and(conditional_node, true_ranges) + process_and(conditional_node, true_ranges, false_ranges) end end @@ -208,9 +211,10 @@ def find_local(variable_name, position) # @param isa_node [Parser::AST::Node] # @param true_presences [Array] + # @param false_presences [Array] # # @return [void] - def process_isa(isa_node, true_presences) + def process_isa(isa_node, true_presences, false_presences) isa_type_name, variable_name = parse_isa(isa_node) return if variable_name.nil? || variable_name.empty? isa_position = Range.from_node(isa_node).start From 5753e8f344ce8e7b4d9ceef514c7f2ed77034e5a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 9 Oct 2025 09:32:13 -0400 Subject: [PATCH 497/930] Generalize some methods for future expansion --- .../parser/flow_sensitive_typing.rb | 75 +++++++++++++------ 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index f727dc0db..52e3cc45b 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -16,6 +16,8 @@ def initialize(locals, enclosing_breakable_pin = nil) # # @return [void] def process_and(and_node, true_ranges = [], false_ranges = []) + return unless and_node.type == :and + # @type [Parser::AST::Node] lhs = and_node.children[0] # @type [Parser::AST::Node] @@ -26,13 +28,32 @@ def process_and(and_node, true_ranges = [], false_ranges = []) rhs_presence = Range.new(before_rhs_pos, get_node_end_position(rhs)) - process_isa(lhs, true_ranges + [rhs_presence], []) + + # can't assume if an and is false that every single condition + # is false, so don't provide any false ranges to assert facts + # on + process_expression(lhs, true_ranges + [rhs_presence], []) + end + + # @param node [Parser::AST::Node] + # @param true_presences [Array] + # @param false_presences [Array] + # + # @return [void] + def process_calls(node, true_presences, false_presences) + return unless node.type == :send + + process_isa(node, true_presences, false_presences) end # @param if_node [Parser::AST::Node] + # @param true_ranges [Array] + # @param false_ranges [Array] # # @return [void] - def process_if(if_node) + def process_if(if_node, true_ranges = [], false_ranges = []) + return if if_node.type != :if + # # See if we can refine a type based on the result of 'if foo.nil?' # @@ -50,8 +71,6 @@ def process_if(if_node) # @type [Parser::AST::Node] else_clause = if_node.children[2] - true_ranges = [] - false_ranges = [] if always_breaks?(else_clause) unless enclosing_breakable_pin.nil? rest_of_breakable_body = Range.new(get_node_end_position(if_node), @@ -160,43 +179,53 @@ def process_facts(facts_by_pin, presences) end end - # @param conditional_node [Parser::AST::Node] + # @param expression_node [Parser::AST::Node] # @param true_ranges [Array] # @param false_ranges [Array] # # @return [void] - def process_expression(conditional_node, true_ranges, false_ranges) - if conditional_node.type == :send - process_isa(conditional_node, true_ranges, false_ranges) - elsif conditional_node.type == :and - process_and(conditional_node, true_ranges, false_ranges) - end + def process_expression(expression_node, true_ranges, false_ranges) + process_calls(expression_node, true_ranges, false_ranges) + process_and(expression_node, true_ranges, false_ranges) end - # @param isa_node [Parser::AST::Node] - # @return [Array(String, String), nil] - def parse_isa(isa_node) - return unless isa_node&.type == :send && isa_node.children[1] == :is_a? + # @param call_node [Parser::AST::Node] + # @param method_name [Symbol] + # @return [Array(String, String), nil] Tuple of rgument to + # function, then receiver of function if it's a variable, + # otherwise nil if no simple variable receiver + def parse_call(call_node, method_name) + return unless call_node&.type == :send && call_node.children[1] == method_name # Check if conditional node follows this pattern: # s(:send, # s(:send, nil, :foo), :is_a?, # s(:const, nil, :Baz)), - isa_receiver = isa_node.children[0] - isa_type_name = type_name(isa_node.children[2]) - return unless isa_type_name + # + call_receiver = call_node.children[0] + call_arg = type_name(call_node.children[2]) - # check if isa_receiver looks like this: + # check if call_receiver looks like this: # s(:send, nil, :foo) # and set variable_name to :foo - if isa_receiver&.type == :send && isa_receiver.children[0].nil? && isa_receiver.children[1].is_a?(Symbol) - variable_name = isa_receiver.children[1].to_s + if call_receiver&.type == :send && call_receiver.children[0].nil? && call_receiver.children[1].is_a?(Symbol) + variable_name = call_receiver.children[1].to_s end # or like this: # (lvar :repr) - variable_name = isa_receiver.children[0].to_s if isa_receiver&.type == :lvar + variable_name = call_receiver.children[0].to_s if call_receiver&.type == :lvar return unless variable_name - [isa_type_name, variable_name] + [call_arg, variable_name] + end + + # @param isa_node [Parser::AST::Node] + # @return [Array(String, String), nil] + def parse_isa(isa_node) + call_type_name, variable_name = parse_call(isa_node, :is_a?) + + return unless call_type_name + + [call_type_name, variable_name] end # @param variable_name [String] From 9de4a72bceee55ee4f893e44108e7abc569e561f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 10 Oct 2025 06:44:29 -0400 Subject: [PATCH 498/930] Use intersection of types in probe() --- lib/solargraph/pin/base_variable.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index e74a74d9a..f2d7baf54 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -107,8 +107,10 @@ def return_types_from_node(parent_node, api_map) def probe api_map if presence_certain? && return_type&.defined? # flow sensitive typing has already figured out this type - # @sg-ignore need to improve handling of &. - return return_type.qualify(api_map, *gates) + # has been downcast - let's include only the common bits, + # trusting that the other ones have been proven not to be + # included + return ComplexType.new(super.items & return_type.items).qualify(api_map, *gates) end unless @assignment.nil? From 87b5a43dff989c3f914884bc1f8de568fe16307f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 10 Oct 2025 07:01:06 -0400 Subject: [PATCH 499/930] Add @sg-ignore --- lib/solargraph/pin/base_variable.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index f2d7baf54..4b825ebbf 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -110,6 +110,7 @@ def probe api_map # has been downcast - let's include only the common bits, # trusting that the other ones have been proven not to be # included + # @sg-ignore flow sensitive typing needs to handle && on both sides return ComplexType.new(super.items & return_type.items).qualify(api_map, *gates) end From 9aea987119725d38007163be5dffd8cd6d4879eb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 10 Oct 2025 07:15:26 -0400 Subject: [PATCH 500/930] Handle simple 'if foo.nil?' cases --- .../parser/flow_sensitive_typing.rb | 41 +++++++++++++++++-- lib/solargraph/pin/local_variable.rb | 12 ++++++ spec/parser/flow_sensitive_typing_spec.rb | 4 -- spec/type_checker/levels/strong_spec.rb | 16 ++++++++ 4 files changed, 66 insertions(+), 7 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 52e3cc45b..2e477b3fd 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -44,6 +44,7 @@ def process_calls(node, true_presences, false_presences) return unless node.type == :send process_isa(node, true_presences, false_presences) + process_nilp(node, true_presences, false_presences) end # @param if_node [Parser::AST::Node] @@ -81,7 +82,7 @@ def process_if(if_node, true_ranges = [], false_ranges = []) unless then_clause.nil? # - # Add specialized locals for the then clause range + # If the condition is true we can assume things about the then clause # before_then_clause_loc = then_clause.location.expression.adjust(begin_pos: -1) before_then_clause_pos = Position.new(before_then_clause_loc.line, before_then_clause_loc.column) @@ -171,9 +172,11 @@ def process_facts(facts_by_pin, presences) # facts_by_pin.each_pair do |pin, facts| facts.each do |fact| - downcast_type_name = fact.fetch(:type) + downcast_type_name = fact.fetch(:type, nil) + nilp = fact.fetch(:nil, nil) presences.each do |presence| - add_downcast_local(pin, downcast_type_name, presence) + add_downcast_local(pin, downcast_type_name, presence) unless downcast_type_name.nil? + add_downcast_local(pin, 'nil', presence) if nilp == true end end end @@ -257,6 +260,36 @@ def process_isa(isa_node, true_presences, false_presences) process_facts(if_true, true_presences) end + # @param nilp_node [Parser::AST::Node] + # @return [Array(String, String), nil] + def parse_nilp(nilp_node) + parse_call(nilp_node, :nil?) + end + + # @param nilp_node [Parser::AST::Node] + # @param true_presences [Array] + # @param false_presences [Array] + # + # @return [void] + def process_nilp(nilp_node, true_presences, false_presences) + nilp_arg, variable_name = parse_nilp(nilp_node) + return if variable_name.nil? || variable_name.empty? + # if .nil? got an argument, move on, this isn't the situation + # we're looking for and typechecking will cover any invalid + # ones + return unless nilp_arg.nil? + + nilp_position = Range.from_node(nilp_node).start + + pin = find_local(variable_name, nilp_position) + return unless pin + + if_true = {} + if_true[pin] ||= [] + if_true[pin] << { nil: true } + process_facts(if_true, true_presences) + end + # @param node [Parser::AST::Node] # # @return [String, nil] @@ -264,7 +297,9 @@ def type_name(node) # e.g., # s(:const, nil, :Baz) return unless node&.type == :const + # @type [Parser::AST::Node, nil] module_node = node.children[0] + # @type [Parser::AST::Node, nil] class_node = node.children[1] return class_node.to_s if module_node.nil? diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 9eae6cc6f..f6e6fe037 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -21,6 +21,18 @@ def initialize assignment: nil, presence: nil, presence_certain: false, **splat @presence_certain = presence_certain end + # @param api_map [ApiMap] + # @return [ComplexType] + def probe api_map + if presence_certain? && return_type&.defined? + # flow sensitive typing has already figured out this type + # has been downcast - use the type it figured out + return return_type.qualify(api_map, *gates) + end + + super + end + def combine_with(other, attrs={}) new_attrs = { assignment: assert_same(other, :assignment), diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 30b813d85..076b99b7a 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -318,8 +318,6 @@ def verify_repro(repr) end it 'uses nil? and && in a simple if() to refine nilness - nil? first' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( # @param repr [Integer, nil] # @param throw_the_dice [Boolean] @@ -914,8 +912,6 @@ def bar(baz: nil) end it 'provides a useful pin after a return if .nil?' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( class A # @param b [Hash{String => String}] diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 970435dc3..750108365 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -4,6 +4,22 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end + it 'respects pin visibility' do + checker = type_checker(%( + class Foo + # Get the namespace's type (Class or Module). + # + # @param baz [Integer, nil] + # @return [Integer, nil] + def foo baz = 123 + return nil if baz.nil? + baz + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'does not complain on array dereference' do checker = type_checker(%( # @param idx [Integer, nil] an index From 6f852f715d87ebf87c4ced24519dae5c5c865009 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 10 Oct 2025 07:18:53 -0400 Subject: [PATCH 501/930] Fix BaseVariable#probe --- lib/solargraph/pin/base_variable.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 4b825ebbf..4e288279d 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -107,11 +107,9 @@ def return_types_from_node(parent_node, api_map) def probe api_map if presence_certain? && return_type&.defined? # flow sensitive typing has already figured out this type - # has been downcast - let's include only the common bits, - # trusting that the other ones have been proven not to be - # included + # has been downcast - use the type it figured out # @sg-ignore flow sensitive typing needs to handle && on both sides - return ComplexType.new(super.items & return_type.items).qualify(api_map, *gates) + return return_type.qualify(api_map, *gates) end unless @assignment.nil? From f46e435ce347e8428b15d62b0b7bb803b2054c60 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 10 Oct 2025 07:56:02 -0400 Subject: [PATCH 502/930] Add varname in a simple if --- spec/parser/flow_sensitive_typing_spec.rb | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index b680b54ab..9efab089e 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -212,6 +212,30 @@ class Repro < ReproBase; end expect(clip.infer.to_s).to eq('Float') end + it 'uses varname in a simple if()' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + it 'uses varname in a "break unless" statement in a while to refine types' do source = Solargraph::Source.load_string(%( class ReproBase; end From 82e5bbfdc082b89aded1681bc2e459eadc3734ed Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 11 Oct 2025 15:49:34 -0400 Subject: [PATCH 503/930] Handle 'if foo' in flow-sensitive typing --- .rubocop_todo.yml | 8 +-- .../parser/flow_sensitive_typing.rb | 51 ++++++++++++++++++- lib/solargraph/pin/base.rb | 1 + lib/solargraph/pin/local_variable.rb | 32 +++++++++++- spec/parser/flow_sensitive_typing_spec.rb | 30 +++++++++-- 5 files changed, 108 insertions(+), 14 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 83339e756..87011662b 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' @@ -1278,12 +1277,7 @@ YARD/MismatchName: Enabled: false YARD/TagTypeSyntax: - Exclude: - - 'lib/solargraph/api_map/constants.rb' - - '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/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 2e477b3fd..7bbaf1e6d 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -142,11 +142,18 @@ def self.visible_pins(pins, name, closure, location) private # @param pin [Pin::LocalVariable] - # @param downcast_type_name [String] + # @param downcast_type_name [String, :not_nil] # @param presence [Range] # # @return [void] def add_downcast_local(pin, downcast_type_name, presence) + return_type = if downcast_type_name == :not_nil + pin.return_type + else + ComplexType.parse(downcast_type_name) + end + exclude_return_type = downcast_type_name == :not_nil ? ComplexType::NIL : nil + # @todo Create pin#update method new_pin = Solargraph::Pin::LocalVariable.new( location: pin.location, @@ -155,10 +162,12 @@ def add_downcast_local(pin, downcast_type_name, presence) assignment: pin.assignment, comments: pin.comments, presence: presence, - return_type: ComplexType.try_parse(downcast_type_name), + return_type: return_type, + exclude_return_type: exclude_return_type, presence_certain: true, source: :flow_sensitive_typing ) + new_pin.reset_generated! locals.push(new_pin) end @@ -174,9 +183,11 @@ def process_facts(facts_by_pin, presences) facts.each do |fact| downcast_type_name = fact.fetch(:type, nil) nilp = fact.fetch(:nil, nil) + not_nilp = fact.fetch(:not_nil, nil) presences.each do |presence| add_downcast_local(pin, downcast_type_name, presence) unless downcast_type_name.nil? add_downcast_local(pin, 'nil', presence) if nilp == true + add_downcast_local(pin, :not_nil, presence) if not_nilp == true end end end @@ -190,6 +201,7 @@ def process_facts(facts_by_pin, presences) def process_expression(expression_node, true_ranges, false_ranges) process_calls(expression_node, true_ranges, false_ranges) process_and(expression_node, true_ranges, false_ranges) + process_variable(expression_node, true_ranges, false_ranges) end # @param call_node [Parser::AST::Node] @@ -290,6 +302,41 @@ def process_nilp(nilp_node, true_presences, false_presences) process_facts(if_true, true_presences) end + # @param var_node [Parser::AST::Node] + # + # @return [String, nil] Variable name referenced + def parse_variable(var_node) + return if var_node.children.length != 1 + + var_node.children[0]&.to_s + end + + # @return [void] + # @param node [Parser::AST::Node] + # @param true_presences [Array] + # @param false_presences [Array] + def process_variable(node, true_presences, false_presences) + return unless [:lvar, :ivar, :cvar, :gvar].include?(node.type) + + variable_name = parse_variable(node) + return if variable_name.nil? + + var_position = Range.from_node(node).start + + pin = find_local(variable_name, var_position) + return unless pin + + if_true = {} + if_true[pin] ||= [] + if_true[pin] << { not_nil: true } + process_facts(if_true, true_presences) + + if_false = {} + if_false[pin] ||= [] + if_false[pin] << { nil: true } + process_facts(if_false, false_presences) + end + # @param node [Parser::AST::Node] # # @return [String, nil] diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 511c7deb7..dae997f89 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -572,6 +572,7 @@ def proxy return_type result = dup result.return_type = return_type result.proxied = true + result.reset_generated! result end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index f6e6fe037..f87031ae1 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -13,12 +13,40 @@ def presence_certain? # @param assignment [AST::Node, nil] # @param presence [Range, nil] # @param presence_certain [Boolean] + # @param exclude_return_type [ComplexType, nil] Ensure any return + # type returned will never include these unique types in the + # unique types of its complex type # @param splat [Hash] - def initialize assignment: nil, presence: nil, presence_certain: false, **splat + def initialize assignment: nil, presence: nil, presence_certain: false, exclude_return_type: nil, + **splat super(**splat) @assignment = assignment @presence = presence @presence_certain = presence_certain + @exclude_return_type = exclude_return_type + end + + def reset_generated! + @return_type_minus_exclusions = nil + super + end + + # @return [ComplexType, nil] + def return_type + return_type_minus_exclusions(@return_type || generate_complex_type) + end + + # @param raw_return_type [ComplexType, nil] + # @return [ComplexType, nil] + def return_type_minus_exclusions(raw_return_type) + @return_type_minus_exclusions ||= + if exclude_return_type && raw_return_type + types = raw_return_type.items - exclude_return_type.items + types = [ComplexType::UniqueType::UNDEFINED] if types.empty? + ComplexType.new(types) + else + raw_return_type + end end # @param api_map [ApiMap] @@ -60,6 +88,8 @@ def to_rbs private + attr_reader :exclude_return_type + # @param tag1 [String] # @param tag2 [String] # @return [Boolean] diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 076b99b7a..77785bc69 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -212,9 +212,33 @@ class Repro < ReproBase; end expect(clip.infer.to_s).to eq('Float') end - it 'uses varname in a "break unless" statement in a while to refine types' do - pending 'lib/solargraph/pin/breakable.rb' + it 'uses varname in a simple if()' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + pending('supporting else after if on varname') + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses varname in a "break unless" statement in a while to refine types' do source = Solargraph::Source.load_string(%( class ReproBase; end class Repro < ReproBase; end @@ -490,8 +514,6 @@ def verify_repro(repr) end it 'uses varname and && in a simple if() - varname first' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( # @param repr [Integer, nil] # @param throw_the_dice [Boolean] From a30c1e6eb8769984d80bf546c54d16e0bcf7713e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 11 Oct 2025 16:22:51 -0400 Subject: [PATCH 504/930] Set up flow-sensitive typing specs, currently passing and pending --- spec/parser/node_methods_spec.rb | 10 +- spec/pin/parameter_spec.rb | 30 ++ spec/source/chain/call_spec.rb | 54 +++ spec/source/chain/or_spec.rb | 35 ++ spec/source/chain/q_call_spec.rb | 23 ++ spec/type_checker/levels/alpha_spec.rb | 79 +++++ spec/type_checker/levels/strict_spec.rb | 134 +++++++- spec/type_checker/levels/strong_spec.rb | 431 +++++++++++++++++++++--- spec/type_checker/levels/typed_spec.rb | 57 ++++ 9 files changed, 797 insertions(+), 56 deletions(-) create mode 100644 spec/source/chain/or_spec.rb create mode 100644 spec/source/chain/q_call_spec.rb create mode 100644 spec/type_checker/levels/alpha_spec.rb diff --git a/spec/parser/node_methods_spec.rb b/spec/parser/node_methods_spec.rb index f9504b584..e5cd0c78f 100644 --- a/spec/parser/node_methods_spec.rb +++ b/spec/parser/node_methods_spec.rb @@ -185,11 +185,11 @@ end it "handles top 'or' nodes" do + pending 'flow-sensitive typing improvements' + node = Solargraph::Parser.parse('1 || "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.length).to eq(2) - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(rets[0])).to eq('::Integer') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(rets[1])).to eq('::String') + expect(rets.length).to eq(1) end it "handles nested 'and' nodes" do @@ -336,9 +336,11 @@ end it "handles top 'or' nodes" do + pending 'flow-sensitive typing improvements' + node = Solargraph::Parser.parse('1 || "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:int, :str]) + expect(rets.map(&:type)).to eq([:or]) end it "handles nested 'and' nodes from return" do diff --git a/spec/pin/parameter_spec.rb b/spec/pin/parameter_spec.rb index 082ec54c6..8c3949a5c 100644 --- a/spec/pin/parameter_spec.rb +++ b/spec/pin/parameter_spec.rb @@ -473,5 +473,35 @@ def self.foo bar: 'bar' type = pin.probe(api_map) expect(type.simple_tags).to eq('String') end + + it 'handles a relative type name case' do + pending 'flow-sensitive typing improvements' + + source = Solargraph::Source.load_string(%( + module A + module B + class Method + end + end + end + + module A + module B + class C < B::Method + # @param alt [Method] + # @return [B::Method, nil] + def resolve_method alt + alt + end + end + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new + api_map.map(source) + + clip = api_map.clip_at('test.rb', [14, 16]) + expect(clip.infer.rooted_tags).to eq('::A::B::Method') + end end end diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index 8b67a3c66..1ffa70b38 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -371,6 +371,21 @@ def yielder(&blk) expect(type.tag).to eq('Enumerator>') end + it 'allows calls off of nilable objects by default' do + source = Solargraph::Source.load_string(%( + # @type [String, nil] + f = foo + a = f.upcase + a + ), 'test.rb') + api_map = Solargraph::ApiMap.new + api_map.map source + + chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(4, 6)) + type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) + expect(type.tag).to eq('String') + end + it 'calculates class return type based on class generic' do source = Solargraph::Source.load_string(%( # @generic A @@ -627,4 +642,43 @@ 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 + pending 'Proxytype improvements' + + 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/source/chain/or_spec.rb b/spec/source/chain/or_spec.rb new file mode 100644 index 000000000..85019a0b7 --- /dev/null +++ b/spec/source/chain/or_spec.rb @@ -0,0 +1,35 @@ +describe Solargraph::Source::Chain::Or do + it 'handles simple nil-removal' do + pending 'flow-sensitive typing improvements' + + source = Solargraph::Source.load_string(%( + # @param a [Integer, nil] + def foo a + b = a || 10 + b + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.simplify_literals.rooted_tags).to eq('::Integer') + end + + it 'removes nil from more complex cases' do + pending 'flow-sensitive typing improvements' + + source = Solargraph::Source.load_string(%( + def foo + out = ENV['BAR'] || + File.join(Dir.home, '.config', 'solargraph', 'config.yml') + out + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + + clip = api_map.clip_at('test.rb', [3, 8]) + expect(clip.infer.simplify_literals.rooted_tags).to eq('::String') + end +end diff --git a/spec/source/chain/q_call_spec.rb b/spec/source/chain/q_call_spec.rb new file mode 100644 index 000000000..a63568358 --- /dev/null +++ b/spec/source/chain/q_call_spec.rb @@ -0,0 +1,23 @@ +describe Solargraph::Source::Chain::QCall do + it 'understands &. in chains' do + source = Solargraph::Source.load_string(%( + # @param a [String, nil] + # @return [String, nil] + def foo a + b = a&.upcase + b + end + + b = foo 123 + b + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + + clip = api_map.clip_at('test.rb', [5, 8]) + expect(clip.infer.to_s).to eq('String, nil') + + clip = api_map.clip_at('test.rb', [9, 6]) + expect(clip.infer.to_s).to eq('String, nil') + end +end diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb new file mode 100644 index 000000000..d6c58df00 --- /dev/null +++ b/spec/type_checker/levels/alpha_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +describe Solargraph::TypeChecker do + context 'when at alpha level' do + def type_checker code + Solargraph::TypeChecker.load_string(code, 'test.rb', :alpha) + end + + it 'allows a compatible function call from two distinct types in a union' do + checker = type_checker(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz.nil? + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end + + it 'does not falsely enforce nil in return types' do + pending 'flow-sensitive typing improvements' + + 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 + pending 'flow-sensitive typing improvements' + + 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 + + it 'tracks type of ivar' do + checker = type_checker(%( + class Foo + # @return [void] + def initialize + @sync_count = 0 + end + + # @return [void] + def synchronized? + @sync_count < 2 + end + + # @return [void] + def catalog + @sync_count += 1 + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end + end +end diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 47bf45a2c..ea2cdccf3 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -5,6 +5,36 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strict) end + it 'can derive return types' do + checker = type_checker(%( + # @param a [String, nil] + # @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 '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 'handles compatible interfaces with self types on call' do checker = type_checker(%( # @param a [Enumerable] @@ -61,7 +91,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 @@ -581,6 +611,46 @@ def bar expect(checker.problems).to be_empty end + it 'Can infer through simple ||= on ivar' do + checker = type_checker(%( + class Foo + def recipient + @recipient ||= true + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'Can infer through simple ||= on lvar' do + checker = type_checker(%( + def recipient + recip ||= true + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'Can infer through simple ||= on cvar' do + checker = type_checker(%( + class Foo + def recipient + @@recipient ||= true + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'Can infer through simple ||= on civar' do + checker = type_checker(%( + class Foo + @recipient ||= true + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'Can infer through ||= with a begin+end' do checker = type_checker(%( def recipient @@ -701,6 +771,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 @@ -769,19 +852,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 @@ -1004,11 +1074,47 @@ def bar 123 elsif rand 456 + else + nil end end end )) expect(checker.problems.map(&:message)).to eq([]) end + + it 'does not complain on defaulted reader with un-elsed if' 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 + + it 'does not complain on defaulted reader with with un-elsed unless' do + checker = type_checker(%( + class Foo + # @return [Integer, nil] + def bar + @bar ||= + unless rand + 123 + end + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end end end diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 750108365..2b106aec6 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -4,6 +4,109 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end + it 'does not misunderstand types during flow-sensitive typing' do + checker = type_checker(%( + class A + # @param b [Hash{String => String}] + # @return [void] + def a b + c = b["123"] + return if c.nil? + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'respects pin visibility in if/nil? pattern' do + checker = type_checker(%( + class Foo + # Get the namespace's type (Class or Module). + # + # @param bar [Symbol, nil] + # @return [Symbol, Integer] + def foo bar + return 123 if bar.nil? + bar + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'respects || overriding nilable types' do + checker = type_checker(%( + # @return [String] + def global_config_path + ENV['SOLARGRAPH_GLOBAL_CONFIG'] || + File.join(Dir.home, '.config', 'solargraph', 'config.yml') + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'is able to probe type over an assignment' do + checker = type_checker(%( + # @return [String] + def global_config_path + out = 'foo' + out + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'respects pin visibility in if/foo pattern' do + checker = type_checker(%( + class Foo + # Get the namespace's type (Class or Module). + # + # @param bar [Symbol, nil] + # @return [Symbol, Integer] + def foo bar + baz = bar + return baz if baz + 123 + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'handles a flow sensitive typing if correctly' do + checker = type_checker(%( + # @param a [String, nil] + # @return [void] + def foo a = nil + b = a + if b + b.upcase + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'handles another flow sensitive typing if correctly' do + checker = type_checker(%( + class A + # @param e [String] + # @param f [String] + # @return [void] + def d(e, f:); end + + # @return [void] + def a + c = rand ? nil : "foo" + if c + d(c, f: c) + end + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'respects pin visibility' do checker = type_checker(%( class Foo @@ -22,7 +125,7 @@ def foo baz = 123 it 'does not complain on array dereference' do checker = type_checker(%( - # @param idx [Integer, nil] an index + # @param idx [Integer] an index # @param arr [Array] an array of integers # # @return [void] @@ -33,6 +136,23 @@ def foo(idx, arr) expect(checker.problems.map(&:message)).to be_empty end + it 'understands local evaluation with ||= removes nil from lhs type' do + checker = type_checker(%( + class Foo + def initialize + @bar = nil + end + + # @return [Integer] + def bar + @bar ||= 123 + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end + it 'complains on bad @type assignment' do checker = type_checker(%( # @type [Integer] @@ -83,21 +203,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] @@ -146,21 +251,6 @@ def bar 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 @@ -272,6 +362,149 @@ 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 'ignores generic resolution failure with no generic tag' 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 from current Solargraph limitation' 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 with only one arg' 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 @@ -289,6 +522,20 @@ 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 + it 'resolves constants inside modules inside classes' do checker = type_checker(%( class Bar @@ -309,18 +556,126 @@ def baz expect(checker.problems.map(&:message)).to be_empty end - it 'understands Open3 methods' do - checker = type_checker(%( - require 'open3' + 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 + + 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 + it 'resolves class name correctly in generic resolution' do + expect(checker.problems.map(&:message)).to be_empty + end + end + + it 'handles "while foo" flow sensitive typing correctly' do + checker = type_checker(%( + # @param a [String, nil] # @return [void] - def run_command - # @type [Hash{String => String}] - foo = {'foo' => 'bar'} - Open3.capture2e(foo, 'ls', chdir: '/tmp') + def foo a = nil + b = a + while b + b.upcase + b = nil if rand > 0.5 + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'does flow sensitive typing even inside a block' do + checker = type_checker(%( + class Quux + # @param foo [String, nil] + # + # @return [void] + def baz(foo) + bar = foo + [].each do + bar.upcase unless bar.nil? + end + end + end)) + + expect(checker.problems.map(&:location).map(&:range).map(&:start)).to be_empty + end + + it 'accepts ivar assignments and references with no intermediate calls as safe' do + checker = type_checker(%( + class Foo + def initialize + # @type [Integer, nil] + @foo = nil + end + + # @return [void] + def twiddle + @foo = nil if rand if rand > 0.5 + end + + # @return [Integer] + def bar + @foo = 123 + out = @foo.round + twiddle + out + end end )) + expect(checker.problems.map(&:message)).to be_empty end + + it 'knows that ivar references with intermediate calls are not safe' do + pending 'flow-sensitive typing improvements' + + checker = type_checker(%( + class Foo + def initialize + # @type [Integer, nil] + @foo = nil + end + + # @return [void] + def twiddle + @foo = nil if rand if rand > 0.5 + end + + # @return [Integer] + def bar + @foo = 123 + twiddle + @foo.round + end + end + )) + + expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round"]) + end end end diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index b2071465e..46d854f3e 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -4,6 +4,23 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :typed) end + it 'respects pin visibility' do + checker = type_checker(%( + class Foo + # Get the namespace's type (Class or Module). + # + # @param bar [Array] + # @return [Symbol, Integer] + def foo bar + baz = bar.first + return 123 if baz.nil? + baz + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'reports mismatched types for empty methods' do checker = type_checker(%( class Foo @@ -38,6 +55,21 @@ def bar expect(checker.problems.first.message).to include('does not match') end + it 'reports mismatched key and subtypes' do + pending 'generic typechecking improvements' + + 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 +221,31 @@ def foo expect(checker.problems).to be_empty 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 c6c96e3bde34f2deec95e0fe398dd3c31837b279 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 11 Oct 2025 17:03:07 -0400 Subject: [PATCH 505/930] Remove nilness in 'foo unless foo.nil? || foo' --- .../parser/flow_sensitive_typing.rb | 35 +++++++++++++++++++ spec/parser/flow_sensitive_typing_spec.rb | 2 -- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 7bbaf1e6d..c8af32b81 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -35,6 +35,35 @@ def process_and(and_node, true_ranges = [], false_ranges = []) process_expression(lhs, true_ranges + [rhs_presence], []) end + # @param or_node [Parser::AST::Node] + # @param true_ranges [Array] + # @param false_ranges [Array] + # + # @return [void] + def process_or(or_node, true_ranges = [], false_ranges = []) + return unless or_node.type == :or + + # @type [Parser::AST::Node] + lhs = or_node.children[0] + # @type [Parser::AST::Node] + rhs = or_node.children[1] + + before_rhs_loc = rhs.location.expression.adjust(begin_pos: -1) + before_rhs_pos = Position.new(before_rhs_loc.line, before_rhs_loc.column) + + rhs_presence = Range.new(before_rhs_pos, + get_node_end_position(rhs)) + + # can assume if an or is false that every single condition is + # false, so't provide false ranges to assert facts on + + # can't assume if an or is true that every single condition is + # true, so don't provide true ranges to assert facts on + + process_expression(lhs, [], [rhs_presence]) + process_expression(rhs, [], []) + end + # @param node [Parser::AST::Node] # @param true_presences [Array] # @param false_presences [Array] @@ -201,6 +230,7 @@ def process_facts(facts_by_pin, presences) def process_expression(expression_node, true_ranges, false_ranges) process_calls(expression_node, true_ranges, false_ranges) process_and(expression_node, true_ranges, false_ranges) + process_or(expression_node, true_ranges, false_ranges) process_variable(expression_node, true_ranges, false_ranges) end @@ -300,6 +330,11 @@ def process_nilp(nilp_node, true_presences, false_presences) if_true[pin] ||= [] if_true[pin] << { nil: true } process_facts(if_true, true_presences) + + if_false = {} + if_false[pin] ||= [] + if_false[pin] << { not_nil: true } + process_facts(if_false, false_presences) end # @param var_node [Parser::AST::Node] diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 77785bc69..80b2c7b72 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -492,8 +492,6 @@ def verify_repro(repr, throw_the_dice) end it 'uses .nil? and or in an unless' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( # @param repr [String, nil] # @param throw_the_dice [Boolean] From ab404c85e8144fd632dbfcfa6eceb914d1e20113 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 11 Oct 2025 17:09:04 -0400 Subject: [PATCH 506/930] Handle foo if unrelated && foo --- lib/solargraph/parser/flow_sensitive_typing.rb | 1 + spec/parser/flow_sensitive_typing_spec.rb | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index c8af32b81..18f281196 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -33,6 +33,7 @@ def process_and(and_node, true_ranges = [], false_ranges = []) # is false, so don't provide any false ranges to assert facts # on process_expression(lhs, true_ranges + [rhs_presence], []) + process_expression(rhs, true_ranges, []) end # @param or_node [Parser::AST::Node] diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 80b2c7b72..b332c6b6f 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -366,8 +366,6 @@ def verify_repro(repr, throw_the_dice) end it 'uses nil? and && in a simple if() to refine nilness - nil? second' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( # @param repr [Integer, nil] # @param throw_the_dice [Boolean] From b20ad1e74853d440caf15218fca8e552f31ec596 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 11 Oct 2025 17:44:43 -0400 Subject: [PATCH 507/930] Handle 'break if foo.nil?' --- lib/solargraph/parser/flow_sensitive_typing.rb | 13 +++++++++---- spec/parser/flow_sensitive_typing_spec.rb | 2 -- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 18f281196..9374a5d76 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -102,10 +102,15 @@ def process_if(if_node, true_ranges = [], false_ranges = []) # @type [Parser::AST::Node] else_clause = if_node.children[2] - if always_breaks?(else_clause) - unless enclosing_breakable_pin.nil? - rest_of_breakable_body = Range.new(get_node_end_position(if_node), - get_node_end_position(enclosing_breakable_pin.node)) + unless enclosing_breakable_pin.nil? + rest_of_breakable_body = Range.new(get_node_end_position(if_node), + get_node_end_position(enclosing_breakable_pin.node)) + + if always_breaks?(then_clause) + false_ranges << rest_of_breakable_body + end + + if always_breaks?(else_clause) true_ranges << rest_of_breakable_body end end diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index b332c6b6f..33e667d3c 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -255,8 +255,6 @@ class Repro < ReproBase; end end it 'uses varname in a "break if" statement in a while to refine types' do - pending 'lib/solargraph/pin/breakable.rb' - source = Solargraph::Source.load_string(%( class ReproBase; end class Repro < ReproBase; end From b0a7294893a32d8c049a5f69461a6ecc013fdf18 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 11 Oct 2025 17:50:59 -0400 Subject: [PATCH 508/930] Handle 'if foo ... else foo' --- lib/solargraph/parser/flow_sensitive_typing.rb | 10 ++++++++++ spec/parser/flow_sensitive_typing_spec.rb | 2 -- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 9374a5d76..e2a1ad1ae 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -125,6 +125,16 @@ def process_if(if_node, true_ranges = [], false_ranges = []) get_node_end_position(then_clause)) end + unless else_clause.nil? + # + # If the condition is true we can assume things about the else clause + # + before_else_clause_loc = else_clause.location.expression.adjust(begin_pos: -1) + before_else_clause_pos = Position.new(before_else_clause_loc.line, before_else_clause_loc.column) + false_ranges << Range.new(before_else_clause_pos, + get_node_end_position(else_clause)) + end + process_expression(conditional_node, true_ranges, false_ranges) end diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 33e667d3c..176ffabec 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -232,8 +232,6 @@ def verify_repro(repr, throw_the_dice) clip = api_map.clip_at('test.rb', [6, 10]) expect(clip.infer.rooted_tags).to eq('::Integer') - pending('supporting else after if on varname') - clip = api_map.clip_at('test.rb', [8, 10]) expect(clip.infer.rooted_tags).to eq('nil') end From 9cba783cca9c8adca5da8dd7420fe1e9f167fd84 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 11 Oct 2025 19:20:52 -0400 Subject: [PATCH 509/930] Refactor --- lib/solargraph/parser/node_processor/base.rb | 11 +++++++++++ .../parser/parser_gem/node_processors/and_node.rb | 2 -- .../parser/parser_gem/node_processors/if_node.rb | 4 ---- lib/solargraph/pin/breakable.rb | 3 +++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index fad31e95b..e220bdefb 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -40,6 +40,17 @@ def process private + # @return [Solargraph::Position] + def position + Position.new(node.loc.line, node.loc.column) + end + + # @sg-ignore downcast output of Enumerable#select + # @return [Solargraph::Pin::Breakable, nil] + def enclosing_breakable_pin + pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location&.range&.contain?(position)}.last + end + # @param subregion [Region] # @return [void] def process_children subregion = region diff --git a/lib/solargraph/parser/parser_gem/node_processors/and_node.rb b/lib/solargraph/parser/parser_gem/node_processors/and_node.rb index d3485af7c..b270af5b3 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/and_node.rb @@ -10,8 +10,6 @@ class AndNode < Parser::NodeProcessor::Base def process process_children - position = get_node_start_position(node) - enclosing_breakable_pin = pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location.range.contain?(position)}.last FlowSensitiveTyping.new(locals, enclosing_breakable_pin).process_and(node) end end 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 2452b9cc5..afb2be11b 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb @@ -10,10 +10,6 @@ class IfNode < Parser::NodeProcessor::Base 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 end diff --git a/lib/solargraph/pin/breakable.rb b/lib/solargraph/pin/breakable.rb index 05907b1bb..9e85fc081 100644 --- a/lib/solargraph/pin/breakable.rb +++ b/lib/solargraph/pin/breakable.rb @@ -4,6 +4,9 @@ module Pin module Breakable # @return [Parser::AST::Node] attr_reader :node + + # @return [Location, nil] + attr_reader :location end end end From 3e678e1e0ef904b32dbd2a235da240688a910cf8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 11 Oct 2025 19:30:40 -0400 Subject: [PATCH 510/930] Support 'if foo.nil?' when 'foo' has more than one lvar pin Choose the first one to define the type to assume --- lib/solargraph/parser/flow_sensitive_typing.rb | 1 - spec/parser/flow_sensitive_typing_spec.rb | 4 ---- 2 files changed, 5 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index e2a1ad1ae..2eaadb838 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -295,7 +295,6 @@ def parse_isa(isa_node) # @return [Solargraph::Pin::LocalVariable, nil] def find_local(variable_name, position) pins = locals.select { |pin| pin.name == variable_name && pin.presence.include?(position) } - return unless pins.length == 1 pins.first end diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 176ffabec..0dc132d7d 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -311,8 +311,6 @@ def baz; end end it 'uses nil? in a simple if() to refine nilness' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( # @param repr [Integer, nil] def verify_repro(repr) @@ -558,8 +556,6 @@ def verify_repro(repr, throw_the_dice) end it 'uses variable in a simple if() to refine types' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( # @param repr [Integer, nil] def verify_repro(repr) From 7b7c6403bba3cee6bce3497165164ba3e9765a4c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 11 Oct 2025 23:59:12 -0400 Subject: [PATCH 511/930] Support 'return if foo.nil?' --- .../parser/flow_sensitive_typing.rb | 30 ++++++++++- lib/solargraph/parser/node_processor/base.rb | 6 +++ .../parser_gem/node_processors/and_node.rb | 4 +- .../parser_gem/node_processors/if_node.rb | 4 +- lib/solargraph/pin.rb | 2 + lib/solargraph/pin/breakable.rb | 5 +- lib/solargraph/pin/compound_statementable.rb | 50 +++++++++++++++++++ lib/solargraph/pin/if.rb | 18 +++++++ spec/parser/flow_sensitive_typing_spec.rb | 4 -- 9 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 lib/solargraph/pin/compound_statementable.rb create mode 100644 lib/solargraph/pin/if.rb diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 2eaadb838..4ae817e0b 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -5,9 +5,11 @@ class FlowSensitiveTyping # @param locals [Array] # @param enclosing_breakable_pin [Solargraph::Pin::Breakable, nil] - def initialize(locals, enclosing_breakable_pin = nil) + # @param enclosing_compound_statement_pin [Solargraph::Pin::CompoundStatementable, nil] + def initialize(locals, enclosing_breakable_pin, enclosing_compound_statement_pin) @locals = locals @enclosing_breakable_pin = enclosing_breakable_pin + @enclosing_compound_statement_pin = enclosing_compound_statement_pin end # @param and_node [Parser::AST::Node] @@ -115,6 +117,24 @@ def process_if(if_node, true_ranges = [], false_ranges = []) end end + unless enclosing_compound_statement_pin.nil? + rest_of_returnable_body = Range.new(get_node_end_position(if_node), + get_node_end_position(enclosing_compound_statement_pin.node)) + + # + # if one of the clauses always leaves the compound + # statement, we can assume things about the rest of the + # compound statement + # + if always_leaves_compound_statement?(then_clause) + false_ranges << rest_of_returnable_body + end + + if always_leaves_compound_statement?(else_clause) + true_ranges << rest_of_returnable_body + end + end + unless then_clause.nil? # # If the condition is true we can assume things about the then clause @@ -412,9 +432,15 @@ def always_breaks?(clause_node) clause_node&.type == :break end + # @param clause_node [Parser::AST::Node, nil] + def always_leaves_compound_statement?(clause_node) + # https://docs.ruby-lang.org/en/2.2.0/keywords_rdoc.html + [:return, :raise, :next, :redo, :retry].include?(clause_node&.type) + end + attr_reader :locals - attr_reader :enclosing_breakable_pin + attr_reader :enclosing_breakable_pin, :enclosing_compound_statement_pin end end end diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index e220bdefb..4d4123d9d 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -51,6 +51,12 @@ def enclosing_breakable_pin pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location&.range&.contain?(position)}.last end + # @sg-ignore downcast output of Enumerable#select + # @return [Solargraph::Pin::CompoundStatementable, nil] + def enclosing_compound_statement_pin + pins.select{|pin| pin.is_a?(Pin::CompoundStatementable) && pin.location&.range&.contain?(position)}.last + end + # @param subregion [Region] # @return [void] def process_children subregion = region diff --git a/lib/solargraph/parser/parser_gem/node_processors/and_node.rb b/lib/solargraph/parser/parser_gem/node_processors/and_node.rb index b270af5b3..085c0c68a 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/and_node.rb @@ -10,7 +10,9 @@ class AndNode < Parser::NodeProcessor::Base def process process_children - FlowSensitiveTyping.new(locals, enclosing_breakable_pin).process_and(node) + FlowSensitiveTyping.new(locals, + enclosing_breakable_pin, + enclosing_compound_statement_pin).process_and(node) end end end 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 afb2be11b..e59fa4baa 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb @@ -10,7 +10,9 @@ class IfNode < Parser::NodeProcessor::Base def process process_children - FlowSensitiveTyping.new(locals, enclosing_breakable_pin).process_if(node) + FlowSensitiveTyping.new(locals, + enclosing_breakable_pin, + enclosing_compound_statement_pin).process_if(node) end end end diff --git a/lib/solargraph/pin.rb b/lib/solargraph/pin.rb index 526ac6fc3..016550966 100644 --- a/lib/solargraph/pin.rb +++ b/lib/solargraph/pin.rb @@ -38,6 +38,8 @@ module Pin autoload :Until, 'solargraph/pin/until' autoload :While, 'solargraph/pin/while' autoload :Callable, 'solargraph/pin/callable' + autoload :CompoundStatementable, + 'solargraph/pin/compound_statementable' ROOT_PIN = Pin::Namespace.new(type: :class, name: '', closure: nil, source: :pin_rb) end diff --git a/lib/solargraph/pin/breakable.rb b/lib/solargraph/pin/breakable.rb index 9e85fc081..5de67cf4a 100644 --- a/lib/solargraph/pin/breakable.rb +++ b/lib/solargraph/pin/breakable.rb @@ -1,7 +1,10 @@ module Solargraph module Pin - # Mix-in for pins which enclose code which the 'break' statement works with-in - e.g., blocks, when, until, ... + # Mix-in for pins which enclose code which the 'break' statement + # works with-in - e.g., blocks, when, until, ... module Breakable + include CompoundStatementable + # @return [Parser::AST::Node] attr_reader :node diff --git a/lib/solargraph/pin/compound_statementable.rb b/lib/solargraph/pin/compound_statementable.rb new file mode 100644 index 000000000..debad6615 --- /dev/null +++ b/lib/solargraph/pin/compound_statementable.rb @@ -0,0 +1,50 @@ +module Solargraph + module Pin + # A series of statements where if a given statement executes, /all + # of the previous statements in the sequence must have executed as + # well/. In other words, the statements are run from the top in + # sequence, until interrupted by something like a + # return/break/next/raise/etc. + # + # This mix-in is used in flow sensitive typing to determine how + # far we can assume a given assertion about a type can be trusted + # to be true. + # + # Some examples in Ruby: + # + # * Bodies of methods and Ruby blocks + # * Branches of conditionals and loops - if/elsif/else, + # unless/else, when, until, ||=, ?:, switch/case/else + # * The body of begin-end/try/rescue/ensure statements + # + # Compare/contrast with: + # + # * Scope - a sequence where variables declared are not available + # after the end of the scope. Note that this is not necessarily + # true for a compound statement. + # * Compound statement - synonym + # * Block - in Ruby this has a special meaning (a closure passed to a method), but + # in general parlance this is also a synonym. + # * Closure - a sequence which is also a scope + # * Namespace - a named sequence which is also a scope and a + # closure + # + # See: + # https://cse.buffalo.edu/~regan/cse305/RubyBNF.pdf + # https://ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/syntax.html + # https://en.wikipedia.org/wiki/Block_(programming) + # + # Note: + # + # Just because statement #1 in a sequence is executed, it doesn't + # mean that future ones will. Consider the effect of + # break/next/return/raise/etc. on control flow. + module CompoundStatementable + # @return [Parser::AST::Node] + attr_reader :node + + # @return [Location, nil] + attr_reader :location + end + end +end diff --git a/lib/solargraph/pin/if.rb b/lib/solargraph/pin/if.rb new file mode 100644 index 000000000..35b8a9bfc --- /dev/null +++ b/lib/solargraph/pin/if.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Solargraph + module Pin + class If < Base + include CompoundStatementable + + # @param receiver [Parser::AST::Node, nil] + # @param node [Parser::AST::Node, nil] + # @param context [ComplexType, nil] + # @param args [::Array] + def initialize node: nil, **splat + super(**splat) + @node = node + end + end + end +end diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 0dc132d7d..27c34cb93 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -709,8 +709,6 @@ def bar(baz: nil) end it 'uses .nil? in a return if() in a while to refine types using nil checks' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( class Foo # @param baz [::Boolean, nil] @@ -734,8 +732,6 @@ def bar(baz: nil) end it 'uses .nil? in a return if() in an until to refine types using nil checks' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( class Foo # @param baz [::Boolean, nil] From 88156b4c4c207dfd9628911f1049505c75897c83 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 12 Oct 2025 11:38:45 -0400 Subject: [PATCH 512/930] Support 'return foo unless !foo' --- .../parser/flow_sensitive_typing.rb | 25 +++++++++++++++++++ spec/parser/flow_sensitive_typing_spec.rb | 2 -- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 4ae817e0b..3ded6010a 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -77,6 +77,7 @@ def process_calls(node, true_presences, false_presences) process_isa(node, true_presences, false_presences) process_nilp(node, true_presences, false_presences) + process_bang(node, true_presences, false_presences) end # @param if_node [Parser::AST::Node] @@ -372,6 +373,30 @@ def process_nilp(nilp_node, true_presences, false_presences) process_facts(if_false, false_presences) end + # @param bang_node [Parser::AST::Node] + # @return [Array(String, String), nil] + def parse_bang(bang_node) + parse_call(bang_node, :!) + end + + # @param bang_node [Parser::AST::Node] + # @param true_presences [Array] + # @param false_presences [Array] + # + # @return [void] + def process_bang(bang_node, true_presences, false_presences) + # pry(main)> require 'parser/current'; Parser::CurrentRuby.parse("!2") + # => s(:send, + # s(:int, 2), :!) + # end + return unless bang_node.type == :send && bang_node.children[1] == :! + + receiver = bang_node.children[0] + + # swap the two presences + process_expression(receiver, false_presences, true_presences) + end + # @param var_node [Parser::AST::Node] # # @return [String, nil] Variable name referenced diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 27c34cb93..8bd6ae3f6 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -944,8 +944,6 @@ def a b end it 'uses ! to detect nilness' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_bang' - source = Solargraph::Source.load_string(%( class A # @param a [Integer, nil] From 5d1e9aeaaeba8357a47fc0f9a487af40869cafe1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 12 Oct 2025 12:21:41 -0400 Subject: [PATCH 513/930] Support 'return if foo.nil?' --- .rubocop_todo.yml | 8 +++++++- lib/solargraph/pin/method.rb | 1 + spec/parser/flow_sensitive_typing_spec.rb | 8 -------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 87011662b..aef9fa8fc 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -468,6 +468,7 @@ Metrics/ClassLength: Exclude: - 'lib/solargraph/api_map.rb' - 'lib/solargraph/language_server/host.rb' + - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/rbs_map/conversions.rb' - 'lib/solargraph/type_checker.rb' @@ -1165,7 +1166,12 @@ Style/SlicingWithRange: # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowModifier. Style/SoleNestedConditional: - Enabled: false + Exclude: + - 'lib/solargraph/complex_type/unique_type.rb' + - 'lib/solargraph/pin/parameter.rb' + - 'lib/solargraph/source.rb' + - 'lib/solargraph/source/source_chainer.rb' + - 'lib/solargraph/type_checker.rb' # This cop supports safe autocorrection (--autocorrect). Style/StderrPuts: diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 011f096f6..871708253 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -6,6 +6,7 @@ module Pin # class Method < Callable include Solargraph::Parser::NodeMethods + include CompoundStatementable # @return [::Symbol] :public, :private, or :protected attr_reader :visibility diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 8bd6ae3f6..c5f443d8e 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -605,8 +605,6 @@ def verify_repro(repr = nil) end it 'uses .nil? in a return if() in an if to refine types using nil checks' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( class Foo # @param baz [::Boolean, nil] @@ -629,8 +627,6 @@ def bar(baz: nil) # https://cse.buffalo.edu/~regan/cse305/RubyBNF.pdf # https://ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/syntax.html it 'uses .nil? in a return if() in a method to refine types using nil checks' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( class Foo # @param baz [::Boolean, nil] @@ -819,8 +815,6 @@ def bar(baz: nil) end it 'uses .nil? in a return if() in a begin/end to refine types using nil checks' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( class Foo # @param baz [::Boolean, nil] @@ -848,8 +842,6 @@ def bar(baz: nil) end it 'uses .nil? in a return if() in a ||= to refine types using nil checks' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( class Foo # @param baz [::Boolean, nil] From 0456bd9a240f44e36bcdcb64e151e73f21ef60b9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 12 Oct 2025 12:39:04 -0400 Subject: [PATCH 514/930] Support 'foo = nilable || not_nilable' --- lib/solargraph/complex_type.rb | 5 +++++ lib/solargraph/source/chain/or.rb | 7 ++++++- spec/source/chain/or_spec.rb | 2 -- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 669a66900..408b896e5 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -237,6 +237,11 @@ def nullable? @items.any?(&:nil_type?) end + # @return [ComplexType] + def without_nil + ComplexType.new(@items.reject(&:nil_type?)) + end + # @return [Array] def all_params @items.first.all_params || [] diff --git a/lib/solargraph/source/chain/or.rb b/lib/solargraph/source/chain/or.rb index 1e3a70f40..fd56b52c0 100644 --- a/lib/solargraph/source/chain/or.rb +++ b/lib/solargraph/source/chain/or.rb @@ -15,7 +15,12 @@ def initialize links def resolve api_map, name_pin, locals types = @links.map { |link| link.infer(api_map, name_pin, locals) } - [Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.new(types.uniq), source: :chain)] + combined_type = Solargraph::ComplexType.new(types) + unless types.all?(&:nullable?) + combined_type = combined_type.without_nil + end + + [Solargraph::Pin::ProxyType.anonymous(combined_type, source: :chain)] end end end diff --git a/spec/source/chain/or_spec.rb b/spec/source/chain/or_spec.rb index 85019a0b7..78bddf34d 100644 --- a/spec/source/chain/or_spec.rb +++ b/spec/source/chain/or_spec.rb @@ -17,8 +17,6 @@ def foo a end it 'removes nil from more complex cases' do - pending 'flow-sensitive typing improvements' - source = Solargraph::Source.load_string(%( def foo out = ENV['BAR'] || From 9c6c1c143abe0ee51a3804cd450e9cf4f328a884 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 12 Oct 2025 13:05:46 -0400 Subject: [PATCH 515/930] Drop 'pending's which are already working This would have failed specs except there was already another 'pending' later. Wonder why RSpec doesn't figure this out itself and complain... --- spec/parser/flow_sensitive_typing_spec.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index c5f443d8e..a4f4c330e 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -528,8 +528,6 @@ def verify_repro(repr, throw_the_dice) end it 'uses varname and && in a simple if() - varname second' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( # @param repr [Integer, nil] # @param throw_the_dice [Boolean] @@ -674,8 +672,6 @@ def bar(arr, baz: nil) end it 'uses .nil? in a return if() in an unless to refine types using nil checks' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( class Foo # @param baz [::Boolean, nil] @@ -751,8 +747,6 @@ def bar(baz: nil) end it 'uses .nil? in a return if() in a switch/case/else to refine types using nil checks' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( class Foo # @param baz [::Boolean, nil] @@ -784,8 +778,6 @@ def bar(baz: nil) end it 'uses .nil? in a return if() in a ternary operator to refine types using nil checks' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( class Foo # @param baz [::Boolean, nil] @@ -869,8 +861,6 @@ def bar(baz: nil) end it 'uses .nil? in a return if() in a try / rescue / ensure to refine types using nil checks' do - pending 'lib/solargraph/parser/flow_sensitive_typing.rb#parse_nilp' - source = Solargraph::Source.load_string(%( class Foo # @param baz [::Boolean, nil] From 10e7306008a2bf1db6217c773e376ad09b3d9830 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 12 Oct 2025 13:24:20 -0400 Subject: [PATCH 516/930] Handle 'foo = nilable_local || non_nilable' scenarios --- lib/solargraph/parser/parser_gem/node_methods.rb | 3 --- spec/parser/node_methods_spec.rb | 4 ---- spec/source/chain/or_spec.rb | 2 -- spec/type_checker/levels/alpha_spec.rb | 2 -- 4 files changed, 11 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 02f790c00..aa1ab4692 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -302,7 +302,6 @@ def repaired_find_recipient_node cursor module DeepInference class << self CONDITIONAL_ALL_BUT_FIRST = [:if, :unless] - CONDITIONAL_ALL = [:or] ONLY_ONE_CHILD = [:return] FIRST_TWO_CHILDREN = [:rescue] COMPOUND_STATEMENTS = [:begin, :kwbegin] @@ -349,8 +348,6 @@ def from_value_position_statement node, include_explicit_returns: true elsif CONDITIONAL_ALL_BUT_FIRST.include?(node.type) result.concat reduce_to_value_nodes(node.children[1..-1]) # result.push NIL_NODE unless node.children[2] - elsif CONDITIONAL_ALL.include?(node.type) - result.concat reduce_to_value_nodes(node.children) elsif ONLY_ONE_CHILD.include?(node.type) result.concat reduce_to_value_nodes([node.children[0]]) elsif FIRST_TWO_CHILDREN.include?(node.type) diff --git a/spec/parser/node_methods_spec.rb b/spec/parser/node_methods_spec.rb index e5cd0c78f..675f15ce4 100644 --- a/spec/parser/node_methods_spec.rb +++ b/spec/parser/node_methods_spec.rb @@ -185,8 +185,6 @@ end it "handles top 'or' nodes" do - pending 'flow-sensitive typing improvements' - node = Solargraph::Parser.parse('1 || "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) expect(rets.length).to eq(1) @@ -336,8 +334,6 @@ end it "handles top 'or' nodes" do - pending 'flow-sensitive typing improvements' - node = Solargraph::Parser.parse('1 || "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:or]) diff --git a/spec/source/chain/or_spec.rb b/spec/source/chain/or_spec.rb index 78bddf34d..084738fe3 100644 --- a/spec/source/chain/or_spec.rb +++ b/spec/source/chain/or_spec.rb @@ -1,7 +1,5 @@ describe Solargraph::Source::Chain::Or do it 'handles simple nil-removal' do - pending 'flow-sensitive typing improvements' - source = Solargraph::Source.load_string(%( # @param a [Integer, nil] def foo a diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index d6c58df00..781907d33 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -21,8 +21,6 @@ def bar(baz: nil) end it 'does not falsely enforce nil in return types' do - pending 'flow-sensitive typing improvements' - checker = type_checker(%( # @return [Integer] def foo From 6f5d60a4be1ea8ef9def194c508c87062dad2490 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 12 Oct 2025 16:04:45 -0400 Subject: [PATCH 517/930] Handle 'while foo' --- .../parser/flow_sensitive_typing.rb | 35 +++++++++++++++++++ lib/solargraph/parser/node_processor/base.rb | 5 +++ .../parser_gem/node_processors/while_node.rb | 5 ++- spec/parser/flow_sensitive_typing_spec.rb | 28 +++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 3ded6010a..071a848bd 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -159,6 +159,41 @@ def process_if(if_node, true_ranges = [], false_ranges = []) process_expression(conditional_node, true_ranges, false_ranges) end + # @param while_node [Parser::AST::Node] + # @param true_ranges [Array] + # @param false_ranges [Array] + # + # @return [void] + def process_while(while_node, true_ranges = [], false_ranges = []) + return if while_node.type != :while + + # + # See if we can refine a type based on the result of 'if foo.nil?' + # + # [3] pry(main)> Parser::CurrentRuby.parse("while a; b; c; end") + # => s(:while, + # s(:send, nil, :a), + # s(:begin, + # s(:send, nil, :b), + # s(:send, nil, :c))) + # [4] pry(main)> + conditional_node = while_node.children[0] + # @type [Parser::AST::Node, nil] + do_clause = while_node.children[1] + + unless do_clause.nil? + # + # If the condition is true we can assume things about the do clause + # + before_do_clause_loc = do_clause.location.expression.adjust(begin_pos: -1) + before_do_clause_pos = Position.new(before_do_clause_loc.line, before_do_clause_loc.column) + true_ranges << Range.new(before_do_clause_pos, + get_node_end_position(do_clause)) + end + + process_expression(conditional_node, true_ranges, false_ranges) + end + class << self include Logging end diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index 4d4123d9d..1a79bea65 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -40,6 +40,11 @@ def process private + # @return [Solargraph::Location] + def location + get_node_location(node) + end + # @return [Solargraph::Position] def position Position.new(node.loc.line, node.loc.column) diff --git a/lib/solargraph/parser/parser_gem/node_processors/while_node.rb b/lib/solargraph/parser/parser_gem/node_processors/while_node.rb index c9211448e..4b025121e 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/while_node.rb @@ -8,7 +8,10 @@ class WhileNode < Parser::NodeProcessor::Base include ParserGem::NodeMethods def process - location = get_node_location(node) + FlowSensitiveTyping.new(locals, + enclosing_breakable_pin, + enclosing_compound_statement_pin).process_while(node) + # Note - this should not be considered a block, as the # while statement doesn't create a closure - e.g., # variables created inside can be seen from outside as diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index a4f4c330e..cd2e7b390 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -723,6 +723,34 @@ def bar(baz: nil) expect(clip.infer.rooted_tags).to eq('::Boolean, nil') end + it 'uses foo in a a while to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @param other [::Boolean, nil] + # @return [void] + def bar(baz: nil, other: nil) + baz + while baz do + baz + baz = other + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [11, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + it 'uses .nil? in a return if() in an until to refine types using nil checks' do source = Solargraph::Source.load_string(%( class Foo From de6df119c9e4300f3fca8712dd8eeff08fd82326 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 12 Oct 2025 19:41:56 -0400 Subject: [PATCH 518/930] Add @sg-ignores --- lib/solargraph/parser/flow_sensitive_typing.rb | 1 + lib/solargraph/rbs_map.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index fb3976d9c..14bdaa304 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -347,6 +347,7 @@ def parse_isa(isa_node) # # @return [Solargraph::Pin::LocalVariable, nil] def find_local(variable_name, position) + # @sg-ignore Need to add nil check here pins = locals.select { |pin| pin.name == variable_name && pin.presence.include?(position) } pins.first end diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index 670fa2431..561bf8f1b 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -162,6 +162,7 @@ def add_library loader, library, version # @return [String] def short_name + # @sg-ignore Need to add nil check here self.class.name.split('::').last end end From f9294571ddcf1f60b83b022d0fb28616d827fc08 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 12 Oct 2025 19:47:16 -0400 Subject: [PATCH 519/930] Fix @sg-ignores --- .../language_server/message/extended/check_gem_version.rb | 2 +- lib/solargraph/library.rb | 2 +- lib/solargraph/type_checker/rules.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 6518affb4..a9f9d22ec 100644 --- a/lib/solargraph/language_server/message/extended/check_gem_version.rb +++ b/lib/solargraph/language_server/message/extended/check_gem_version.rb @@ -73,7 +73,7 @@ def process attr_reader :current # @return [Gem::Version] - # @sg-ignore flow sensitive typing needs to handle "if foo ... else" + # @sg-ignore flow sensitive typing needs to handle ivars def available if !@available && !@fetched @fetched = true diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index dab4c394f..bdacb9faf 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -559,7 +559,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 && ... + # @sg-ignore flow sensitive typing needs to handle ivars # @return [Solargraph::Source] def read filename # @sg-ignore flow sensitive typing needs to handle ivars diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 96eebf3e5..85987336a 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,7 +58,7 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # @todo 260: Need to add nil check here + # @todo 262: Need to add nil check here # @todo 39: flow sensitive typing needs to handle ivars # @todo 9: Need to validate config # @todo 8: Should handle redefinition of types in simple contexts From 18036998929624d86dfc54388297d055a892bad8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 14 Oct 2025 20:11:07 -0400 Subject: [PATCH 520/930] Update rules.rb counts --- lib/solargraph/type_checker/rules.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 85987336a..cdaefb706 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -69,13 +69,13 @@ def require_inferred_type_params? # @todo 5: need to improve handling of &. # @todo 5: flow sensitive typing needs to handle return if foo.nil? || bar # @todo 4: Translate to something flow sensitive typing understands + # @todo 4: flow sensitive typing needs to handle && on both sides # @todo 3: downcast output of Enumerable#select # @todo 3: flow sensitive typing needs better handling of ||= on lvars # @todo 3: flow sensitive typing needs to handle 'raise if' # @todo 2: Need to look at Tuple#include? handling # @todo 2: Should better support meaning of '&' in RBS # @todo 2: flow sensitive typing needs to handle "if foo = bar" - # @todo 2: flow sensitive typing needs to handle && on both sides # @todo 2: Need a downcast here # @todo 1: Need to look at infer handling of recursive methods # @todo 1: flow sensitive typing needs to handle if !foo From 82183e4ad4c0072ed84a1bb54c17c6d75716c4bd Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 15 Oct 2025 08:35:57 -0400 Subject: [PATCH 521/930] Handle 'foo unless unrelated || repr.nil? --- lib/solargraph/parser/flow_sensitive_typing.rb | 4 ++-- spec/parser/flow_sensitive_typing_spec.rb | 10 +++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 071a848bd..42143d6a4 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -63,8 +63,8 @@ def process_or(or_node, true_ranges = [], false_ranges = []) # can't assume if an or is true that every single condition is # true, so don't provide true ranges to assert facts on - process_expression(lhs, [], [rhs_presence]) - process_expression(rhs, [], []) + process_expression(lhs, [], false_ranges + [rhs_presence]) + process_expression(rhs, [], false_ranges) end # @param node [Parser::AST::Node] diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index cd2e7b390..50b356f40 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -404,7 +404,7 @@ def verify_repro(repr, throw_the_dice) expect(clip.infer.rooted_tags).to eq('::Integer, nil') clip = api_map.clip_at('test.rb', [8, 10]) - expect(clip.infer.rooted_tags).to eq('::Integer, nil') + expect(clip.infer.rooted_tags).to eq('::Integer') end it 'uses nil? and || in a simple if() - nil? second' do @@ -428,7 +428,7 @@ def verify_repro(repr, throw_the_dice) expect(clip.infer.rooted_tags).to eq('::Integer, nil') clip = api_map.clip_at('test.rb', [8, 10]) - expect(clip.infer.rooted_tags).to eq('::Integer, nil') + expect(clip.infer.rooted_tags).to eq('::Integer') end it 'uses varname and || in a simple if() - varname first' do @@ -451,8 +451,6 @@ def verify_repro(repr, throw_the_dice) clip = api_map.clip_at('test.rb', [6, 10]) expect(clip.infer.rooted_tags).to eq('::Integer, nil') - pending('supporting else after || on varname') - clip = api_map.clip_at('test.rb', [8, 10]) expect(clip.infer.rooted_tags).to eq('nil') end @@ -477,8 +475,6 @@ def verify_repro(repr, throw_the_dice) clip = api_map.clip_at('test.rb', [6, 10]) expect(clip.infer.rooted_tags).to eq('::Integer, nil') - pending('supporting else after || on varname') - clip = api_map.clip_at('test.rb', [8, 10]) expect(clip.infer.rooted_tags).to eq('nil') end @@ -497,7 +493,7 @@ def verify_repro(repr) expect(clip.infer.rooted_tags).to eq('::String') clip = api_map.clip_at('test.rb', [4, 8]) - expect(clip.infer.rooted_tags).to eq('::String, nil') + expect(clip.infer.rooted_tags).to eq('::String') clip = api_map.clip_at('test.rb', [5, 8]) expect(clip.infer.rooted_tags).to eq('::String, nil') From 06ecb08f6d70024a9942b4eb695e35099c7c5662 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 15 Oct 2025 08:58:28 -0400 Subject: [PATCH 522/930] Drop @sg-ignores --- lib/solargraph/api_map/constants.rb | 1 - lib/solargraph/api_map/source_to_yard.rb | 1 - lib/solargraph/api_map/store.rb | 2 -- lib/solargraph/complex_type/unique_type.rb | 1 - lib/solargraph/convention/data_definition.rb | 1 - .../message/text_document/formatting.rb | 1 - lib/solargraph/pin/parameter.rb | 3 +-- lib/solargraph/rbs_map.rb | 2 +- lib/solargraph/source.rb | 1 - lib/solargraph/type_checker.rb | 2 -- lib/solargraph/type_checker/rules.rb | 12 ++++++++---- 11 files changed, 10 insertions(+), 17 deletions(-) diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index b8fb2fc8b..95f997071 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -245,7 +245,6 @@ def inner_qualify name, root, skip # @return [Array] def inner_get_constants fqns, visibility, skip return [] if fqns.nil? || skip.include?(fqns) - # @sg-ignore flow sensitive typing needs to handle return if foo.nil? || bar skip.add fqns result = [] diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index 76aa3ca8f..960432b96 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -53,7 +53,6 @@ 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? - # @sg-ignore flow sensitive typing needs to handle return if foo.nil? || bar include_object.instance_mixins.push code_object_map[ref.parametrized_tag.to_s] end end diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 175b0e4a7..ae0742bfd 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -83,10 +83,8 @@ def get_methods fqns, scope: :instance, visibility: [:public] # @return [Pin::Reference::Superclass, nil] def get_superclass fqns return nil if fqns.nil? || fqns.empty? - # @sg-ignore flow sensitive typing needs to handle return if foo.nil? || bar return BOOLEAN_SUPERCLASS_PIN if %w[TrueClass FalseClass].include?(fqns) - # @sg-ignore flow sensitive typing needs to handle "if foo.nil?" superclass_references[fqns].first || try_special_superclasses(fqns) end diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index e2700e4ee..1cdef80ba 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -340,7 +340,6 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge return self unless type_param && generics_to_resolve.include?(type_param) unless context_type.nil? || !resolved_generic_values[type_param].nil? new_binding = true - # @sg-ignore flow sensitive typing needs to handle return if foo.nil? || bar resolved_generic_values[type_param] = context_type end if new_binding diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index eb4517506..f4c943427 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -97,7 +97,6 @@ def attribute_comments(attribute_node, attribute_name) data_comments = comments_for(attribute_node) return if data_comments.nil? || data_comments.empty? - # @sg-ignore flow sensitive typing needs to handle return if foo.nil? || bar data_comments.split("\n").find do |row| row.include?(attribute_name) end&.gsub('@param', '@return')&.gsub(attribute_name, '') diff --git a/lib/solargraph/language_server/message/text_document/formatting.rb b/lib/solargraph/language_server/message/text_document/formatting.rb index 20dbde474..dbd6cc746 100644 --- a/lib/solargraph/language_server/message/text_document/formatting.rb +++ b/lib/solargraph/language_server/message/text_document/formatting.rb @@ -97,7 +97,6 @@ def formatter_class(config) # @param value [Array, String] # - # @sg-ignore Need to handle this case in flow sensitive typing # @return [String, nil] def cop_list(value) # @type [String] diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index f0f1fa1e2..22cf980e0 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -152,7 +152,6 @@ def return_type if @return_type.nil? @return_type = ComplexType::UNDEFINED found = param_tag - # @sg-ignore flow sensitive typing needs to handle ivars @return_type = ComplexType.try_parse(*found.types) unless found.nil? or found.types.nil? # @sg-ignore Need to add nil check here if @return_type.undefined? @@ -201,10 +200,10 @@ def compatible_arg?(atype, api_map) ptype.generic? end + # @sg-ignore flow sensitive typing needs to handle ivars def documentation tag = param_tag return '' if tag.nil? || tag.text.nil? - # @sg-ignore flow sensitive typing needs to handle ivars tag.text end diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index 561bf8f1b..d991176ce 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -68,7 +68,6 @@ def cache_key 'unresolved' end else - # @sg-ignore flow sensitive typing needs to handle return if foo.nil? || bar Digest::SHA1.hexdigest(data) end end @@ -160,6 +159,7 @@ def add_library loader, library, version end end + # @sg-ignore Need to add nil check here # @return [String] def short_name # @sg-ignore Need to add nil check here diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index c8b523b1b..86b109ce7 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -268,7 +268,6 @@ def associated_comments end last = num end - # @sg-ignore Need to add nil check here result[first_not_empty_from(last + 1)] = buffer unless buffer.empty? || last.nil? result end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index ee32f9d8c..de1ba2e99 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -337,7 +337,6 @@ def call_problems 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 - # @sg-ignore Need to support nested flow sensitive types if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) # @sg-ignore Need to add nil check here unless closest.generic? || ignored_pins.include?(found) @@ -699,7 +698,6 @@ def declared_externally? pin end all_closest = all_found.map { |pin| pin.typify(api_map) } closest = ComplexType.new(all_closest.flat_map(&:items).uniq) - # @sg-ignore Need to support nested flow sensitive types if !found || closest.defined? || internal?(found) return false end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index cdaefb706..1f425c8db 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,17 +58,21 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end + # pending code fixes: + # # @todo 262: Need to add nil check here - # @todo 39: flow sensitive typing needs to handle ivars # @todo 9: Need to validate config + # @todo 4: Translate to something flow sensitive typing understands + # + # flow-sensitive typing could handle: + # + # @todo 39: flow sensitive typing needs to handle ivars # @todo 8: Should handle redefinition of types in simple contexts # @todo 7: Need support for reduce_class_type in UniqueType # @todo 7: flow sensitive typing needs to handle inner closures # @todo 6: Need to support nested flow sensitive types # @todo 5: need boolish support for ? methods - # @todo 5: need to improve handling of &. - # @todo 5: flow sensitive typing needs to handle return if foo.nil? || bar - # @todo 4: Translate to something flow sensitive typing understands + # @todo 4: need to improve handling of &. # @todo 4: flow sensitive typing needs to handle && on both sides # @todo 3: downcast output of Enumerable#select # @todo 3: flow sensitive typing needs better handling of ||= on lvars From cc32200f88f8d17df3f0e82fd93b7493eaebd990 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 19 Oct 2025 13:38:26 -0400 Subject: [PATCH 523/930] Feedback from CI --- lib/solargraph/rbs_map.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index d991176ce..d4bb55cd4 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -159,10 +159,8 @@ def add_library loader, library, version end end - # @sg-ignore Need to add nil check here # @return [String] def short_name - # @sg-ignore Need to add nil check here self.class.name.split('::').last end end From 1a4ef7876f338b346187f12fd608f388f90e9c33 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 21 Oct 2025 23:04:41 -0400 Subject: [PATCH 524/930] Use presence information to improve local variable type probing * Use Pin::LocalVariable#combine_with to merge information from multiple assignments to the same local variable that may be visible from a given location. * Offer a single pin with consolidated information with ApiMap#var_at_location * fix: Ensure Pin::Base#reset_generated! gets called consistently when information is invalidated. * fix: Ensure #reduce_class_type is in both ComplexType and UniqueType * fix: Track location information more consistently so that we can use it to determine visibility of assignments. * fix: Handle a number of Pin::Block rebinding cases more consistently so that we can rely on the closure information inside to determine visibility of assignments. * Improve some Pin::Base#inspect representations for easier debugging. --- .rubocop_todo.yml | 13 +- lib/solargraph/api_map.rb | 24 +- lib/solargraph/api_map/index.rb | 12 +- lib/solargraph/complex_type/unique_type.rb | 11 + lib/solargraph/location.rb | 8 +- .../parser/flow_sensitive_typing.rb | 40 - .../parser/parser_gem/class_methods.rb | 15 +- .../parser/parser_gem/node_chainer.rb | 7 +- .../parser_gem/node_processors/block_node.rb | 21 +- .../parser_gem/node_processors/def_node.rb | 6 + lib/solargraph/pin/base.rb | 4 +- lib/solargraph/pin/base_variable.rb | 114 ++- lib/solargraph/pin/block.rb | 17 +- lib/solargraph/pin/local_variable.rb | 101 ++- lib/solargraph/pin/method.rb | 5 + lib/solargraph/pin/parameter.rb | 19 +- lib/solargraph/source.rb | 4 +- lib/solargraph/source/chain.rb | 3 +- lib/solargraph/source/chain/call.rb | 25 +- lib/solargraph/source/source_chainer.rb | 7 +- lib/solargraph/source_map.rb | 2 +- lib/solargraph/source_map/clip.rb | 2 +- lib/solargraph/type_checker.rb | 4 +- spec/parser/flow_sensitive_typing_spec.rb | 806 +++++++++++++++++- spec/parser/node_chainer_spec.rb | 26 +- spec/parser/node_methods_spec.rb | 102 +-- spec/parser/node_processor_spec.rb | 12 +- spec/parser_spec.rb | 8 +- spec/pin/local_variable_spec.rb | 159 ++++ spec/source/chain/call_spec.rb | 31 +- spec/source/chain_spec.rb | 3 +- spec/source_map/clip_spec.rb | 6 +- spec/source_map_spec.rb | 16 +- spec/type_checker/levels/strong_spec.rb | 375 +++++++- 34 files changed, 1751 insertions(+), 257 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 83339e756..c68bb6c0a 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' @@ -458,7 +457,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: @@ -469,6 +468,7 @@ Metrics/ClassLength: Exclude: - 'lib/solargraph/api_map.rb' - 'lib/solargraph/language_server/host.rb' + - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/rbs_map/conversions.rb' - 'lib/solargraph/type_checker.rb' @@ -773,7 +773,6 @@ Style/AndOr: # RedundantBlockArgumentNames: blk, block, proc Style/ArgumentsForwarding: Exclude: - - 'lib/solargraph/api_map.rb' - 'lib/solargraph/complex_type.rb' # This cop supports safe autocorrection (--autocorrect). @@ -1188,7 +1187,6 @@ Style/StringLiterals: # This cop supports safe autocorrection (--autocorrect). Style/SuperArguments: Exclude: - - 'lib/solargraph/pin/base_variable.rb' - 'lib/solargraph/pin/callable.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/signature.rb' @@ -1230,7 +1228,12 @@ Style/TrailingCommaInArrayLiteral: # Configuration parameters: EnforcedStyleForMultiline. # SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma Style/TrailingCommaInHashLiteral: - Enabled: false + 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/rbs_map/conversions.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, AllowedMethods. diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 71855d04a..eaa64b756 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -346,10 +346,26 @@ 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) + # Find a variable pin by name and where it is used. + # + # Resolves our most specific view of this variable's type by + # preferring pins created by flow-sensitive typing when we have + # them based on the Closure and Location. + # + # @param locals [Array] + # @param name [String] + # @param closure [Pin::Closure] + # @param location [Location] + # + # @return [Pin::LocalVariable, nil] + def var_at_location(locals, name, closure, location) + with_correct_name = locals.select { |pin| pin.name == name} + with_presence = with_correct_name.reject { |pin| pin.presence.nil? } + vars_at_location = with_presence.reject do |pin| + (!pin.visible_at?(closure, location) && + !pin.starts_at?(location)) + end + vars_at_location.inject(&:combine_with) end # Get an array of class variable pins for a namespace. diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 35d86446a..d5f5f1821 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -140,10 +140,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 @@ -160,7 +163,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/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 05a585dcf..6b941d76d 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -437,6 +437,17 @@ def self_to_type dst end end + # @return [ComplexType] + 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 + ComplexType.new(new_items) + end + def all_rooted? return true if name == GENERIC_TAG_NAME rooted? && all_params.all?(&:rooted?) diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index 713b4fef1..592da03bf 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -13,9 +13,11 @@ class Location # @return [Solargraph::Range] attr_reader :range - # @param filename [String] + # @param filename [String, nil] # @param range [Solargraph::Range] def initialize filename, range + raise "Use nil to represent no-file" if filename&.empty? + @filename = filename @range = range end @@ -64,8 +66,10 @@ def to_hash # @return [Location, nil] def self.from_node(node) return nil if node.nil? || node.loc.nil? + filename = node.loc.expression.source_buffer.name + filename = nil if filename.empty? range = Range.from_node(node) - self.new(node.loc.expression.source_buffer.name, range) + self.new(filename, range) end # @param other [BasicObject] diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 41ce6eeaf..d589488da 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -75,46 +75,6 @@ class << self include Logging end - # Find a variable pin by name and where it is used. - # - # Resolves our most specific view of this variable's type by - # preferring pins created by flow-sensitive typing when we have - # them based on the Closure and Location. - # - # @param pins [Array] - # @param name [String] - # @param closure [Pin::Closure] - # @param location [Location] - # - # @return [Array] - def self.visible_pins(pins, name, closure, location) - logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location})" } - pins_with_name = pins.select { |p| p.name == name } - if pins_with_name.empty? - logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => [] - no pins with name" } - return [] - end - pins_with_specific_visibility = pins.select { |p| p.name == name && p.presence && p.visible_at?(closure, location) } - if pins_with_specific_visibility.empty? - logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{pins_with_name} - no pins with specific visibility" } - return pins_with_name - end - visible_pins_specific_to_this_closure = pins_with_specific_visibility.select { |p| p.closure == closure } - if visible_pins_specific_to_this_closure.empty? - logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{pins_with_specific_visibility} - no visible pins specific to this closure (#{closure})}" } - return pins_with_specific_visibility - end - flow_defined_pins = pins_with_specific_visibility.select { |p| p.presence_certain? } - if flow_defined_pins.empty? - logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{visible_pins_specific_to_this_closure} - no flow-defined pins" } - return visible_pins_specific_to_this_closure - end - - logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{flow_defined_pins}" } - - flow_defined_pins - end - include Logging private diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index 2daf22fc7..adafbae32 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -8,19 +8,22 @@ module ParserGem module ClassMethods # @param code [String] # @param filename [String, nil] + # @param starting_line [Integer] must be provided so that we + # can find relevant local variables later even if this is just + # a subset of the file in question # @return [Array(Parser::AST::Node, Hash{Integer => Solargraph::Parser::Snippet})] - def parse_with_comments code, filename = nil - node = parse(code, filename) + def parse_with_comments code, filename, starting_line + node = parse(code, filename, starting_line) comments = CommentRipper.new(code, filename, 0).parse [node, comments] end # @param code [String] - # @param filename [String, nil] - # @param line [Integer] + # @param filename [String] + # @param starting_line [Integer] # @return [Parser::AST::Node] - def parse code, filename = nil, line = 0 - buffer = ::Parser::Source::Buffer.new(filename, line) + def parse code, filename, starting_line + buffer = ::Parser::Source::Buffer.new(filename, starting_line) buffer.source = code parser.parse(buffer) rescue ::Parser::SyntaxError, ::Parser::UnknownEncodingInMagicComment => e diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index d8d46319b..438d99811 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -35,9 +35,12 @@ def chain node, filename = nil, parent = nil end # @param code [String] + # @param filename [String] + # @param starting_line [Integer] + # # @return [Source::Chain] - def load_string(code) - node = Parser.parse(code.sub(/\.$/, '')) + def load_string(code, filename, starting_line) + node = Parser.parse(code.sub(/\.$/, ''), filename, starting_line) chain = NodeChainer.new(node).chain chain.links.push(Chain::Link.new) if code.end_with?('.') chain 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 d773e8e50..c77713fad 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/block_node.rb @@ -9,23 +9,22 @@ class BlockNode < Parser::NodeProcessor::Base def process location = get_node_location(node) - parent = if other_class_eval? - Solargraph::Pin::Namespace.new( - location: location, - type: :class, - name: unpack_name(node.children[0].children[0]), - source: :parser, - ) - else - region.closure + binder = nil + scope = region.scope || region.closure.context.scope + if other_class_eval? + clazz_name = unpack_name(node.children[0].children[0]) + binder = ComplexType.try_parse("Class<#{clazz_name}>") + scope = :class end block_pin = Solargraph::Pin::Block.new( location: location, - closure: parent, + closure: region.closure, node: node, + context: binder, + binder: binder, receiver: node.children[0], comments: comments_for(node), - scope: region.scope || region.closure.context.scope, + scope: scope, source: :parser ) pins.push block_pin diff --git a/lib/solargraph/parser/parser_gem/node_processors/def_node.rb b/lib/solargraph/parser/parser_gem/node_processors/def_node.rb index 47c01e728..5d0d9a284 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/def_node.rb @@ -12,6 +12,8 @@ def process location: get_node_location(node), closure: region.closure, name: name, + context: region.closure.binder.namespace_type, + binder: region.closure.binder.namespace_type, comments: comments_for(node), scope: scope, visibility: scope == :instance && name == 'initialize' ? :private : region.visibility, @@ -23,6 +25,8 @@ def process location: methpin.location, closure: methpin.closure, name: methpin.name, + context: region.closure.binder, + binder: region.closure.binder, comments: methpin.comments, scope: :class, visibility: :public, @@ -34,6 +38,8 @@ def process location: methpin.location, closure: methpin.closure, name: methpin.name, + context: region.closure.binder, + binder: region.closure.binder, comments: methpin.comments, scope: :instance, visibility: :private, diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 511c7deb7..d56f0fffd 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -81,7 +81,6 @@ def closure # # @return [self] def combine_with(other, attrs={}) - raise "tried to combine #{other.class} with #{self.class}" unless other.class == self.class priority_choice = choose_priority(other) return priority_choice unless priority_choice.nil? @@ -572,6 +571,7 @@ def proxy return_type result = dup result.return_type = return_type result.proxied = true + result.reset_generated! result end @@ -616,7 +616,7 @@ def type_desc # @return [String] def inner_desc - closure_info = closure&.desc + closure_info = closure&.name.inspect binder_info = binder&.desc "name=#{name.inspect} return_type=#{type_desc}, context=#{context.rooted_tags}, closure=#{closure_info}, binder=#{binder_info}" end diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 764c1fb39..af1d44c9a 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -6,28 +6,52 @@ class BaseVariable < Base # include Solargraph::Source::NodeMethods include Solargraph::Parser::NodeMethods - # @return [Parser::AST::Node, nil] - attr_reader :assignment + # @return [Array] + attr_reader :assignments attr_accessor :mass_assignment # @param return_type [ComplexType, nil] - # @param assignment [Parser::AST::Node, nil] - def initialize assignment: nil, return_type: nil, **splat + # @param assignment [Parser::AST::Node, nil] First assignment + # that was made to this variable + # @param assignments [Array] Possible + # assignments that may have been made to this variable + # @param mass_assignment [Array(Parser::AST::Node, Integer), nil] + def initialize assignment: nil, assignments: [], mass_assignment: nil, return_type: nil, **splat super(**splat) - @assignment = assignment + @assignments = (assignment.nil? ? [] : [assignment]) + assignments # @type [nil, ::Array(Parser::AST::Node, Integer)] - @mass_assignment = nil + @mass_assignment = mass_assignment @return_type = return_type end + def reset_generated! + @assignment = nil + super + end + def combine_with(other, attrs={}) - attrs.merge({ - assignment: assert_same(other, :assignment), + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 + new_assignments = combine_assignments(other) + new_attrs = attrs.merge({ + assignments: new_assignments, mass_assignment: assert_same(other, :mass_assignment), return_type: combine_return_type(other), - }) - super(other, attrs) + }) + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 + super(other, new_attrs) + end + + # @return [Parser::AST::Node, nil] + def assignment + @assignment ||= assignments.last + end + + # @param other [self] + # + # @return [::Array] + def combine_assignments(other) + (other.assignments + assignments).uniq end def completion_item_kind @@ -80,11 +104,13 @@ def return_types_from_node(parent_node, api_map) # @param api_map [ApiMap] # @return [ComplexType] def probe api_map - unless @assignment.nil? - types = return_types_from_node(@assignment, api_map) - return ComplexType.new(types.uniq) unless types.empty? - end + assignment_types = assignments.flat_map { |node| return_types_from_node(node, api_map) } + type_from_assignment = ComplexType.new(assignment_types.flat_map(&:items).uniq) unless assignment_types.empty? + return type_from_assignment unless type_from_assignment.nil? + # @todo should handle merging types from mass assignments as + # well so that we can do better flow sensitive typing with + # multiple assignments unless @mass_assignment.nil? mass_node, index = @mass_assignment types = return_types_from_node(mass_node, api_map) @@ -95,7 +121,7 @@ def probe api_map type.all_params.first end end.compact! - return ComplexType.new(types.uniq) unless types.empty? + return ComplexType.new(types.uniq).qualify(api_map, *gates) unless types.empty? end ComplexType::UNDEFINED @@ -113,6 +139,64 @@ def type_desc private + # See if this variable is visible within 'other_closure' + # + # @param other_closure [Pin::Closure] + # @return [Boolean] + def visible_in_closure? other_closure + needle = closure + haystack = other_closure + + cursor = haystack + + until cursor.nil? + if cursor.is_a?(Pin::Method) && closure.context.tags == 'Class<>' + # methods can't see local variables declared in their + # parent closure + return false + end + + if cursor.binder.namespace == needle.binder.namespace + return true + end + + if cursor.return_type == needle.context + return true + end + + if scope == :instance && cursor.is_a?(Pin::Namespace) + # classes and modules can't see local variables declared + # in their parent closure, so stop here + return false + end + + cursor = cursor.closure + end + false + end + + # @param other [self] + # @param attr [::Symbol] + # + # @return [ComplexType, nil] + def combine_types(other, attr) + # @type [ComplexType, nil] + type1 = send(attr) + # @type [ComplexType, nil] + type2 = other.send(attr) + if type1 && type2 + types = (type1.items + type2.items).uniq + ComplexType.new(types) + else + type1 || type2 + end + end + + # @return [::Symbol] + def scope + :instance + end + # @return [ComplexType] def generate_complex_type tag = docstring.tag(:type) diff --git a/lib/solargraph/pin/block.rb b/lib/solargraph/pin/block.rb index 227bc0873..7a63ccc13 100644 --- a/lib/solargraph/pin/block.rb +++ b/lib/solargraph/pin/block.rb @@ -12,14 +12,16 @@ class Block < Callable attr_reader :node # @param receiver [Parser::AST::Node, nil] + # @param binder [Pin::Namespace, nil] # @param node [Parser::AST::Node, nil] # @param context [ComplexType, nil] # @param args [::Array] - def initialize receiver: nil, args: [], context: nil, node: nil, **splat + def initialize receiver: nil, binder: nil, args: [], context: nil, node: nil, **splat super(**splat, parameters: args) @receiver = receiver @context = context @return_type = ComplexType.parse('::Proc') + @rebind = binder if binder @node = node end @@ -85,7 +87,7 @@ def typify_parameters(api_map) def maybe_rebind api_map return ComplexType::UNDEFINED unless receiver - chain = Parser.chain(receiver, location.filename) + chain = Parser.chain(receiver, location.filename, node) locals = api_map.source_map(location.filename).locals_at(location) receiver_pin = chain.define(api_map, closure, locals).first return ComplexType::UNDEFINED unless receiver_pin @@ -93,8 +95,15 @@ def maybe_rebind api_map types = receiver_pin.docstring.tag(:yieldreceiver)&.types return ComplexType::UNDEFINED unless types&.any? - target = chain.base.infer(api_map, receiver_pin, locals) - target = full_context unless target.defined? + name_pin = self + # if we have Foo.bar { |x| ... }, and the bar method references self... + target = if chain.base.defined? + # figure out Foo + chain.base.infer(api_map, name_pin, locals) + else + # if not, any self there must be the context of our closure + closure.full_context + end ComplexType.try_parse(*types).qualify(api_map, *receiver_pin.gates).self_to_type(target) end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 9eae6cc6f..427122329 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -3,75 +3,104 @@ module Solargraph module Pin class LocalVariable < BaseVariable - # @return [Range] + # @return [Range, nil] attr_reader :presence def presence_certain? @presence_certain end - # @param assignment [AST::Node, nil] # @param presence [Range, nil] # @param presence_certain [Boolean] # @param splat [Hash] - def initialize assignment: nil, presence: nil, presence_certain: false, **splat + def initialize presence: nil, presence_certain: false, **splat super(**splat) - @assignment = assignment @presence = presence @presence_certain = presence_certain end def combine_with(other, attrs={}) - new_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) + # keep this as a parameter + return other.combine_with(self, attrs) if other.is_a?(Parameter) && !self.is_a?(Parameter) + + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 + new_assignments = combine_assignments(other) + new_attrs = attrs.merge({ + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 + presence: combine_presence(other), + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 + presence_certain: combine_presence_certain(other), + }) super(other, new_attrs) end + def inner_desc + super + ", presence=#{presence.inspect}, presence_certain=#{presence_certain?}" + end + + # @param other_loc [Location] + def starts_at?(other_loc) + location&.filename == other_loc.filename && + presence && + presence.start == other_loc.range.start + end + # @param other_closure [Pin::Closure] # @param other_loc [Location] def visible_at?(other_closure, other_loc) location.filename == other_loc.filename && - presence.include?(other_loc.range.start) && - match_named_closure(other_closure, closure) + (!presence || presence.include?(other_loc.range.start)) && + visible_in_closure?(other_closure) end def to_rbs (name || '(anon)') + ' ' + (return_type&.to_rbs || 'untyped') end - private + # @param other [self] + # @return [ComplexType, nil] + def combine_return_type(other) + if presence_certain? && return_type&.defined? + # flow sensitive typing has already figured out this type + # has been downcast - use the type it figured out + return return_type + end + if other.presence_certain? && other.return_type&.defined? + return other.return_type + end + combine_types(other, :return_type) + end + + def probe api_map + if presence_certain? && return_type&.defined? + # flow sensitive typing has already probed this type - use + # the type it figured out + return return_type.qualify(api_map, *gates) + end - # @param tag1 [String] - # @param tag2 [String] - # @return [Boolean] - def match_tags tag1, tag2 - # @todo This is an unfortunate hack made necessary by a discrepancy in - # how tags indicate the root namespace. The long-term solution is to - # standardize it, whether it's `Class<>`, an empty string, or - # something else. - tag1 == tag2 || - (['', 'Class<>'].include?(tag1) && ['', 'Class<>'].include?(tag2)) + super end - # @param needle [Pin::Base] - # @param haystack [Pin::Base] + # Narrow the presence range to the intersection of both. + # + # @param other [self] + # + # @return [Range, nil] + def combine_presence(other) + return presence || other.presence if presence.nil? || other.presence.nil? + + Range.new([presence.start, other.presence.start].max, [presence.ending, other.presence.ending].min) + end + + # If a certain pin is being combined with an uncertain pin, we + # end up with a certain result + # + # @param other [self] + # # @return [Boolean] - def match_named_closure needle, haystack - return true if needle == haystack || haystack.is_a?(Pin::Block) - cursor = haystack - until cursor.nil? - return true if needle.path == cursor.path - return false if cursor.path && !cursor.path.empty? - cursor = cursor.closure - end - false + def combine_presence_certain(other) + presence_certain? || other.presence_certain? end end end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 011f096f6..94d254ddc 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -22,7 +22,10 @@ class Method < Callable # @param attribute [Boolean] # @param signatures [::Array, nil] # @param anon_splat [Boolean] + # @param context [ComplexType, nil] + # @param binder [ComplexType, nil] def initialize visibility: :public, explicit: true, block: :undefined, node: nil, attribute: false, signatures: nil, anon_splat: false, + context: nil, binder: nil, **splat super(**splat) @visibility = visibility @@ -32,6 +35,8 @@ def initialize visibility: :public, explicit: true, block: :undefined, node: nil @attribute = attribute @signatures = signatures @anon_splat = anon_splat + @context = context if context + @binder = binder if binder end # @return [Array] diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 947513689..af02a2f1c 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -30,12 +30,19 @@ def location end def combine_with(other, attrs={}) - new_attrs = { - decl: assert_same(other, :decl), - presence: choose(other, :presence), - asgn_code: choose(other, :asgn_code), - }.merge(attrs) - super(other, new_attrs) + # Parameters can be combined with local variables + new_attrs = if other.is_a?(Parameter) + { + decl: assert_same(other, :decl), + asgn_code: choose(other, :asgn_code) + } + else + { + decl: decl, + asgn_code: asgn_code + } + end + super(other, new_attrs.merge(attrs)) end def keyword? diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index ae5b08d3b..1bbfc1991 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -396,7 +396,7 @@ def finalize end @finalized = true begin - @node, @comments = Solargraph::Parser.parse_with_comments(@code, filename) + @node, @comments = Solargraph::Parser.parse_with_comments(@code, filename, 0) @parsed = true @repaired = @code rescue Parser::SyntaxError, EncodingError => e @@ -412,7 +412,7 @@ def finalize end error_ranges.concat(changes.map(&:range)) begin - @node, @comments = Solargraph::Parser.parse_with_comments(@repaired, filename) + @node, @comments = Solargraph::Parser.parse_with_comments(@repaired, filename, 0) @parsed = true rescue Parser::SyntaxError, EncodingError => e @node = nil diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index c08d04878..e941a347d 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -138,7 +138,8 @@ def define api_map, name_pin, locals # @return [ComplexType] # @sg-ignore def infer api_map, name_pin, locals - cache_key = [node, node&.location, links, name_pin&.return_type, locals] + # includes binder as it is mutable in Pin::Block + cache_key = [node, node&.location, links, name_pin&.return_type, name_pin&.binder, locals] if @@inference_invalidation_key == api_map.hash cached = @@inference_cache[cache_key] return cached if cached diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 24d10656d..08193afc3 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -50,12 +50,8 @@ def with_block? def resolve api_map, name_pin, locals return super_pins(api_map, name_pin) if word == 'super' return yield_pins(api_map, name_pin) if word == 'yield' - found = if head? - api_map.visible_pins(locals, word, name_pin, location) - else - [] - end - return inferred_pins(found, api_map, name_pin, locals) unless found.empty? + found = api_map.var_at_location(locals, word, name_pin, location) if head? + return inferred_pins([found], api_map, name_pin, locals) unless found.nil? pins = name_pin.binder.each_unique_type.flat_map do |context| ns_tag = context.namespace == '' ? '' : context.namespace_type.tag stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) @@ -67,7 +63,7 @@ def resolve api_map, name_pin, locals private - # @param pins [::Enumerable] + # @param pins [::Enumerable] # @param api_map [ApiMap] # @param name_pin [Pin::Base] # @param locals [::Array] @@ -98,7 +94,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) + arg_name_pin = Pin::ProxyType.anonymous(name_pin.context, + closure: name_pin.closure, + 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 @@ -309,7 +308,7 @@ def block_symbol_call_type(api_map, context, block_parameter_types, locals) # @return [Pin::Block, nil] def find_block_pin(api_map) node_location = Solargraph::Location.from_node(block.node) - return if node_location.nil? + return if node_location.nil? block_pins = api_map.get_block_pins block_pins.find { |pin| pin.location.contain?(node_location) } end @@ -322,10 +321,12 @@ def find_block_pin(api_map) def block_call_type(api_map, name_pin, locals) return nil unless with_block? - block_context_pin = name_pin block_pin = find_block_pin(api_map) - block_context_pin = block_pin.closure if block_pin - block.infer(api_map, block_context_pin, locals) + # We use the block pin as the closure, as the parameters + # here will only be defined inside the block itself and we need to be able to see them + + # @sg-ignore Need to add nil check here + block.infer(api_map, block_pin, locals) end end end diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index 5758a9d35..94dfb1a4d 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -44,14 +44,15 @@ def chain node, parent = tree[0..2] elsif source.parsed? && source.repaired? && end_of_phrase == '.' node, parent = source.tree_at(fixed_position.line, fixed_position.column)[0..2] - node = Parser.parse(fixed_phrase) if node.nil? + # provide filename and line so that we can look up local variables there later + node = Parser.parse(fixed_phrase, source.filename, fixed_position.line) if node.nil? elsif source.repaired? - node = Parser.parse(fixed_phrase) + node = Parser.parse(fixed_phrase, source.filename, fixed_position.line) else node, parent = source.tree_at(fixed_position.line, fixed_position.column)[0..2] unless source.error_ranges.any?{|r| r.nil? || r.include?(fixed_position)} # Exception for positions that chain literal nodes in unsynchronized sources node = nil unless source.synchronized? || !Parser.infer_literal_node_type(node).nil? - node = Parser.parse(fixed_phrase) if node.nil? + node = Parser.parse(fixed_phrase, source.filename, fixed_position.line) if node.nil? end rescue Parser::SyntaxError return Chain.new([Chain::UNDEFINED_CALL]) diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index d7b6fb4fc..78fb77dd8 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -141,7 +141,7 @@ def references name # @return [Array] def locals_at(location) return [] if location.filename != filename - closure = locate_named_path_pin(location.range.start.line, location.range.start.character) + closure = locate_closure_pin(location.range.start.line, location.range.start.character) locals.select { |pin| pin.visible_at?(closure, location) } end diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index 3d198ac1e..f7e76b711 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -78,7 +78,7 @@ def gates # @param phrase [String] # @return [Array] def translate phrase - chain = Parser.chain(Parser.parse(phrase)) + chain = Parser.chain(Parser.parse(phrase, cursor.filename, cursor.position.line)) chain.define(api_map, closure, locals) end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 4600767b5..2486e01d8 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -745,8 +745,8 @@ def fake_args_for(pin) args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)]) end end - args.push Solargraph::Parser.chain_string('{}') if with_opts - args.push Solargraph::Parser.chain_string('&') if with_block + args.push Solargraph::Parser.chain_string('{}', filename, pin.position.line) if with_opts + args.push Solargraph::Parser.chain_string('&', filename, pin.position.line) if with_block args end diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index bf747fc76..460571cb6 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -3,7 +3,7 @@ # @todo These tests depend on `Clip`, but we're putting the tests here to # avoid overloading clip_spec.rb. describe Solargraph::Parser::FlowSensitiveTyping do - it 'uses is_a? in a simple if() to refine types on a simple class' do + it 'uses is_a? in a simple if() to refine types' do source = Solargraph::Source.load_string(%( class ReproBase; end class Repro < ReproBase; end @@ -24,6 +24,30 @@ def verify_repro(repr) expect(clip.infer.to_s).to eq('ReproBase') end + it 'uses is_a? in a simple if() with a union to refine types' do + source = Solargraph::Source.load_string(%( + class ReproBase; end + class Repro1 < ReproBase; end + class Repro2 < ReproBase; end + # @param repr [Repro1, Repro2] + def verify_repro(repr) + if repr.is_a?(Repro1) + repr + else + repr + end + end + ), 'test.rb') + pending 'FlowSensitiveTyping improvements' + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 10]) + expect(clip.infer.to_s).to eq('Repro1') + + clip = api_map.clip_at('test.rb', [9, 10]) + expect(clip.infer.to_s).to eq('Repro2') + end + it 'uses is_a? in a simple if() to refine types on a module-scoped class' do source = Solargraph::Source.load_string(%( class ReproBase; end @@ -72,7 +96,7 @@ def verify_repro(repr) expect(clip.infer.to_s).to eq('ReproBase') end - it 'uses is_a? in a simple unless statement to refine types on a simple class' do + it 'uses is_a? in a simple unless statement to refine types' do source = Solargraph::Source.load_string(%( class ReproBase; end class Repro < ReproBase; end @@ -173,22 +197,6 @@ class Repro < ReproBase; end expect(clip.infer.to_s).to eq('Repro') end - it 'uses is_a? in a "break unless" statement in a while to refine types' do - source = Solargraph::Source.load_string(%( - class ReproBase; end - class Repro < ReproBase; end - # @type [ReproBase] - value = bar - while !is_done() - break unless value.is_a? Repro - value - end - ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [7, 8]) - expect(clip.infer.to_s).to eq('Repro') - end - it 'uses unless is_a? in a ".each" block to refine types' do source = Solargraph::Source.load_string(%( # @type [Array] @@ -212,6 +220,68 @@ class Repro < ReproBase; end expect(clip.infer.to_s).to eq('Float') end + it 'uses varname in a simple if()' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr + repr + else + repr + end + end + ), 'test.rb') + pending 'FlowSensitiveTyping improvements' + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses varname in a "break unless" statement in a while to refine types' do + source = Solargraph::Source.load_string(%( + class ReproBase; end + class Repro < ReproBase; end + # @type [ReproBase, nil] + value = bar + while !is_done() + break unless value + value + end + ), 'test.rb') + pending 'FlowSensitiveTyping improvements' + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 8]) + expect(clip.infer.to_s).to eq('ReproBase') + end + + it 'uses varname in a "break if" statement in a while to refine types' do + source = Solargraph::Source.load_string(%( + class ReproBase; end + class Repro < ReproBase; end + # @type [ReproBase, nil] + value = bar + while !is_done() + break if value.nil? + value + end + ), 'test.rb') + pending 'FlowSensitiveTyping improvements' + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 8]) + expect(clip.infer.to_s).to eq('ReproBase') + end + it 'understands compatible reassignments' do source = Solargraph::Source.load_string(%( class Foo @@ -253,4 +323,704 @@ def baz; end clip = api_map.clip_at('test.rb', [3, 6]) expect { clip.infer.to_s }.not_to raise_error end + + it 'uses nil? in a simple if() to refine nilness' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + def verify_repro(repr) + repr = 10 if floop + repr + if repr.nil? + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + end + + it 'uses nil? and && in a simple if() to refine nilness - nil? first' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr.nil? && throw_the_dice + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses nil? and && in a simple if() to refine nilness - nil? second' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if throw_the_dice && repr.nil? + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses nil? and || in a simple if() - nil? first' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr.nil? || throw_the_dice + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + end + + it 'uses nil? and || in a simple if() - nil? second' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if throw_the_dice || repr.nil? + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + end + + it 'uses varname and || in a simple if() - varname first' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr || throw_the_dice + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses varname and || in a simple if() - varname second' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if throw_the_dice || repr + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses .nil? and or in an unless' do + source = Solargraph::Source.load_string(%( + # @param repr [String, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr) + repr unless repr.nil? || repr.downcase + repr + end + ), 'test.rb') + pending 'FlowSensitiveTyping improvements' + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 33]) + expect(clip.infer.rooted_tags).to eq('::String') + + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::String') + + clip = api_map.clip_at('test.rb', [5, 8]) + expect(clip.infer.rooted_tags).to eq('::String, nil') + end + + it 'uses varname and && in a simple if() - varname first' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if repr && throw_the_dice + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses varname and && in a simple if() - varname second' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + # @param throw_the_dice [Boolean] + def verify_repro(repr, throw_the_dice) + repr + if throw_the_dice && repr + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + end + + it 'uses variable in a simple if() to refine types' do + source = Solargraph::Source.load_string(%( + # @param repr [Integer, nil] + def verify_repro(repr) + repr = 10 if floop + repr + if repr + repr + else + repr + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [4, 8]) + expect(clip.infer.rooted_tags).to eq('::Integer, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Integer') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses variable in a simple if() to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + def verify_repro(repr = nil) + repr = 10 if floop + repr + if repr + repr + else + repr + end + end + ), 'test.rb') + pending 'FlowSensitiveTyping improvements' + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [3, 8]) + expect(clip.infer.rooted_tags).to eq('10, nil') + + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('10') + + clip = api_map.clip_at('test.rb', [7, 10]) + expect(clip.infer.rooted_tags).to eq('nil') + end + + it 'uses .nil? in a return if() in an if to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + if rand + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + pending 'FlowSensitiveTyping improvements' + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + end + + # https://cse.buffalo.edu/~regan/cse305/RubyBNF.pdf + # https://ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/syntax.html + it 'uses .nil? in a return if() in a method to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + return if baz.nil? + baz + end + end + ), 'test.rb') + + pending 'FlowSensitiveTyping improvements' + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + end + + it 'uses .nil? in a return if() in a block to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @param arr [Array] + # @return [void] + def bar(arr, baz: nil) + baz + arr.each do |item| + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [9, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [11, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in an unless to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + unless rand + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + pending('better scoping of return if in unless') + + clip = api_map.clip_at('test.rb', [10, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in a while to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + while rand do + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + pending 'FlowSensitiveTyping improvements' + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [9, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses foo in a a while to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @param other [::Boolean, nil] + # @return [void] + def bar(baz: nil, other: nil) + baz + while baz do + baz + baz = other + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [11, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in an until to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + until rand do + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + pending 'FlowSensitiveTyping improvements' + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [9, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in a switch/case/else to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + case rand + when 0..0.5 + return if baz.nil? + baz + else + baz + end + baz + end + end + ), 'test.rb') + + pending 'FlowSensitiveTyping improvements' + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + pending('better scoping of return if in case/when') + + clip = api_map.clip_at('test.rb', [10, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [12, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in a ternary operator to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + rand > 0.5 ? (return if baz.nil?; baz) : baz + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [6, 44]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + pending('better scoping of return if in ternary operator') + + clip = api_map.clip_at('test.rb', [6, 51]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [7, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'uses .nil? in a return if() in a begin/end to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + begin + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [10, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + end + + it 'uses .nil? in a return if() in a ||= to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + baz ||= begin + return if baz.nil? + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [10, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + end + + it 'uses .nil? in a return if() in a try / rescue / ensure to refine types using nil checks' do + source = Solargraph::Source.load_string(%( + class Foo + # @param baz [::Boolean, nil] + # @return [void] + def bar(baz: nil) + baz + begin + return if baz.nil? + baz + rescue StandardError + baz + ensure + baz + end + baz + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [8, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + clip = api_map.clip_at('test.rb', [10, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean') + + pending('better scoping of return if in begin/rescue/ensure') + + clip = api_map.clip_at('test.rb', [12, 12]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + + clip = api_map.clip_at('test.rb', [14, 10]) + expect(clip.infer.rooted_tags).to eq('::Boolean, nil') + end + + it 'provides a useful pin after a return if .nil?' do + source = Solargraph::Source.load_string(%( + class A + # @param b [Hash{String => String}] + # @return [void] + def a b + c = b["123"] + c + return c if c.nil? + c + end + end + ), 'test.rb') + + api_map = Solargraph::ApiMap.new.map(source) + + clip = api_map.clip_at('test.rb', [6, 10]) + expect(clip.infer.to_s).to eq('String') + + pending 'FlowSensitiveTyping improvements' + + clip = api_map.clip_at('test.rb', [7, 17]) + expect(clip.infer.to_s).to eq('nil') + + clip = api_map.clip_at('test.rb', [8, 10]) + expect(clip.infer.to_s).to eq('String') + end + + it 'uses ! to detect nilness' do + source = Solargraph::Source.load_string(%( + class A + # @param a [Integer, nil] + # @return [Integer] + def foo a + return a unless !a + 123 + end + end + ), 'test.rb') + pending 'FlowSensitiveTyping improvements' + + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [5, 17]) + expect(clip.infer.to_s).to eq('Integer') + end end diff --git a/spec/parser/node_chainer_spec.rb b/spec/parser/node_chainer_spec.rb index e92431aae..e4722d77a 100644 --- a/spec/parser/node_chainer_spec.rb +++ b/spec/parser/node_chainer_spec.rb @@ -1,51 +1,55 @@ describe 'NodeChainer' do + def chain_string str + Solargraph::Parser.chain_string(str, 'file.rb', 0) + end + it "recognizes self keywords" do - chain = Solargraph::Parser.chain_string('self.foo') + chain = chain_string('self.foo') expect(chain.links.first.word).to eq('self') expect(chain.links.first).to be_a(Solargraph::Source::Chain::Head) end it "recognizes super keywords" do - chain = Solargraph::Parser.chain_string('super.foo') + chain = chain_string('super.foo') expect(chain.links.first.word).to eq('super') expect(chain.links.first).to be_a(Solargraph::Source::Chain::ZSuper) end it "recognizes constants" do - chain = Solargraph::Parser.chain_string('Foo::Bar') + chain = chain_string('Foo::Bar') expect(chain.links.length).to eq(1) expect(chain.links.first).to be_a(Solargraph::Source::Chain::Constant) expect(chain.links.map(&:word)).to eq(['Foo::Bar']) end it "splits method calls with arguments and blocks" do - chain = Solargraph::Parser.chain_string('var.meth1(1, 2).meth2 do; end') + chain = chain_string('var.meth1(1, 2).meth2 do; end') expect(chain.links.map(&:word)).to eq(['var', 'meth1', 'meth2']) end it "recognizes literals" do - chain = Solargraph::Parser.chain_string('"string"') + chain = chain_string('"string"') expect(chain).to be_literal - chain = Solargraph::Parser.chain_string('100') + chain = chain_string('100') expect(chain).to be_literal - chain = Solargraph::Parser.chain_string('[1, 2, 3]') + chain = chain_string('[1, 2, 3]') expect(chain).to be_literal - chain = Solargraph::Parser.chain_string('{ foo: "bar" }') + chain = chain_string('{ foo: "bar" }') expect(chain).to be_literal end it "recognizes instance variables" do - chain = Solargraph::Parser.chain_string('@foo') + chain = chain_string('@foo') expect(chain.links.first).to be_a(Solargraph::Source::Chain::InstanceVariable) end it "recognizes class variables" do - chain = Solargraph::Parser.chain_string('@@foo') + chain = chain_string('@@foo') expect(chain.links.first).to be_a(Solargraph::Source::Chain::ClassVariable) end it "recognizes global variables" do - chain = Solargraph::Parser.chain_string('$foo') + chain = chain_string('$foo') expect(chain.links.first).to be_a(Solargraph::Source::Chain::GlobalVariable) end diff --git a/spec/parser/node_methods_spec.rb b/spec/parser/node_methods_spec.rb index f9504b584..561195a40 100644 --- a/spec/parser/node_methods_spec.rb +++ b/spec/parser/node_methods_spec.rb @@ -1,65 +1,69 @@ # These tests are deliberately generic because they apply to both the Legacy # and Rubyvm node methods. describe Solargraph::Parser::NodeMethods do + def parse source + Solargraph::Parser.parse(source, 'test.rb', 0) + end + it "unpacks constant nodes into strings" do - ast = Solargraph::Parser.parse("Foo::Bar") + ast = parse("Foo::Bar") expect(Solargraph::Parser::NodeMethods.unpack_name(ast)).to eq "Foo::Bar" end it "infers literal strings" do - ast = Solargraph::Parser.parse("x = 'string'") + ast = parse("x = 'string'") expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::String' end it "infers literal hashes" do - ast = Solargraph::Parser.parse("x = {}") + ast = parse("x = {}") expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Hash' end it "infers literal arrays" do - ast = Solargraph::Parser.parse("x = []") + ast = parse("x = []") expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Array' end it "infers literal integers" do - ast = Solargraph::Parser.parse("x = 100") + ast = parse("x = 100") expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Integer' end it "infers literal floats" do - ast = Solargraph::Parser.parse("x = 10.1") + ast = parse("x = 10.1") expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Float' end it "infers literal symbols" do - ast = Solargraph::Parser.parse(":symbol") + ast = parse(":symbol") expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' end it "infers double quoted symbols" do - ast = Solargraph::Parser.parse(':"symbol"') + ast = parse(':"symbol"') expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' end it "infers interpolated double quoted symbols" do - ast = Solargraph::Parser.parse(':"#{Object}"') + ast = parse(':"#{Object}"') expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' end it "infers single quoted symbols" do - ast = Solargraph::Parser.parse(":'symbol'") + ast = parse(":'symbol'") expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' end it 'infers literal booleans' do - true_ast = Solargraph::Parser.parse("true") + true_ast = parse("true") expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(true_ast)).to eq '::Boolean' - false_ast = Solargraph::Parser.parse("false") + false_ast = parse("false") expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(false_ast)).to eq '::Boolean' end it "handles return nodes with implicit nil values" do - node = Solargraph::Parser.parse(%( + node = parse(%( return if true )) rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) @@ -69,7 +73,7 @@ end it "handles return nodes with implicit nil values" do - node = Solargraph::Parser.parse(%( + node = parse(%( return bla if true )) rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) @@ -78,7 +82,7 @@ end it 'handles return nodes from case statements' do - node = Solargraph::Parser.parse(%( + node = parse(%( case x when 100 true @@ -90,7 +94,7 @@ end it 'handles return nodes from case statements with else' do - node = Solargraph::Parser.parse(%( + node = parse(%( case x when 100, 125 true @@ -114,7 +118,7 @@ end it 'handles return nodes from case statements with boolean conditions' do - node = Solargraph::Parser.parse(%( + node = parse(%( case true when x true @@ -128,7 +132,7 @@ it "handles return nodes in reduceable (begin) nodes" do # @todo Temporarily disabled. Result is 3 nodes instead of 2. - # node = Solargraph::Parser.parse(%( + # node = parse(%( # begin # return if true # end @@ -138,7 +142,7 @@ end it "handles return nodes after other nodes" do - node = Solargraph::Parser.parse(%( + node = parse(%( x = 1 return x )) @@ -147,7 +151,7 @@ end it "handles return nodes with unreachable code" do - node = Solargraph::Parser.parse(%( + node = parse(%( x = 1 return x y @@ -157,7 +161,7 @@ end it "handles conditional returns with following code" do - node = Solargraph::Parser.parse(%( + node = parse(%( x = 1 return x if foo y @@ -167,7 +171,7 @@ end it "handles return nodes with reduceable code" do - node = Solargraph::Parser.parse(%( + node = parse(%( return begin x if foo y @@ -178,14 +182,14 @@ end it "handles top 'and' nodes" do - node = Solargraph::Parser.parse('1 && "2"') + node = parse('1 && "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) expect(rets.length).to eq(1) expect(rets[0].type.to_s.downcase).to eq('and') end it "handles top 'or' nodes" do - node = Solargraph::Parser.parse('1 || "2"') + node = parse('1 || "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) expect(rets.length).to eq(2) expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(rets[0])).to eq('::Integer') @@ -193,14 +197,14 @@ end it "handles nested 'and' nodes" do - node = Solargraph::Parser.parse('return 1 && "2"') + node = parse('return 1 && "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) expect(rets.length).to eq(1) expect(rets[0].type.to_s.downcase).to eq('and') end it "handles nested 'or' nodes" do - node = Solargraph::Parser.parse('return 1 || "2"') + node = parse('return 1 || "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) expect(rets.length).to eq(2) expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(rets[0])).to eq('::Integer') @@ -208,7 +212,7 @@ end it 'finds return nodes in blocks' do - node = Solargraph::Parser.parse(%( + node = parse(%( array.each do |item| return item if foo end @@ -218,7 +222,7 @@ end it 'finds correct return node line in begin expressions' do - node = Solargraph::Parser.parse(%( + node = parse(%( begin 123 '123' @@ -229,7 +233,7 @@ end it 'returns nested return blocks' do - node = Solargraph::Parser.parse(%( + node = parse(%( if foo array.each do |item| return item if foo @@ -242,7 +246,7 @@ end it "handles return nodes with implicit nil values" do - node = Solargraph::Parser.parse(%( + node = parse(%( return if true )) rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) @@ -252,7 +256,7 @@ end it "handles return nodes with implicit nil values" do - node = Solargraph::Parser.parse(%( + node = parse(%( return bla if true )) rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) @@ -261,7 +265,7 @@ it "handles return nodes in reduceable (begin) nodes" do # @todo Temporarily disabled. Result is 3 nodes instead of 2 in legacy. - # node = Solargraph::Parser.parse(%( + # node = parse(%( # begin # return if true # end @@ -271,7 +275,7 @@ end it "handles return nodes after other nodes" do - node = Solargraph::Parser.parse(%( + node = parse(%( x = 1 return x )) @@ -280,7 +284,7 @@ end it "handles return nodes with unreachable code" do - node = Solargraph::Parser.parse(%( + node = parse(%( x = 1 return x y @@ -290,7 +294,7 @@ end xit "short-circuits return node finding after a raise statement in a begin expressiona" do - node = Solargraph::Parser.parse(%( + node = parse(%( raise "Error" y )) @@ -299,7 +303,7 @@ end it "does not short circuit return node finding after a raise statement in a conditional" do - node = Solargraph::Parser.parse(%( + node = parse(%( x = 1 raise "Error" if foo y @@ -309,7 +313,7 @@ end it "does not short circuit return node finding after a return statement in a conditional" do - node = Solargraph::Parser.parse(%( + node = parse(%( x = 1 return "Error" if foo y @@ -319,7 +323,7 @@ end it "handles return nodes with reduceable code" do - node = Solargraph::Parser.parse(%( + node = parse(%( return begin x if foo y @@ -330,31 +334,31 @@ end it "handles top 'and' nodes" do - node = Solargraph::Parser.parse('1 && "2"') + node = parse('1 && "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:and]) end it "handles top 'or' nodes" do - node = Solargraph::Parser.parse('1 || "2"') + node = parse('1 || "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:int, :str]) end it "handles nested 'and' nodes from return" do - node = Solargraph::Parser.parse('return 1 && "2"') + node = parse('return 1 && "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:and]) end it "handles nested 'or' nodes from return" do - node = Solargraph::Parser.parse('return 1 || "2"') + node = parse('return 1 || "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:int, :str]) end it 'finds return nodes in blocks' do - node = Solargraph::Parser.parse(%( + node = parse(%( array.each do |item| return item if foo end @@ -365,7 +369,7 @@ end it 'returns nested return blocks' do - node = Solargraph::Parser.parse(%( + node = parse(%( if foo array.each do |item| return item if foo @@ -379,7 +383,7 @@ end it 'handles return nodes from case statements' do - node = Solargraph::Parser.parse(%( + node = parse(%( case 1 when 1 then "" else @@ -391,7 +395,7 @@ end it 'handles return nodes from case statements without else' do - node = Solargraph::Parser.parse(%( + node = parse(%( case 1 when 1 "" @@ -402,7 +406,7 @@ end it 'handles return nodes from case statements with super' do - node = Solargraph::Parser.parse(%( + node = parse(%( case other when Docstring Docstring.new([all, other.all].join("\n"), object) @@ -416,13 +420,13 @@ describe 'convert_hash' do it 'converts literal hash arguments' do - node = Solargraph::Parser.parse('{foo: :bar}') + node = parse('{foo: :bar}') hash = Solargraph::Parser::NodeMethods.convert_hash(node) expect(hash.keys).to eq([:foo]) end it 'ignores call arguments' do - node = Solargraph::Parser.parse('some_call') + node = parse('some_call') hash = Solargraph::Parser::NodeMethods.convert_hash(node) expect(hash).to eq({}) end diff --git a/spec/parser/node_processor_spec.rb b/spec/parser/node_processor_spec.rb index 5b8d7cd40..2033e21ca 100644 --- a/spec/parser/node_processor_spec.rb +++ b/spec/parser/node_processor_spec.rb @@ -1,6 +1,10 @@ describe Solargraph::Parser::NodeProcessor do + def parse source + Solargraph::Parser.parse(source, 'file.rb', 0) + end + it 'ignores bare private_constant calls' do - node = Solargraph::Parser.parse(%( + node = parse(%( class Foo private_constant end @@ -11,7 +15,7 @@ class Foo end it 'orders optional args correctly' do - node = Solargraph::Parser.parse(%( + node = parse(%( def foo(bar = nil, baz = nil); end )) pins, = Solargraph::Parser::NodeProcessor.process(node) @@ -21,7 +25,7 @@ def foo(bar = nil, baz = nil); end end it 'understands +=' do - node = Solargraph::Parser.parse(%( + node = parse(%( detail = '' detail += "foo" detail.strip! @@ -53,7 +57,7 @@ def process Solargraph::Parser::NodeProcessor.register(:def, dummy_processor1) Solargraph::Parser::NodeProcessor.register(:def, dummy_processor2) - node = Solargraph::Parser.parse(%( + node = parse(%( def some_method; end )) pins, = Solargraph::Parser::NodeProcessor.process(node) diff --git a/spec/parser_spec.rb b/spec/parser_spec.rb index 267f412f4..3c1e3cca0 100644 --- a/spec/parser_spec.rb +++ b/spec/parser_spec.rb @@ -1,11 +1,15 @@ describe Solargraph::Parser do + def parse source + Solargraph::Parser.parse(source, 'file.rb', 0) + end + it "parses nodes" do - node = Solargraph::Parser.parse('class Foo; end', 'test.rb') + node = parse('class Foo; end') expect(Solargraph::Parser.is_ast_node?(node)).to be(true) end it 'raises repairable SyntaxError for unknown encoding errors' do code = "# encoding: utf-\nx = 'y'" - expect { Solargraph::Parser.parse(code) }.to raise_error(Solargraph::Parser::SyntaxError) + expect { parse(code) }.to raise_error(Solargraph::Parser::SyntaxError) end end diff --git a/spec/pin/local_variable_spec.rb b/spec/pin/local_variable_spec.rb index 88075efb9..68ed71ae7 100644 --- a/spec/pin/local_variable_spec.rb +++ b/spec/pin/local_variable_spec.rb @@ -50,4 +50,163 @@ class Bar expect { pin1.combine_with(pin2) }.to raise_error(RuntimeError, /Inconsistent :closure name/) end end + + + describe '#visible_at?' do + it 'detects scoped methods in rebound blocks' do + source = Solargraph::Source.load_string(%( + object = MyClass.new + + ), 'test.rb') + api_map = Solargraph::ApiMap.new + api_map.map source + clip = api_map.clip_at('test.rb', [2, 0]) + object_pin = api_map.source_map('test.rb').locals.find { |p| p.name == 'object' } + expect(object_pin).not_to be_nil + location = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(2, 0, 2, 0)) + expect(object_pin.visible_at?(Solargraph::Pin::ROOT_PIN, location)).to be true + end + + it 'does not allow access to top-level locals from top-level methods' do + map = Solargraph::SourceMap.load_string(%( + x = 'string' + def foo + x + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new + api_map.map map.source + x_pin = api_map.source_map('test.rb').locals.find { |p| p.name == 'x' } + expect(x_pin).not_to be_nil + foo_pin = api_map.get_path_pins('#foo').first + expect(foo_pin).not_to be_nil + location = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(3, 9, 3, 9)) + expect(x_pin.visible_at?(foo_pin, location)).to be false + end + + it 'scopes local variables correctly in class_eval blocks' do + map = Solargraph::SourceMap.load_string(%( + class Foo; end + x = 'y' + Foo.class_eval do + foo = :bar + etc + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new + api_map.map map.source + block_pin = api_map.get_block_pins.find do |b| + b.location.range.start.line == 3 + end + expect(block_pin).not_to be_nil + x_pin = api_map.source_map('test.rb').locals.find { |p| p.name == 'x' } + expect(x_pin).not_to be_nil + location = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(5, 10, 5, 10)) + expect(x_pin.visible_at?(block_pin, location)).to be true + end + + it "understands local lookup in root scope" do + api_map = Solargraph::ApiMap.new + source = Solargraph::Source.load_string(%( + # @type [Array] + arr = [] + + + ), "test.rb") + api_map.map source + arr_pin = api_map.source_map('test.rb').locals.find { |p| p.name == 'arr' } + expect(arr_pin).not_to be_nil + location = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(3, 0, 3, 0)) + expect(arr_pin.visible_at?(Solargraph::Pin::ROOT_PIN, location)).to be true + end + + it 'selects local variables using gated scopes' do + source = Solargraph::Source.load_string(%( + lvar1 = 'lvar1' + module MyModule + lvar2 = 'lvar2' + + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new + api_map.map source + lvar1_pin = api_map.source_map('test.rb').locals.find { |p| p.name == 'lvar1' } + expect(lvar1_pin).not_to be_nil + my_module_pin = api_map.get_namespace_pins('MyModule', 'Class<>').first + expect(my_module_pin).not_to be_nil + location = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(4, 0, 4, 0)) + expect(lvar1_pin.visible_at?(my_module_pin, location)).to be false + + lvar2_pin = api_map.source_map('test.rb').locals.find { |p| p.name == 'lvar2' } + expect(lvar2_pin).not_to be_nil + expect(lvar2_pin.visible_at?(my_module_pin, location)).to be true + end + + it 'is visible within same method' do + source = Solargraph::Source.load_string(%( + class Foo + def bar + x = 1 + puts x + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.source_map('test.rb').locals.first + bar_method = api_map.get_path_pins('Foo#bar').first + expect(bar_method).not_to be_nil + range = Solargraph::Range.from_to(4, 16, 4, 17) + location = Solargraph::Location.new('test.rb', range) + expect(pin.visible_at?(bar_method, location)).to be true + end + + it 'is visible within each block scope inside function' do + source = Solargraph::Source.load_string(%( + class Foo + def bar + x = 1 + [2,3,4].each do |i| + puts x + i + end + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new + api_map.map source + x = api_map.source_map('test.rb').locals.find { |p| p.name == 'x' } + bar_method = api_map.get_path_pins('Foo#bar').first + each_block_pin = api_map.get_block_pins.find do |b| + b.location.range.start.line == 4 + end + expect(each_block_pin).not_to be_nil + range = Solargraph::Range.from_to(5, 24, 5, 25) + location = Solargraph::Location.new('test.rb', range) + expect(x.visible_at?(each_block_pin, location)).to be true + end + + it 'sees block parameter inside block' do + source = Solargraph::Source.load_string(%( + class Foo + def bar + [1,2,3].each do |i| + puts i + end + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new + api_map.map source + i = api_map.source_map('test.rb').locals.find { |p| p.name == 'i' } + bar_method = api_map.get_path_pins('Foo#bar').first + expect(bar_method).not_to be_nil + each_block_pin = api_map.get_block_pins.find do |b| + b.location.range.start.line == 3 + end + expect(each_block_pin).not_to be_nil + range = Solargraph::Range.from_to(4, 24, 4, 25) + location = Solargraph::Location.new('test.rb', range) + expect(i.visible_at?(each_block_pin, location)).to be true + end + end end diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index 8b67a3c66..3dc686605 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -224,7 +224,8 @@ def self.bar type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) expect(type.tag).to eq('Set') chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(4, 17)) - type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) + block_pin = api_map.source_map('test.rb').pins.find { |p| p.is_a?(Solargraph::Pin::Block) } + type = chain.infer(api_map, block_pin, api_map.source_map('test.rb').locals) expect(type.tag).to eq('Class') chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(7, 9)) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) @@ -406,8 +407,9 @@ def foo(params) api_map = Solargraph::ApiMap.new api_map.map source + foo_pin = api_map.source_map('test.rb').pins.find { |p| p.name == 'foo' } chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(4, 8)) - type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) + type = chain.infer(api_map, foo_pin, api_map.source_map('test.rb').locals) expect(type.rooted_tags).to eq('::Array, ::Hash{::String => undefined}, ::String, ::Integer') end @@ -443,8 +445,9 @@ def foo api_map = Solargraph::ApiMap.new api_map.map source + foo_pin = api_map.source_map('test.rb').pins.find { |p| p.name == 'foo' } chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(5, 8)) - type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) + type = chain.infer(api_map, foo_pin, api_map.source_map('test.rb').locals) expect(type.rooted_tags).to eq('::Array<::String>') end @@ -464,8 +467,12 @@ def c api_map = Solargraph::ApiMap.new api_map.map source + closure_pin = api_map.source_map('test.rb').pins.find do |p| + p.is_a?(Solargraph::Pin::Block) && p.location.range.start.line == 4 + end + chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(5, 14)) - type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) + type = chain.infer(api_map, closure_pin, api_map.source_map('test.rb').locals) expect(type.tags).to eq('A::B') end @@ -485,8 +492,12 @@ def c api_map = Solargraph::ApiMap.new api_map.map source + closure_pin = api_map.source_map('test.rb').pins.find do |p| + p.is_a?(Solargraph::Pin::Block) && p.location.range.start.line == 4 + end + chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(5, 14)) - type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) + type = chain.infer(api_map, closure_pin, api_map.source_map('test.rb').locals) expect(type.rooted_tags).to eq('::A::B') end @@ -512,11 +523,17 @@ def d api_map.map source chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(6, 14)) - type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) + closure_pin = api_map.source_map('test.rb').pins.find do |p| + p.is_a?(Solargraph::Pin::Block) && p.location.range.start.line == 5 + end + type = chain.infer(api_map, closure_pin, api_map.source_map('test.rb').locals) expect(type.rooted_tags).to eq('::A::B').or eq('::A::B, ::A::C').or eq('::A::C, ::A::B') + closure_pin = api_map.source_map('test.rb').pins.find do |p| + p.is_a?(Solargraph::Pin::Block) && p.location.range.start.line == 10 + end chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(11, 14)) - type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) + type = chain.infer(api_map, closure_pin, api_map.source_map('test.rb').locals) # valid options here: # * emit type checker warning when adding [B.new] and type whole thing as '::A::B' # * type whole thing as '::A::B, A::C' diff --git a/spec/source/chain_spec.rb b/spec/source/chain_spec.rb index abc8c2b05..369e302e9 100644 --- a/spec/source/chain_spec.rb +++ b/spec/source/chain_spec.rb @@ -428,8 +428,9 @@ def obj(foo); end str = obj.stringify ), 'test.rb') api_map = Solargraph::ApiMap.new.map(source) + obj_fn_pin = api_map.get_path_pins('Example.obj').first chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(12, 6)) - type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) + type = chain.infer(api_map, obj_fn_pin, api_map.source_map('test.rb').locals) expect(type.to_s).to eq('String') end end diff --git a/spec/source_map/clip_spec.rb b/spec/source_map/clip_spec.rb index ee7e4bcfa..c0a60fff0 100644 --- a/spec/source_map/clip_spec.rb +++ b/spec/source_map/clip_spec.rb @@ -1230,7 +1230,7 @@ def one updated = source.synchronize(updater) api_map.map updated clip = api_map.clip_at('test.rb', [2, 8]) - expect(clip.complete.pins.first.path).to start_with('Array#') + expect(clip.complete.pins.first&.path).to start_with('Array#') end it 'selects local variables using gated scopes' do @@ -2007,7 +2007,7 @@ def foo ), 'test.rb') api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [8, 6]) + clip = api_map.clip_at('test.rb', [9, 6]) type = clip.infer expect(type.tags).to eq('Integer') @@ -2712,7 +2712,7 @@ def bar; end ), 'test.rb') api_map = Solargraph::ApiMap.new.map(source) clip = api_map.clip_at('test.rb', [7, 6]) - expect(clip.infer.to_s).to eq(':foo, 123, nil') + expect(clip.infer.to_s).to eq('123, :foo, nil') end it 'expands type with conditional reassignments' do diff --git a/spec/source_map_spec.rb b/spec/source_map_spec.rb index 60d4b523e..5d587e27c 100644 --- a/spec/source_map_spec.rb +++ b/spec/source_map_spec.rb @@ -76,7 +76,7 @@ class Foo expect(pin).to be_a(Solargraph::Pin::Block) end - it 'scopes local variables correctly from root def blocks' do + it 'scopes local variables correctly from root def methods' do map = Solargraph::SourceMap.load_string(%( x = 'string' def foo @@ -88,6 +88,20 @@ def foo expect(locals).to be_empty end + it 'scopes local variables correctly from class methods' do + map = Solargraph::SourceMap.load_string(%( + class Foo + x = 'string' + def foo + x + end + end + ), 'test.rb') + loc = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(4, 11, 3, 11)) + locals = map.locals_at(loc) + expect(locals).to be_empty + end + it 'handles op_asgn case with assertions on' do # set SOLARGRAPH_ASSERTS=onto test this old_asserts = ENV.fetch('SOLARGRAPH_ASSERTS', nil) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 970435dc3..1f0812fe4 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -4,9 +4,128 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end + it 'does not misunderstand types during flow-sensitive typing' do + checker = type_checker(%( + class A + # @param b [Hash{String => String}] + # @return [void] + def a b + c = b["123"] + return if c.nil? + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'respects pin visibility in if/nil? pattern' do + checker = type_checker(%( + class Foo + # Get the namespace's type (Class or Module). + # + # @param bar [Symbol, nil] + # @return [Symbol, Integer] + def foo bar + return 123 if bar.nil? + bar + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'respects || overriding nilable types' do + checker = type_checker(%( + # @return [String] + def global_config_path + ENV['SOLARGRAPH_GLOBAL_CONFIG'] || + File.join(Dir.home, '.config', 'solargraph', 'config.yml') + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'is able to probe type over an assignment' do + checker = type_checker(%( + # @return [String] + def global_config_path + out = 'foo' + out + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'respects pin visibility in if/foo pattern' do + checker = type_checker(%( + class Foo + # Get the namespace's type (Class or Module). + # + # @param bar [Symbol, nil] + # @return [Symbol, Integer] + def foo bar + baz = bar + return baz if baz + 123 + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'handles a flow sensitive typing if correctly' do + checker = type_checker(%( + # @param a [String, nil] + # @return [void] + def foo a = nil + b = a + if b + b.upcase + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'handles another flow sensitive typing if correctly' do + checker = type_checker(%( + class A + # @param e [String] + # @param f [String] + # @return [void] + def d(e, f:); end + + # @return [void] + def a + c = rand ? nil : "foo" + if c + d(c, f: c) + end + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'respects pin visibility' do + checker = type_checker(%( + class Foo + # Get the namespace's type (Class or Module). + # + # @param baz [Integer, nil] + # @return [Integer, nil] + def foo baz = 123 + return nil if baz.nil? + baz + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'does not complain on array dereference' do checker = type_checker(%( - # @param idx [Integer, nil] an index + # @param idx [Integer] an index # @param arr [Array] an array of integers # # @return [void] @@ -17,6 +136,23 @@ def foo(idx, arr) expect(checker.problems.map(&:message)).to be_empty end + it 'understands local evaluation with ||= removes nil from lhs type' do + checker = type_checker(%( + class Foo + def initialize + @bar = nil + end + + # @return [Integer] + def bar + @bar ||= 123 + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end + it 'complains on bad @type assignment' do checker = type_checker(%( # @type [Integer] @@ -97,6 +233,31 @@ def bar expect(checker.problems.map(&:message)).to include('Call to #foo is missing keyword argument b') end + it 'understands complex use of self' do + pending 'https://github.com/castwide/solargraph/pull/1050' + + 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') @@ -256,6 +417,149 @@ 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 'ignores generic resolution failure with no generic tag' 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 from current Solargraph limitation' 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 with only one arg' 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 @@ -306,5 +610,74 @@ def run_command )) expect(checker.problems.map(&:message)).to be_empty end + + 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 + + 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 + + it 'resolves class name correctly in generic resolution' do + expect(checker.problems.map(&:message)).to be_empty + end + end + + it 'handles "while foo" flow sensitive typing correctly' do + checker = type_checker(%( + # @param a [String, nil] + # @return [void] + def foo a = nil + b = a + while b + b.upcase + b = nil if rand > 0.5 + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + + it 'does flow sensitive typing even inside a block' do + checker = type_checker(%( + class Quux + # @param foo [String, nil] + # + # @return [void] + def baz(foo) + bar = foo + [].each do + bar.upcase unless bar.nil? + end + end + end)) + + expect(checker.problems.map(&:location).map(&:range).map(&:start)).to be_empty + end end end From 8a8469ec7d9df94159a053bc861b3d21fb1b38cf Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 22 Oct 2025 08:05:27 -0400 Subject: [PATCH 525/930] Fix issues from typechecking in CI --- lib/solargraph/location.rb | 1 + lib/solargraph/pin/base.rb | 9 ++++++-- lib/solargraph/pin/base_variable.rb | 14 +++++++++++- lib/solargraph/pin/local_variable.rb | 33 ++++++++++++++++++++++++++++ lib/solargraph/pin/parameter.rb | 1 + lib/solargraph/source/chain/call.rb | 1 - lib/solargraph/type_checker.rb | 4 ++-- spec/pin/local_variable_spec.rb | 22 ------------------- 8 files changed, 57 insertions(+), 28 deletions(-) diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index 592da03bf..c59df233d 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -6,6 +6,7 @@ module Solargraph # class Location include Equality + include Comparable # @return [String] attr_reader :filename diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index d56f0fffd..6ff60e2cd 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -91,7 +91,7 @@ def combine_with(other, attrs={}) location: location, type_location: type_location, name: combined_name, - closure: choose_pin_attr_with_same_name(other, :closure), + closure: combine_closure(other), comments: choose_longer(other, :comments), source: :combined, docstring: choose(other, :docstring), @@ -146,6 +146,12 @@ def combine_directives(other) [directives + other.directives].uniq end + # @param other [self] + # @return [Pin::Closure, nil] + def combine_closure(other) + choose_pin_attr_with_same_name(other, :closure) + end + # @param other [self] # @return [String] def combine_name(other) @@ -307,7 +313,6 @@ def assert_same_count(other, attr) # @sg-ignore # @return [undefined] def assert_same(other, attr) - return false if other.nil? val1 = send(attr) val2 = other.send(attr) return val1 if val1 == val2 diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index af1d44c9a..433351c29 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -35,13 +35,25 @@ def combine_with(other, attrs={}) new_assignments = combine_assignments(other) new_attrs = attrs.merge({ assignments: new_assignments, - mass_assignment: assert_same(other, :mass_assignment), + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 + mass_assignment: combine_mass_assignment(other), return_type: combine_return_type(other), }) # @sg-ignore https://github.com/castwide/solargraph/pull/1050 super(other, new_attrs) end + # @param other [self] + # + # @return [Array(Parser::AST::Node, Integer), nil] + # + # @sg-ignore + # Solargraph::Pin::BaseVariable#combine_mass_assignment return + # type could not be inferred + def combine_mass_assignment(other) + assert_same(other, :mass_assignment) + end + # @return [Parser::AST::Node, nil] def assignment @assignment ||= assignments.last diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 427122329..72b85b6d5 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -46,6 +46,30 @@ def starts_at?(other_loc) presence.start == other_loc.range.start end + # @param other [self] + # @return [Pin::Closure, nil] + def combine_closure(other) + return closure if self.closure == other.closure + + # choose first defined, as that establishes the scope of the variable + if closure.nil? || other.closure.nil? + Solargraph.assert_or_log(:varible_closure_missing) do + "One of the local variables being combined is missing a closure: " \ + "#{self.inspect} vs #{other.inspect}" + end + return closure || other.closure + end + + if closure.location.nil? || other.closure.location.nil? + return closure.location.nil? ? other.closure : closure + end + + # if filenames are different, this will just pick one + return closure if closure.location <= other.closure.location + + other.closure + end + # @param other_closure [Pin::Closure] # @param other_loc [Location] def visible_at?(other_closure, other_loc) @@ -72,6 +96,15 @@ def combine_return_type(other) combine_types(other, :return_type) end + # @param other [self] + # + # @return [Array(AST::Node, Integer), nil] + def combine_mass_assignment(other) + # @todo pick first non-nil arbitrarily - we don't yet support + # mass assignment merging + mass_assignment || other.mass_assignment + end + def probe api_map if presence_certain? && return_type&.defined? # flow sensitive typing has already probed this type - use diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index af02a2f1c..4018d1201 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -42,6 +42,7 @@ def combine_with(other, attrs={}) asgn_code: asgn_code } end + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 super(other, new_attrs.merge(attrs)) end diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 08193afc3..bd8382851 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -325,7 +325,6 @@ def block_call_type(api_map, name_pin, locals) # We use the block pin as the closure, as the parameters # here will only be defined inside the block itself and we need to be able to see them - # @sg-ignore Need to add nil check here block.infer(api_map, block_pin, locals) end end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 2486e01d8..7e4e910ed 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -745,8 +745,8 @@ def fake_args_for(pin) args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)]) end end - args.push Solargraph::Parser.chain_string('{}', filename, pin.position.line) if with_opts - args.push Solargraph::Parser.chain_string('&', filename, pin.position.line) if with_block + args.push Solargraph::Parser.chain_string('{}', filename, pin.location&.line || 0) if with_opts + args.push Solargraph::Parser.chain_string('&', filename, pin.location&.line || 0) if with_block args end diff --git a/spec/pin/local_variable_spec.rb b/spec/pin/local_variable_spec.rb index 68ed71ae7..97e11db93 100644 --- a/spec/pin/local_variable_spec.rb +++ b/spec/pin/local_variable_spec.rb @@ -30,28 +30,6 @@ class Foo # should indicate which one should override in the range situation end - it "asserts on attempt to merge namespace changes" do - map1 = Solargraph::SourceMap.load_string(%( - class Foo - foo = 'foo' - end - )) - pin1 = map1.locals.first - map2 = Solargraph::SourceMap.load_string(%( - class Bar - foo = 'foo' - end - )) - pin2 = map2.locals.first - # set env variable 'FOO' to 'true' in block - - with_env_var('SOLARGRAPH_ASSERTS', 'on') do - expect(Solargraph.asserts_on?(:combine_with_closure_name)).to be true - expect { pin1.combine_with(pin2) }.to raise_error(RuntimeError, /Inconsistent :closure name/) - end - end - - describe '#visible_at?' do it 'detects scoped methods in rebound blocks' do source = Solargraph::Source.load_string(%( From c29deaff0e4e21d7c0a80f3841cf5fb22bf62a4b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 22 Oct 2025 08:30:12 -0400 Subject: [PATCH 526/930] Fix type issue --- lib/solargraph/type_checker.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 7e4e910ed..3ce0a9b35 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -745,8 +745,9 @@ def fake_args_for(pin) args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)]) end end - args.push Solargraph::Parser.chain_string('{}', filename, pin.location&.line || 0) if with_opts - args.push Solargraph::Parser.chain_string('&', filename, pin.location&.line || 0) if with_block + starting_line = pin.location&.range&.start&.line || 0 + args.push Solargraph::Parser.chain_string('{}', filename, starting_line) if with_opts + args.push Solargraph::Parser.chain_string('&', filename, starting_line) if with_block args end From 46088728ba70701a8c48333c87e141e7622be617 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 22 Oct 2025 09:22:41 -0400 Subject: [PATCH 527/930] Fix RuboCop issue --- 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 3ce0a9b35..8febd7d54 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -745,7 +745,8 @@ def fake_args_for(pin) args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)]) end end - starting_line = pin.location&.range&.start&.line || 0 + pin_location = pin.location + starting_line = pin_location ? pin_location.range.start.line : 0 args.push Solargraph::Parser.chain_string('{}', filename, starting_line) if with_opts args.push Solargraph::Parser.chain_string('&', filename, starting_line) if with_block args From 350b01ea34c253c099d88806875667cdfb9ef2b8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 22 Oct 2025 09:22:58 -0400 Subject: [PATCH 528/930] Deal with issue seen in typechecking --- lib/solargraph/parser/parser_gem/node_processors/def_node.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/def_node.rb b/lib/solargraph/parser/parser_gem/node_processors/def_node.rb index 5d0d9a284..309de01a1 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/def_node.rb @@ -8,12 +8,13 @@ class DefNode < Parser::NodeProcessor::Base def process name = node.children[0].to_s scope = region.scope || (region.closure.is_a?(Pin::Singleton) ? :class : :instance) + method_binder = scope == :instance ? region.closure.binder.namespace_type : region.closure.binder methpin = Solargraph::Pin::Method.new( location: get_node_location(node), closure: region.closure, name: name, - context: region.closure.binder.namespace_type, - binder: region.closure.binder.namespace_type, + context: method_binder, + binder: method_binder, comments: comments_for(node), scope: scope, visibility: scope == :instance && name == 'initialize' ? :private : region.visibility, From 6e0fc44bcfc708a6664c831d9723549f94c69040 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 22 Oct 2025 22:52:30 -0400 Subject: [PATCH 529/930] Set default on parameter for solargraph-rails compatibility --- lib/solargraph/parser/parser_gem/class_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index adafbae32..654ea1b01 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -12,7 +12,7 @@ module ClassMethods # can find relevant local variables later even if this is just # a subset of the file in question # @return [Array(Parser::AST::Node, Hash{Integer => Solargraph::Parser::Snippet})] - def parse_with_comments code, filename, starting_line + def parse_with_comments code, filename, starting_line = 0 node = parse(code, filename, starting_line) comments = CommentRipper.new(code, filename, 0).parse [node, comments] From c9abf56918ff7f2a40b838bc0537883c067b76b8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 22 Oct 2025 23:02:21 -0400 Subject: [PATCH 530/930] Set default on parameter for solargraph-rails compatibility --- lib/solargraph/parser/parser_gem/class_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index 654ea1b01..1cc40e33b 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -22,7 +22,7 @@ def parse_with_comments code, filename, starting_line = 0 # @param filename [String] # @param starting_line [Integer] # @return [Parser::AST::Node] - def parse code, filename, starting_line + def parse code, filename, starting_line = 0 buffer = ::Parser::Source::Buffer.new(filename, starting_line) buffer.source = code parser.parse(buffer) From 39704a0648106fb10fb05a596977728601a12505 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 23 Oct 2025 08:49:23 -0400 Subject: [PATCH 531/930] Force build --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 7f344c712..2382a25f3 100755 --- a/README.md +++ b/README.md @@ -132,9 +132,7 @@ 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. +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. From 3d71d062c19ab72dd3a9804efb068dba4aa353d6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 23 Oct 2025 09:10:52 -0400 Subject: [PATCH 532/930] Update accounting of remaining issues --- lib/solargraph/convention/struct_definition.rb | 2 +- lib/solargraph/pin/base.rb | 4 ++-- lib/solargraph/pin/base_variable.rb | 4 ++-- lib/solargraph/pin/parameter.rb | 4 ++-- lib/solargraph/pin/signature.rb | 4 ++-- lib/solargraph/source/chain/call.rb | 2 +- lib/solargraph/source/change.rb | 4 ++-- lib/solargraph/type_checker.rb | 2 +- lib/solargraph/type_checker/rules.rb | 18 ++++++------------ 9 files changed, 19 insertions(+), 25 deletions(-) diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index 064d40677..3f9a255da 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -142,8 +142,8 @@ def parse_comments # @param tag [YARD::Tags::Tag, nil] The param tag for this attribute.xtract_ # # @return [String] - # @sg-ignore need to improve nil-removal of || def tag_string(tag) + # @sg-ignore need to improve handling of &. tag&.types&.join(',') || 'undefined' end diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index a69303f7d..e4c457573 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -525,7 +525,7 @@ def macros # # @return [Boolean] def maybe_directives? - # @sg-ignore flow sensitive typing needs to handle && on both sides + # @sg-ignore flow sensitive typing needs to handle ivars return !@directives.empty? if defined?(@directives) && @directives @maybe_directives ||= comments.include?('@!') end @@ -613,7 +613,7 @@ def identity # Example: Given the name 'Bar' and the gates ['Foo', ''], # the fully qualified namespace should be 'Foo::Bar' or 'Bar'. # - # @sg-ignore Need to look at infer handling of recursive methods + # @sg-ignore need to improve handling of &. # @return [Array] def gates @gates ||= closure&.gates || [''] diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index b15028c0e..4be837d5f 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -137,7 +137,7 @@ def probe api_map if presence_certain? && return_type&.defined? # flow sensitive typing has already figured out this type # has been downcast - use the type it figured out - # @sg-ignore flow sensitive typing needs to handle && on both sides + # @sg-ignore flow sensitive typing needs to handle ivars return return_type.qualify(api_map, *gates) end @@ -198,7 +198,7 @@ def starts_at?(other_loc) # @sg-ignore need to improve handling of &. location&.filename == other_loc.filename && presence && - # @sg-ignore flow sensitive typing needs to handle && on both sides + # @sg-ignore flow sensitive typing needs to handle ivars presence.start == other_loc.range.start end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index a43e39ca3..6a646008e 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -21,12 +21,12 @@ def initialize decl: :arg, asgn_code: nil, **splat @decl = decl end - # @sg-ignore need to improve nil-removal of || + # @sg-ignore need to improve handling of &. def type_location super || closure&.type_location end - # @sg-ignore need to improve nil-removal of || + # @sg-ignore need to improve handling of &. def location super || closure&.type_location end diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 5e70432f9..ab5ddb5ba 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -25,12 +25,12 @@ def dodgy_return_type_source? super || closure&.dodgy_return_type_source? end - # @sg-ignore need to improve nil-removal of || + # @sg-ignore need to improve handling of &. def type_location super || closure&.type_location end - # @sg-ignore need to improve nil-removal of || + # @sg-ignore need to improve handling of &. def location super || closure&.location end diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 53275c2f9..d20f2e9bc 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -331,7 +331,7 @@ def find_block_pin(api_map) node_location = Solargraph::Location.from_node(block.node) return if node_location.nil? block_pins = api_map.get_block_pins - # @sg-ignore flow sensitive typing needs to handle inner closures + # @sg-ignore Need to add nil check here block_pins.find { |pin| pin.location.contain?(node_location) } end diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index 0bbf90972..9a8302f48 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -31,11 +31,11 @@ def write text, nullable = false if nullable and !range.nil? and new_text.match(/[.\[{(@$:]$/) [':', '@'].each do |dupable| next unless new_text == dupable - # @sg-ignore flow sensitive typing needs to handle && on both sides + # @sg-ignore flow sensitive typing needs to handle ivars offset = Position.to_offset(text, range.start) if text[offset - 1] == dupable p = Position.from_offset(text, offset - 1) - # @sg-ignore flow sensitive typing needs to handle && on both sides + # @sg-ignore flow sensitive typing needs to handle ivars r = Change.new(Range.new(p, range.start), ' ') text = r.write(text) end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 8b442416d..8682b4120 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -872,7 +872,7 @@ def without_ignored problems problems.reject do |problem| node = source.node_at(problem.location.range.start.line, problem.location.range.start.column) ignored = node && source.comments_for(node)&.include?('@sg-ignore') - # @sg-ignore Unresolved call to ! + # @sg-ignore need boolish support for ? methods 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}" } diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 82239b356..c8b8914f5 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,20 +58,19 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # pending code fixes: + # pending code fixes (~280): # - # @todo 265: Need to add nil check here + # @todo 266: Need to add nil check here # @todo 9: Need to validate config # @todo 3: Translate to something flow sensitive typing understands + # @todo 2: Need a downcast here # # flow-sensitive typing could handle (~100): # - # @todo 39: flow sensitive typing needs to handle ivars + # @todo 50: flow sensitive typing needs to handle ivars + # @todo 13: need to improve handling of &. # @todo 8: Should handle redefinition of types in simple contexts - # @todo 7: need to improve handling of &. - # @todo 7: flow sensitive typing needs to handle inner closures - # @todo 5: need boolish support for ? methods - # @todo 5: flow sensitive typing needs to handle && on both sides + # @todo 7: need boolish support for ? methods # @todo 4: Need to understand .is_a? implies not nil # @todo 4: flow sensitive typing needs better handling of ||= on lvars # @todo 4: @type should override probed type @@ -81,16 +80,11 @@ def require_inferred_type_params? # @todo 2: Need to look at Tuple#include? handling # @todo 2: Should better support meaning of '&' in RBS # @todo 2: flow sensitive typing needs to handle "if foo = bar" - # @todo 2: Need a downcast here # @todo 1: flow sensitive typing needs to create separate ranges for postfix if # @todo 1: flow sensitive typing handling else from is_a? with union types - # @todo 1: Need to look at infer handling of recursive methods - # @todo 1: need to improve nil-removal of || # @todo 1: flow sensitive typing needs to handle constants # @todo 1: To make JSON strongly typed we'll need a record syntax - # @todo 1: Untyped method Solargraph::Pin::Base#assert_same could not be inferred # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' - # @todo 1: Unresolved call to ! # @todo 1: flow sensitive typing needs to eliminate literal from union with return if foo == :bar # @todo 1: flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) def require_all_unique_types_match_expected? From 979419d7743ef36f23db24134bb11803e1d6ce87 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 23 Oct 2025 17:24:43 -0400 Subject: [PATCH 533/930] Fix a bug in q_call handling --- lib/solargraph/complex_type.rb | 4 +++- lib/solargraph/complex_type/unique_type.rb | 7 +++++++ lib/solargraph/source/chain/call.rb | 9 ++++++++- spec/type_checker/levels/alpha_spec.rb | 13 +++++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index c64152445..f4c72fa0a 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -292,7 +292,9 @@ def nullable? # @return [ComplexType] def without_nil - ComplexType.new(@items.reject(&:nil_type?)) + new_items = @items.reject(&:nil_type?) + return ComplexType::UNDEFINED if new_items.empty? + ComplexType.new(new_items) end # @return [Array] diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 4fd281f60..a1df14a98 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -123,6 +123,13 @@ def non_literal_name @non_literal_name ||= determine_non_literal_name end + # @return [self] + def without_nil + return UniqueType::UNDEFINED if nil_type? + + self + end + # @return [String] def determine_non_literal_name # https://github.com/ruby/rbs/blob/master/docs/syntax.md diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index d20f2e9bc..ba8cad23a 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -53,8 +53,15 @@ def resolve api_map, name_pin, locals return yield_pins(api_map, name_pin) if word == 'yield' found = api_map.var_at_location(locals, word, name_pin, location) if head? return inferred_pins([found], api_map, name_pin, locals) unless found.nil? + binder = name_pin.binder + # this is a q_call - i.e., foo&.bar - assume result of call + # will be nil or result as if binder were not nil - + # chain.rb#maybe_nil will add the nil type later, we just + # need to worry about the not-nil case + # @sg-ignore ComplexType::UniqueType needs without_nil + binder = binder.without_nil if nullable? # @sg-ignore signature isn't down-selected - pin_groups = name_pin.binder.each_unique_type.map do |context| + pin_groups = binder.each_unique_type.map do |context| ns_tag = context.namespace == '' ? '' : context.namespace_type.tag stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) [stack.first].compact diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 824810038..bbe78e47e 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -71,5 +71,18 @@ def catalog expect(checker.problems.map(&:message)).to eq([]) end + + it 'understands &. in return position' do + checker = type_checker(%( + class Baz + # @param bar [String, nil] + # @return [String] + def foo bar + bar&.upcase || 'undefined' + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end end end From cc9c2b429b4908125deaf2f985f048cd74a5189d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 23 Oct 2025 17:42:32 -0400 Subject: [PATCH 534/930] Update accounting --- lib/solargraph/convention/struct_definition.rb | 1 - lib/solargraph/library.rb | 3 +-- lib/solargraph/parser/flow_sensitive_typing.rb | 1 + lib/solargraph/parser/parser_gem/node_chainer.rb | 1 - lib/solargraph/pin/base.rb | 2 -- lib/solargraph/pin/base_variable.rb | 4 +--- lib/solargraph/pin/delegated_method.rb | 1 - lib/solargraph/pin/method.rb | 1 - lib/solargraph/pin/namespace.rb | 1 - lib/solargraph/pin/parameter.rb | 2 -- lib/solargraph/pin/signature.rb | 4 +--- lib/solargraph/source/chain/call.rb | 8 ++++---- lib/solargraph/source/chain/or.rb | 2 ++ lib/solargraph/source_map/mapper.rb | 2 +- lib/solargraph/type_checker.rb | 1 - lib/solargraph/type_checker/rules.rb | 13 ++++++++----- 16 files changed, 19 insertions(+), 28 deletions(-) diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index 3f9a255da..fee661d84 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -143,7 +143,6 @@ def parse_comments # # @return [String] def tag_string(tag) - # @sg-ignore need to improve handling of &. tag&.types&.join(',') || 'undefined' end diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index bdacb9faf..91a3154f7 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -151,7 +151,6 @@ def delete *filenames # @param filename [String] # @return [void] def close filename - # @sg-ignore need to improve handling of &. return unless @current&.filename == filename @current = nil @@ -268,8 +267,8 @@ def references_from filename, line, column, strip: false, only: false found = source.references(pin.name) found.select! do |loc| # @sg-ignore Need to add nil check here + # @type [Solargraph::Pin::Base, nil] referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character).first - # @sg-ignore need to improve handling of &. referenced&.path == pin.path end if pin.path == 'Class#new' diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 7d928e7b4..8d81c4a00 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -466,6 +466,7 @@ def always_breaks?(clause_node) # @param clause_node [Parser::AST::Node, nil] def always_leaves_compound_statement?(clause_node) # https://docs.ruby-lang.org/en/2.2.0/keywords_rdoc.html + # @sg-ignore Need to look at Tuple#include? handling [:return, :raise, :next, :redo, :retry].include?(clause_node&.type) end diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index ebce10d78..bc04c2855 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -170,7 +170,6 @@ def hash_is_splatted? node # @param node [Parser::AST::Node] # @return [Source::Chain, nil] def passed_block node - # @sg-ignore need to improve handling of &. return unless node == @node && @parent&.type == :block # @sg-ignore Need to add nil check here diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index e4c457573..a4b5cd618 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -613,7 +613,6 @@ def identity # Example: Given the name 'Bar' and the gates ['Foo', ''], # the fully qualified namespace should be 'Foo::Bar' or 'Bar'. # - # @sg-ignore need to improve handling of &. # @return [Array] def gates @gates ||= closure&.gates || [''] @@ -642,7 +641,6 @@ def type_desc # @return [String] def inner_desc - # @sg-ignore need to improve handling of &. closure_info = closure&.name.inspect binder_info = binder&.desc "name=#{name.inspect} return_type=#{type_desc}, context=#{context.rooted_tags}, closure=#{closure_info}, binder=#{binder_info}" diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 4be837d5f..426a5de7d 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -172,7 +172,6 @@ def == other end def type_desc - # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array "#{super} = #{assignment&.type.inspect}" end @@ -193,9 +192,8 @@ def visible_at?(other_closure, other_loc) end # @param other_loc [Location] - # @sg-ignore need to improve handling of &. + # @sg-ignore flow sensitive typing needs to handle ivars def starts_at?(other_loc) - # @sg-ignore need to improve handling of &. location&.filename == other_loc.filename && presence && # @sg-ignore flow sensitive typing needs to handle ivars diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index f6f814d71..2474ffe2c 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -15,7 +15,6 @@ class DelegatedMethod < Pin::Method # @param receiver [Source::Chain, nil] the source code used to resolve the receiver for this delegated method. # @param name [String, nil] # @param receiver_method_name [String, nil] the method name that will be called on the receiver (defaults to :name). - # @sg-ignore need to improve handling of &. 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) # @sg-ignore Need to add nil check here diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 45a0accec..95928e65c 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -311,7 +311,6 @@ def typify api_map return decl end type = see_reference(api_map) || typify_from_super(api_map) - # @sg-ignore Need to add nil check here logger.debug { "Method#typify(self=#{self}) - type=#{type&.rooted_tags.inspect}" } unless type.nil? # @sg-ignore Need to add nil check here diff --git a/lib/solargraph/pin/namespace.rb b/lib/solargraph/pin/namespace.rb index c6fec5e7e..6bd472b47 100644 --- a/lib/solargraph/pin/namespace.rb +++ b/lib/solargraph/pin/namespace.rb @@ -104,7 +104,6 @@ def typify api_map return_type end - # @sg-ignore Solargraph::Pin::Namespace#gates return type could not be inferred def gates @gates ||= if path.empty? @open_gates diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 6a646008e..638662edd 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -21,12 +21,10 @@ def initialize decl: :arg, asgn_code: nil, **splat @decl = decl end - # @sg-ignore need to improve handling of &. def type_location super || closure&.type_location end - # @sg-ignore need to improve handling of &. def location super || closure&.type_location end diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index ab5ddb5ba..0a6dbbafb 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -20,17 +20,15 @@ def identity attr_writer :closure - # @sg-ignore need boolish support for ? methods + # @ sg-ignore need boolish support for ? methods def dodgy_return_type_source? super || closure&.dodgy_return_type_source? end - # @sg-ignore need to improve handling of &. def type_location super || closure&.type_location end - # @sg-ignore need to improve handling of &. def location super || closure&.location end diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index ba8cad23a..1b7eb6c5f 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -58,15 +58,16 @@ def resolve api_map, name_pin, locals # will be nil or result as if binder were not nil - # chain.rb#maybe_nil will add the nil type later, we just # need to worry about the not-nil case - # @sg-ignore ComplexType::UniqueType needs without_nil + + # @sg-ignore Need to handle duck-typed method calls on union types binder = binder.without_nil if nullable? - # @sg-ignore signature isn't down-selected + # @sg-ignore Need to handle duck-typed method calls on union types pin_groups = binder.each_unique_type.map do |context| ns_tag = context.namespace == '' ? '' : context.namespace_type.tag stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) [stack.first].compact end - # @sg-ignore signature isn't down-selected + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array if !api_map.loose_unions && pin_groups.any? { |pins| pins.empty? } pin_groups = [] end @@ -313,7 +314,6 @@ def fix_block_pass # @param context [ComplexType, ComplexType::UniqueType] # @param block_parameter_types [::Array] # @param locals [::Array] - # @sg-ignore Need to add nil check here # @return [ComplexType, nil] def block_symbol_call_type(api_map, context, block_parameter_types, locals) # Ruby's shorthand for sending the passed in method name diff --git a/lib/solargraph/source/chain/or.rb b/lib/solargraph/source/chain/or.rb index a96c13178..87fa9a7fb 100644 --- a/lib/solargraph/source/chain/or.rb +++ b/lib/solargraph/source/chain/or.rb @@ -8,6 +8,8 @@ def word '' end + attr_reader :links + # @param links [::Array] def initialize links @links = links diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index e221b7028..1e107354e 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -169,8 +169,8 @@ def process_directive source_position, comment_position, directive end end when 'visibility' - kind = directive.tag.text&.to_sym + # @sg-ignore Need to look at Tuple#include? handling return unless [:private, :protected, :public].include?(kind) name = directive.tag.name diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 8682b4120..91c456871 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -872,7 +872,6 @@ def without_ignored problems problems.reject do |problem| node = source.node_at(problem.location.range.start.line, problem.location.range.start.column) ignored = node && source.comments_for(node)&.include?('@sg-ignore') - # @sg-ignore need boolish support for ? methods 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}" } diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index c8b8914f5..1a7ae0b2f 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -60,30 +60,33 @@ def require_inferred_type_params? # pending code fixes (~280): # - # @todo 266: Need to add nil check here + # @todo 264: Need to add nil check here # @todo 9: Need to validate config # @todo 3: Translate to something flow sensitive typing understands # @todo 2: Need a downcast here # # flow-sensitive typing could handle (~100): # - # @todo 50: flow sensitive typing needs to handle ivars - # @todo 13: need to improve handling of &. + # @todo 51 flow sensitive typing needs to handle ivars # @todo 8: Should handle redefinition of types in simple contexts - # @todo 7: need boolish support for ? methods + # @todo 6: need boolish support for ? methods # @todo 4: Need to understand .is_a? implies not nil # @todo 4: flow sensitive typing needs better handling of ||= on lvars # @todo 4: @type should override probed type + # @todo 4: Need to look at Tuple#include? handling + # @todo 4: literal arrays in this module turn into ::Solargraph::Source::Chain::Array # @todo 3: downcast output of Enumerable#select # @todo 3: flow sensitive typing needs to handle 'raise if' # @todo 2: should warn on nil dereference below - # @todo 2: Need to look at Tuple#include? handling # @todo 2: Should better support meaning of '&' in RBS # @todo 2: flow sensitive typing needs to handle "if foo = bar" + # @todo 2: Need to handle duck-typed method calls on union types # @todo 1: flow sensitive typing needs to create separate ranges for postfix if # @todo 1: flow sensitive typing handling else from is_a? with union types # @todo 1: flow sensitive typing needs to handle constants # @todo 1: To make JSON strongly typed we'll need a record syntax + # @todo 1: signature isn't down-selected + # @todo 1: ComplexType::UniqueType needs without_nil # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' # @todo 1: flow sensitive typing needs to eliminate literal from union with return if foo == :bar # @todo 1: flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) From 9f009df8a1232f55df4a6c54803bf4ff08070142 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 23 Oct 2025 20:17:03 -0400 Subject: [PATCH 535/930] Update accounting --- lib/solargraph/pin/base_variable.rb | 6 +++--- lib/solargraph/source/chain/call.rb | 2 +- lib/solargraph/type_checker/rules.rb | 9 ++++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 426a5de7d..b46d0bf1c 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -279,19 +279,19 @@ def visible_in_closure? other_closure cursor = haystack until cursor.nil? - # @sg-ignore Need to understand .is_a? implies not nil + # @sg-ignore Need to add nil check here if cursor.is_a?(Pin::Method) && closure.context.tags == 'Class<>' # methods can't see local variables declared in their # parent closure return false end - # @sg-ignore Need to understand .is_a? implies not nil + # @sg-ignore Need to add nil check here if cursor.binder.namespace == needle.binder.namespace return true end - # @sg-ignore Need to understand .is_a? implies not nil + # @sg-ignore Need to add nil check here if cursor.return_type == needle.context return true end diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 1b7eb6c5f..d0cac35c8 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -267,7 +267,7 @@ def extra_return_type docstring, context def find_method_pin(name_pin) method_pin = name_pin until method_pin.is_a?(Pin::Method) - # @sg-ignore Need to understand .is_a? implies not nil + # @sg-ignore Flow sensitive typing could figure out this is not nil at both entrypoints method_pin = method_pin.closure return if method_pin.nil? end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 1a7ae0b2f..4a2750e37 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -60,7 +60,7 @@ def require_inferred_type_params? # pending code fixes (~280): # - # @todo 264: Need to add nil check here + # @todo 267: Need to add nil check here # @todo 9: Need to validate config # @todo 3: Translate to something flow sensitive typing understands # @todo 2: Need a downcast here @@ -70,16 +70,14 @@ def require_inferred_type_params? # @todo 51 flow sensitive typing needs to handle ivars # @todo 8: Should handle redefinition of types in simple contexts # @todo 6: need boolish support for ? methods - # @todo 4: Need to understand .is_a? implies not nil - # @todo 4: flow sensitive typing needs better handling of ||= on lvars - # @todo 4: @type should override probed type + # @todo 4: (*) flow sensitive typing needs better handling of ||= on lvars # @todo 4: Need to look at Tuple#include? handling # @todo 4: literal arrays in this module turn into ::Solargraph::Source::Chain::Array # @todo 3: downcast output of Enumerable#select # @todo 3: flow sensitive typing needs to handle 'raise if' # @todo 2: should warn on nil dereference below # @todo 2: Should better support meaning of '&' in RBS - # @todo 2: flow sensitive typing needs to handle "if foo = bar" + # @todo 2: (*) flow sensitive typing needs to handle "if foo = bar" # @todo 2: Need to handle duck-typed method calls on union types # @todo 1: flow sensitive typing needs to create separate ranges for postfix if # @todo 1: flow sensitive typing handling else from is_a? with union types @@ -90,6 +88,7 @@ def require_inferred_type_params? # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' # @todo 1: flow sensitive typing needs to eliminate literal from union with return if foo == :bar # @todo 1: flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) + # @todo 1: Flow sensitive typing could figure out this is not nil at both entrypoints def require_all_unique_types_match_expected? rank >= LEVELS[:strong] end From d5cfffa283ff52de1facb373f4da7b9a087a4ec3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 24 Oct 2025 08:18:35 -0400 Subject: [PATCH 536/930] Update accounting --- lib/solargraph/shell.rb | 2 +- lib/solargraph/type_checker/rules.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 5cc57cc3f..baab55a2d 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -229,7 +229,7 @@ def scan STDERR.puts "Error testing #{pin_description(pin)} #{pin.location ? "at #{pin.location.filename}:#{pin.location.range.start.line + 1}" : ''}" STDERR.puts "[#{e.class}]: #{e.message}" # @todo Need to add nil check here - # @todo should warn on nil dereference below + # @todo Should handle redefinition of types in simple contexts STDERR.puts e.backtrace.join("\n") exit 1 end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 4a2750e37..307ce43ed 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -68,7 +68,7 @@ def require_inferred_type_params? # flow-sensitive typing could handle (~100): # # @todo 51 flow sensitive typing needs to handle ivars - # @todo 8: Should handle redefinition of types in simple contexts + # @todo 9: Should handle redefinition of types in simple contexts # @todo 6: need boolish support for ? methods # @todo 4: (*) flow sensitive typing needs better handling of ||= on lvars # @todo 4: Need to look at Tuple#include? handling From 2b9e12c8d5532c323ea133ccee7c29643a926c5b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 24 Oct 2025 08:20:42 -0400 Subject: [PATCH 537/930] Add (back) @sg-ignore --- 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 337b02a4f..5ffaa13e1 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 - # + # @sg-ignore Unrecognized keyword argument chdir to Open3.capture2e 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}" } From bba54e748f7f7f567cb4fb409924b151eafc4816 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 24 Oct 2025 17:34:37 -0400 Subject: [PATCH 538/930] Add missing-but-probably-needed method --- lib/solargraph/complex_type/unique_type.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 05a585dcf..946b26374 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -118,6 +118,13 @@ def non_literal_name @non_literal_name ||= determine_non_literal_name end + # @return [self] + def without_nil + return UniqueType::UNDEFINED if nil_type? + + self + end + # @return [String] def determine_non_literal_name # https://github.com/ruby/rbs/blob/master/docs/syntax.md From 8a0bf224aa906b9b48bbf68cf901925b05601576 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 24 Oct 2025 22:00:42 -0400 Subject: [PATCH 539/930] Fix merge issues --- lib/solargraph/pin/local_variable.rb | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 63ca58483..6998b1d09 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -16,7 +16,7 @@ def presence_certain? # type returned will never include these unique types in the # unique types of its complex type # @param splat [Hash] - def initialize assignment: nil, presence: nil, presence_certain: false, exclude_return_type: nil, + def initialize presence: nil, presence_certain: false, exclude_return_type: nil, **splat super(**splat) @presence = presence @@ -161,16 +161,6 @@ def combine_mass_assignment(other) mass_assignment || other.mass_assignment end - def probe api_map - if presence_certain? && return_type&.defined? - # flow sensitive typing has already probed this type - use - # the type it figured out - return return_type.qualify(api_map, *gates) - end - - super - end - # Narrow the presence range to the intersection of both. # # @param other [self] From 1cda7aca78d7d17636f0a5e5c59531c18eef5bd8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 24 Oct 2025 22:28:04 -0400 Subject: [PATCH 540/930] Fix merge issues --- spec/parser/flow_sensitive_typing_spec.rb | 6 +++--- spec/source/chain/call_spec.rb | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index cac5523c0..dcc256956 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -552,7 +552,7 @@ def verify_repro(repr, throw_the_dice) expect(clip.infer.rooted_tags).to eq('::Integer') clip = api_map.clip_at('test.rb', [8, 10]) - expect(clip.infer.rooted_tags).to eq('nil') + expect(clip.infer.rooted_tags).to eq('::Integer, nil') end it 'uses variable in a simple if() to refine types' do @@ -595,6 +595,8 @@ def verify_repro(repr = nil) clip = api_map.clip_at('test.rb', [3, 8]) expect(clip.infer.rooted_tags).to eq('10, nil') + pending('TODO: regression?') + clip = api_map.clip_at('test.rb', [5, 10]) expect(clip.infer.rooted_tags).to eq('10') @@ -662,8 +664,6 @@ def bar(arr, baz: nil) clip = api_map.clip_at('test.rb', [6, 10]) expect(clip.infer.rooted_tags).to eq('::Boolean, nil') - pending('better scoping of return if in blocks') - clip = api_map.clip_at('test.rb', [9, 12]) expect(clip.infer.rooted_tags).to eq('::Boolean') diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index 7dea01b76..e02f8f0bd 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -661,8 +661,6 @@ def bl end it 'sends proper gates in ProxyType' do - pending 'Proxytype improvements' - source = Solargraph::Source.load_string(%( module Foo module Bar From 41e7d49b14ee2a4bb9edb5cc71d1ac65b86e31cf Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 24 Oct 2025 22:37:51 -0400 Subject: [PATCH 541/930] Add @sg-ignore --- lib/solargraph/source/chain/or.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solargraph/source/chain/or.rb b/lib/solargraph/source/chain/or.rb index 6f4c9e23d..0f422c384 100644 --- a/lib/solargraph/source/chain/or.rb +++ b/lib/solargraph/source/chain/or.rb @@ -17,6 +17,8 @@ def resolve api_map, name_pin, locals types = @links.map { |link| link.infer(api_map, name_pin, locals) } combined_type = Solargraph::ComplexType.new(types) unless types.all?(&:nullable?) + # @sg-ignore Unresolved call to without_nil on + # Solargraph::ComplexType combined_type = combined_type.without_nil end From 97f5c6a527c8cacf89c7c9c89e1a46620daeedf4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 24 Oct 2025 22:46:47 -0400 Subject: [PATCH 542/930] Drop @sg-ignore --- lib/solargraph/yardoc.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 5ffaa13e1..a4e5c2ab4 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -23,7 +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 Unrecognized keyword argument chdir to Open3.capture2e 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}" } From 545ca32c0ef58db19b77eba83e79d7d2f26688d8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 24 Oct 2025 22:56:46 -0400 Subject: [PATCH 543/930] Fix @sg-ignore issues --- lib/solargraph/pin/base_variable.rb | 3 --- lib/solargraph/pin/local_variable.rb | 3 --- lib/solargraph/pin/parameter.rb | 1 - 3 files changed, 7 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 433351c29..b2c30f4c1 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -31,15 +31,12 @@ def reset_generated! end def combine_with(other, attrs={}) - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 new_assignments = combine_assignments(other) new_attrs = attrs.merge({ assignments: new_assignments, - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 mass_assignment: combine_mass_assignment(other), return_type: combine_return_type(other), }) - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 super(other, new_attrs) end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 6998b1d09..3cf8a0e82 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -63,12 +63,9 @@ def combine_with(other, attrs={}) # keep this as a parameter return other.combine_with(self, attrs) if other.is_a?(Parameter) && !self.is_a?(Parameter) - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 new_assignments = combine_assignments(other) new_attrs = attrs.merge({ - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 presence: combine_presence(other), - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 presence_certain: combine_presence_certain(other), }) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 7ef684934..77eb55e86 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -42,7 +42,6 @@ def combine_with(other, attrs={}) asgn_code: asgn_code } end - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 super(other, new_attrs.merge(attrs)) end From 68bec899a3b4c88b00ac788a9d62984c69187c18 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 24 Oct 2025 22:57:59 -0400 Subject: [PATCH 544/930] Add @sg-ignore --- lib/solargraph/yardoc.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index a4e5c2ab4..5ffaa13e1 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -23,6 +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 + # @sg-ignore Unrecognized keyword argument chdir to Open3.capture2e 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}" } From 3c90be364d93ae36602bc8bfc4f9c30b269d4c91 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 24 Oct 2025 23:13:49 -0400 Subject: [PATCH 545/930] Add another unmerged-Yard-PR issue --- .rubocop_todo.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e735c4ddc..9d1487e5a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1269,7 +1269,12 @@ YARD/MismatchName: Enabled: false YARD/TagTypeSyntax: - Enabled: false + Exclude: + - 'lib/solargraph/api_map/constants.rb' + - '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 41c84a45b2f37880c4eeacfd0e2bdbbf2067e151 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 24 Oct 2025 23:24:43 -0400 Subject: [PATCH 546/930] Fix merge --- .rubocop_todo.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 9d1487e5a..e735c4ddc 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1269,12 +1269,7 @@ YARD/MismatchName: Enabled: false YARD/TagTypeSyntax: - Exclude: - - 'lib/solargraph/api_map/constants.rb' - - '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. From 64c94b46a125eff124c6e1d509d16d81cfcd2b69 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 24 Oct 2025 23:33:24 -0400 Subject: [PATCH 547/930] Fix merge issue --- spec/type_checker/levels/strong_spec.rb | 57 ------------------------- 1 file changed, 57 deletions(-) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index c3ca712ba..628c9d1b0 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -596,63 +596,6 @@ def objects_by_class klass end 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 - - 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 - - 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 - - it 'resolves class name correctly in generic resolution' do - expect(checker.problems.map(&:message)).to be_empty - end - end - it 'handles "while foo" flow sensitive typing correctly' do checker = type_checker(%( # @param a [String, nil] From dd1b0c25bf6e8f4df7d0b149649e15d4fa064bf7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 24 Oct 2025 23:41:56 -0400 Subject: [PATCH 548/930] Adjust RuboCop todo --- .rubocop_todo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0df83216a..f29d03232 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -452,7 +452,7 @@ Metrics/AbcSize: # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. # AllowedMethods: refine Metrics/BlockLength: - Max: 56 + Max: 57 # Configuration parameters: CountBlocks, CountModifierForms. Metrics/BlockNesting: From 05f1c1ec6a6ca6afe74b904260036ca6ea5189cf Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 07:57:15 -0400 Subject: [PATCH 549/930] Fix merge issue --- lib/solargraph/source/chain/call.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 5fdbf1e30..c9430c0fb 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -52,7 +52,7 @@ def resolve api_map, name_pin, locals return yield_pins(api_map, name_pin) if word == 'yield' found = api_map.var_at_location(locals, word, name_pin, location) if head? - return inferred_pins(found, api_map, name_pin, locals) unless found.empty? + return inferred_pins(found, api_map, name_pin, locals) unless found.nil? pin_groups = name_pin.binder.each_unique_type.map do |context| ns_tag = context.namespace == '' ? '' : context.namespace_type.tag stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) From 1d63a0d3db6cd29f64ee1820a2c584c55e500304 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 08:01:51 -0400 Subject: [PATCH 550/930] Fix merge issue --- 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 1e6903976..301bd393c 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -105,10 +105,10 @@ def load filename, level = :normal # @param level [Symbol] # @param api_map [Solargraph::ApiMap] # @return [self] - def load_string code, filename = nil, level = :normal, api_map: Solargraph::ApiMap.new + def load_string code, filename = nil, level = :normal, api_map: nil source = Solargraph::Source.load_string(code, filename) rules = Rules.new(level) - api_map = Solargraph::ApiMap.new(loose_unions: rules.loose_unions?) + api_map ||= Solargraph::ApiMap.new(loose_unions: rules.loose_unions?) api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end From 36273f474735e50ce9c51a64455c697ae0bbfa8c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 08:02:57 -0400 Subject: [PATCH 551/930] Fix merge issue --- lib/solargraph/source/chain/call.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index c9430c0fb..41d02a214 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -52,7 +52,7 @@ def resolve api_map, name_pin, locals return yield_pins(api_map, name_pin) if word == 'yield' found = api_map.var_at_location(locals, word, name_pin, location) if head? - return inferred_pins(found, api_map, name_pin, locals) unless found.nil? + return inferred_pins([found], api_map, name_pin, locals) unless found.nil? pin_groups = name_pin.binder.each_unique_type.map do |context| ns_tag = context.namespace == '' ? '' : context.namespace_type.tag stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) From 281fabf654a9890eee19be1456b9c99a96c98f3c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 08:12:10 -0400 Subject: [PATCH 552/930] Fix annotations --- lib/solargraph/api_map.rb | 1 + lib/solargraph/doc_map.rb | 1 + lib/solargraph/pin/namespace.rb | 1 - lib/solargraph/source_map.rb | 1 - lib/solargraph/source_map/mapper.rb | 8 ++------ lib/solargraph/type_checker.rb | 1 - 6 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 2f01ebd48..3797493ea 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -804,6 +804,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if scope == :instance store.get_includes(fqns).reverse.each do |ref| in_tag = dereference(ref) + # @sg-ignore Need to add nil check here 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) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 0df33f350..e606b92d5 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -178,6 +178,7 @@ 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 better typing for Hash[] # @type [Array] paths = Hash[without_gemspecs].keys # @type [Array] diff --git a/lib/solargraph/pin/namespace.rb b/lib/solargraph/pin/namespace.rb index 6bd472b47..8799e970a 100644 --- a/lib/solargraph/pin/namespace.rb +++ b/lib/solargraph/pin/namespace.rb @@ -35,7 +35,6 @@ def initialize type: :class, visibility: :public, gates: [''], name: '', **splat # but Foo does not exist. parts = name.split('::') name = parts.pop - # @sg-ignore Need to look at Tuple#include? handling closure_name = if [Solargraph::Pin::ROOT_PIN, nil].include?(closure) '' else diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 9e25ae29c..241cc680f 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -50,7 +50,6 @@ def initialize source # @generic T # @param klass [Class>] # - # @sg-ignore Need better generic inference here # @return [Array>] def pins_by_class klass @pin_select_cache[klass] ||= pin_class_hash.select { |key, _| key <= klass }.values.flatten diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 163ac7221..78abc2c41 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -71,9 +71,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) @@ -122,6 +119,8 @@ def process_directive source_position, comment_position, directive begin src = Solargraph::Source.load_string("def #{directive.tag.name};end", @source.filename) region = Parser::Region.new(source: src, closure: namespace) + # @sg-ignore Variable type could not be inferred for method_gen_pins + # @type [Array] method_gen_pins = Parser.process_node(src.node, region).first.select { |pin| pin.is_a?(Pin::Method) } gen_pin = method_gen_pins.last return if gen_pin.nil? @@ -262,9 +261,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| diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 60d6c54f0..91c456871 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -531,7 +531,6 @@ def kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pi unless ptype.undefined? # @type [ComplexType] 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 a36cadcc3a05c6dc201b0438f83999acf250b211 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 08:16:15 -0400 Subject: [PATCH 553/930] Swap ivar expectations --- spec/type_checker/levels/strong_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 628c9d1b0..1ae981b12 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -651,12 +651,12 @@ def bar end )) + pending 'flow-sensitive typing improvements' + expect(checker.problems.map(&:message)).to be_empty end it 'knows that ivar references with intermediate calls are not safe' do - pending 'flow-sensitive typing improvements' - checker = type_checker(%( class Foo def initialize From 1bae34bf0c0be4b0f03a2e781e28f58017740c0b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 10:02:07 -0400 Subject: [PATCH 554/930] Fix merge issue --- lib/solargraph/pin/local_variable.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index b87f20790..f1bfc3808 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -67,6 +67,7 @@ def combine_with(other, attrs={}) new_attrs = attrs.merge({ presence: combine_presence(other), presence_certain: combine_presence_certain(other), + exclude_return_type: combine_types(other, :exclude_return_type) }) super(other, new_attrs) end From 104f8abbf359e0a013a507291cf11753bb24207b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 10:19:56 -0400 Subject: [PATCH 555/930] Add probe method --- lib/solargraph/pin/local_variable.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index f1bfc3808..b52e96b1d 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -133,6 +133,19 @@ def combine_return_type(other) combine_types(other, :return_type) end + # @param api_map [ApiMap] + # @return [ComplexType] + def probe api_map + if presence_certain? && return_type&.defined? + # flow sensitive typing has already figured out this type + # has been downcast - use the type it figured out + # @sg-ignore flow sensitive typing needs to handle ivars + return return_type.qualify(api_map, *gates) + end + + super + end + private attr_reader :exclude_return_type From 6151cb76a062a7c8d82ebdb6afba80bb0dd6b910 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 10:29:52 -0400 Subject: [PATCH 556/930] Fix merge issue --- lib/solargraph/pin/base_variable.rb | 3 ++- spec/parser/flow_sensitive_typing_spec.rb | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index b2c30f4c1..a3587241d 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -114,7 +114,8 @@ def return_types_from_node(parent_node, api_map) # @return [ComplexType] def probe api_map assignment_types = assignments.flat_map { |node| return_types_from_node(node, api_map) } - type_from_assignment = ComplexType.new(assignment_types.flat_map(&:items).uniq) unless assignment_types.empty? + exclude_items = exclude_return_type&.items&.uniq + type_from_assignment = ComplexType.new(assignment_types.flat_map(&:items).uniq - (exclude_items || [])) unless assignment_types.empty? return type_from_assignment unless type_from_assignment.nil? # @todo should handle merging types from mass assignments as diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index dcc256956..9a49f041d 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -595,8 +595,6 @@ def verify_repro(repr = nil) clip = api_map.clip_at('test.rb', [3, 8]) expect(clip.infer.rooted_tags).to eq('10, nil') - pending('TODO: regression?') - clip = api_map.clip_at('test.rb', [5, 10]) expect(clip.infer.rooted_tags).to eq('10') From 9e2ba8a920f58ba5899cb83e857afc461f6bb448 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 10:34:12 -0400 Subject: [PATCH 557/930] Fix merge issue --- lib/solargraph/pin/base_variable.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index a3587241d..c91d01a77 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -110,6 +110,11 @@ def return_types_from_node(parent_node, api_map) types end + # @return [nil] + def exclude_return_type + nil + end + # @param api_map [ApiMap] # @return [ComplexType] def probe api_map From f72e4a00772f994f431b22621b9e0e89ba418577 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 10:51:58 -0400 Subject: [PATCH 558/930] Drop @sg-ignores --- lib/solargraph/api_map.rb | 1 - lib/solargraph/api_map/store.rb | 4 ---- lib/solargraph/doc_map.rb | 1 - lib/solargraph/pin/local_variable.rb | 1 - lib/solargraph/position.rb | 1 - lib/solargraph/rbs_map/conversions.rb | 1 - lib/solargraph/shell.rb | 2 -- lib/solargraph/source_map/mapper.rb | 6 ------ lib/solargraph/type_checker.rb | 1 - 9 files changed, 18 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 05c60f3d1..a428e647c 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -901,7 +901,6 @@ def resolve_method_alias(alias_pin) break if original end - # @sg-ignore ignore `received nil` for original create_resolved_alias_pin(alias_pin, original) if original end diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index ccd5e64d5..d52bbf71a 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/doc_map.rb b/lib/solargraph/doc_map.rb index 5dcf28552..05f2f1647 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -180,7 +180,6 @@ def load_serialized_gem_pins # @sg-ignore Need support for RBS duck interfaces like _ToHash # @type [Array] 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 diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index b52e96b1d..2203e5dd5 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -139,7 +139,6 @@ def probe api_map if presence_certain? && return_type&.defined? # flow sensitive typing has already figured out this type # has been downcast - use the type it figured out - # @sg-ignore flow sensitive typing needs to handle ivars return return_type.qualify(api_map, *gates) end diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index f0475bbe2..ec8605d18 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -57,7 +57,6 @@ 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/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index f40824aa4..3e1874adb 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -353,7 +353,6 @@ def global_decl_to_pin decl # @param context [Context] # @param scope [Symbol] :instance or :class # @param name [String] The name of the method - # @sg-ignore # @return [Symbol] def calculate_method_visibility(decl, context, closure, scope, name) override_key = [closure.path, scope, name] diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 1a8514922..7ea659dc2 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -172,7 +172,6 @@ def reporters # @return [void] def typecheck *files directory = File.realpath(options[:directory]) - # @sg-ignore Unresolved call to options level = options[:level].to_sym rules = Solargraph::TypeChecker::Rules.new(level) api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout, loose_unions: rules.loose_unions?) @@ -185,7 +184,6 @@ def typecheck *files filecount = 0 time = Benchmark.measure { files.each do |file| - # @sg-ignore Unresolved call to options checker = TypeChecker.new(file, api_map: api_map, rules: rules, level: options[:level].to_sym) problems = checker.problems next if problems.empty? diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 871f3ed5f..5fdcb9fe6 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -70,9 +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) @@ -247,9 +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| diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 301bd393c..a3cd0b575 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -505,7 +505,6 @@ def kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pi unless ptype.undefined? # @type [ComplexType] 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 8e0bf1a5597af30c507e4badead1c0a9e8b7bc90 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 14:09:36 -0400 Subject: [PATCH 559/930] Add spec for is_a? in a "break unless" statement --- spec/parser/flow_sensitive_typing_spec.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 50b356f40..450eaa37c 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -189,6 +189,22 @@ class Repro < ReproBase; end expect(clip.infer.to_s).to eq('Repro') end + it 'uses is_a? in a "break unless" statement in a while to refine types' do + source = Solargraph::Source.load_string(%( + class ReproBase; end + class Repro < ReproBase; end + # @type [ReproBase] + value = bar + while !is_done() + break unless value.is_a? Repro + value + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 8]) + expect(clip.infer.to_s).to eq('Repro') + end + it 'uses unless is_a? in a ".each" block to refine types' do source = Solargraph::Source.load_string(%( # @type [Array] @@ -201,6 +217,7 @@ class Repro < ReproBase; end value end ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) clip = api_map.clip_at('test.rb', [3, 6]) expect(clip.infer.to_s).to eq('Array') @@ -225,6 +242,7 @@ def verify_repro(repr, throw_the_dice) end end ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) clip = api_map.clip_at('test.rb', [4, 8]) expect(clip.infer.rooted_tags).to eq('::Integer, nil') @@ -247,6 +265,7 @@ class Repro < ReproBase; end value end ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) clip = api_map.clip_at('test.rb', [7, 8]) expect(clip.infer.to_s).to eq('ReproBase') @@ -263,6 +282,7 @@ class Repro < ReproBase; end value end ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) clip = api_map.clip_at('test.rb', [7, 8]) expect(clip.infer.to_s).to eq('ReproBase') @@ -279,6 +299,7 @@ def baz; end bar = Foo.new bar ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) clip = api_map.clip_at('test.rb', [6, 6]) expect(clip.infer.to_s).to eq('Foo') From 5cdedb8d70231e089e4cb1072fba2f9a4ec6b496 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 14:14:29 -0400 Subject: [PATCH 560/930] Fix merge issue --- spec/parser/flow_sensitive_typing_spec.rb | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 450eaa37c..6953656b8 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -189,22 +189,6 @@ class Repro < ReproBase; end expect(clip.infer.to_s).to eq('Repro') end - it 'uses is_a? in a "break unless" statement in a while to refine types' do - source = Solargraph::Source.load_string(%( - class ReproBase; end - class Repro < ReproBase; end - # @type [ReproBase] - value = bar - while !is_done() - break unless value.is_a? Repro - value - end - ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [7, 8]) - expect(clip.infer.to_s).to eq('Repro') - end - it 'uses unless is_a? in a ".each" block to refine types' do source = Solargraph::Source.load_string(%( # @type [Array] From 97ffc4bec848e502cf69fb3ca86a1109efd06de1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 14:24:22 -0400 Subject: [PATCH 561/930] Add missing loose_unions location --- 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 d867dcd29..deeb7f031 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -241,7 +241,7 @@ def self.load_with_cache directory, out = nil, loose_unions: true end api_map.cache_all!(out) - load(directory) + load(directory, loose_unions: loose_unions) end # @return [Array] From 60fb1cb433e80b8c26d350e1bbb71b1127a72065 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 14:24:44 -0400 Subject: [PATCH 562/930] Move to enforcement at alpha level for now --- lib/solargraph/type_checker/rules.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 7abc7c3f2..4984b3efe 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -51,7 +51,7 @@ def must_tag_or_infer? end def loose_unions? - rank < LEVELS[:strong] + rank < LEVELS[:alpha] end def validate_tags? From 2263c950c2d3af547c2ca0b7856f14c3a7eb3ced Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 15:52:28 -0400 Subject: [PATCH 563/930] Fix type issues --- .../parser_gem/node_processors/or_node.rb | 23 +++++++++++++++++++ lib/solargraph/type_checker.rb | 2 +- lib/solargraph/yardoc.rb | 1 + 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 lib/solargraph/parser/parser_gem/node_processors/or_node.rb diff --git a/lib/solargraph/parser/parser_gem/node_processors/or_node.rb b/lib/solargraph/parser/parser_gem/node_processors/or_node.rb new file mode 100644 index 000000000..78f3a087a --- /dev/null +++ b/lib/solargraph/parser/parser_gem/node_processors/or_node.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Solargraph + module Parser + module ParserGem + module NodeProcessors + class OrNode < Parser::NodeProcessor::Base + include ParserGem::NodeMethods + + def process + process_children + + # TODO: Write spec for below + + # FlowSensitiveTyping.new(locals, + # enclosing_breakable_pin, + # enclosing_compound_statement_pin).process_or(node) + end + end + end + end + end +end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index a3cd0b575..4fe36aeb5 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -103,7 +103,7 @@ def load filename, level = :normal # @param code [String] # @param filename [String, nil] # @param level [Symbol] - # @param api_map [Solargraph::ApiMap] + # @param api_map [Solargraph::ApiMap, nil] # @return [self] def load_string code, filename = nil, level = :normal, api_map: nil source = Solargraph::Source.load_string(code, filename) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 09bcd4586..0afdf1482 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -23,6 +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 + # @sg-ignore Unrecognized keyword argument chdir to Open3.capture2e 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}" } From 47c472039f3f09c6c353ee716ef4c20e7bc7fdd4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 16:05:24 -0400 Subject: [PATCH 564/930] Fix solargraph-rspec issues --- lib/solargraph/parser/parser_gem/class_methods.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index 1cc40e33b..42868010e 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -12,17 +12,17 @@ module ClassMethods # can find relevant local variables later even if this is just # a subset of the file in question # @return [Array(Parser::AST::Node, Hash{Integer => Solargraph::Parser::Snippet})] - def parse_with_comments code, filename, starting_line = 0 + def parse_with_comments code, filename = nil, starting_line = 0 node = parse(code, filename, starting_line) comments = CommentRipper.new(code, filename, 0).parse [node, comments] end # @param code [String] - # @param filename [String] + # @param filename [String, nil] # @param starting_line [Integer] # @return [Parser::AST::Node] - def parse code, filename, starting_line = 0 + def parse code, filename = nil, starting_line = 0 buffer = ::Parser::Source::Buffer.new(filename, starting_line) buffer.source = code parser.parse(buffer) From f51954aa00689264f007619d5014acc0bbe9a4c8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 16:10:59 -0400 Subject: [PATCH 565/930] Allow filename to be nil for solargraph-rspec specs --- lib/solargraph/parser/parser_gem/class_methods.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index 1cc40e33b..42868010e 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -12,17 +12,17 @@ module ClassMethods # can find relevant local variables later even if this is just # a subset of the file in question # @return [Array(Parser::AST::Node, Hash{Integer => Solargraph::Parser::Snippet})] - def parse_with_comments code, filename, starting_line = 0 + def parse_with_comments code, filename = nil, starting_line = 0 node = parse(code, filename, starting_line) comments = CommentRipper.new(code, filename, 0).parse [node, comments] end # @param code [String] - # @param filename [String] + # @param filename [String, nil] # @param starting_line [Integer] # @return [Parser::AST::Node] - def parse code, filename, starting_line = 0 + def parse code, filename = nil, starting_line = 0 buffer = ::Parser::Source::Buffer.new(filename, starting_line) buffer.source = code parser.parse(buffer) From 15142efb19f2a1b5c0b756487d971a6fa736da9f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 16:20:52 -0400 Subject: [PATCH 566/930] Blow 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 6262dd494..f473ece4e 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-10-25 - name: Update to best available RBS run: | From 0834ab1011e09795a6d00b71432a522944d9de91 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 16:52:56 -0400 Subject: [PATCH 567/930] Add pending flag --- spec/type_checker/levels/strong_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 1ae981b12..1a51a9f62 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -628,7 +628,9 @@ def baz(foo) expect(checker.problems.map(&:location).map(&:range).map(&:start)).to be_empty end - it 'accepts ivar assignments and references with no intermediate calls as safe' do + xit 'accepts ivar assignments and references with no intermediate calls as safe' do + pending 'support within flow-sensitive typing' + checker = type_checker(%( class Foo def initialize From 53ba5209411f7ec3fe518feda144b3b6baca7773 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 16:56:54 -0400 Subject: [PATCH 568/930] Typechecking fixes --- lib/solargraph/parser/parser_gem/class_methods.rb | 2 +- lib/solargraph/yardoc.rb | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index bec0fdaf6..eac4e7f27 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -7,7 +7,7 @@ module Parser module ParserGem module ClassMethods # @param code [String] - # @param filename [String] + # @param filename [String, nil] # @param starting_line [Integer] must be provided so that we # can find relevant local variables later even if this is just # a subset of the file in question diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 5ffaa13e1..a4e5c2ab4 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -23,7 +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 Unrecognized keyword argument chdir to Open3.capture2e 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}" } From 3f895fd20828429591dce5632d01e945790e35a6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 5 Oct 2025 16:39:23 -0400 Subject: [PATCH 569/930] Use appraisal gem while running solargraph-rspec specs This change matches the change in https://github.com/lekemula/solargraph-rspec/pull/27 that now requires use of the appraisal gem to run soalrgraph-rspec's specs --- .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 796932d58..83bcba5a6 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -129,6 +129,7 @@ jobs: echo "gem 'solargraph', path: '../solargraph'" >> Gemfile bundle config path ${{ env.BUNDLE_PATH }} bundle install --jobs 4 --retry 3 + bundle exec appraisal install - name: Configure .solargraph.yml run: | cd ../solargraph-rspec @@ -137,11 +138,11 @@ jobs: run: | cd ../solargraph-rspec rspec_gems=$(bundle exec ruby -r './lib/solargraph-rspec' -e 'puts Solargraph::Rspec::Gems.gem_names.join(" ")' 2>/dev/null | tail -n1) - bundle exec solargraph gems $rspec_gems + bundle exec appraisal solargraph gems $rspec_gems - name: Run specs run: | cd ../solargraph-rspec - bundle exec rspec --format progress + bundle exec appraisal rspec --format progress run_solargraph_rails_specs: # check out solargraph-rails as well as this project, and point the former to use the latter as a local gem From 105a2c4ede03009034f0fc352e917d4a8e6843c2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 5 Oct 2025 16:39:23 -0400 Subject: [PATCH 570/930] Use appraisal gem while running solargraph-rspec specs This change matches the change in https://github.com/lekemula/solargraph-rspec/pull/27 that now requires use of the appraisal gem to run soalrgraph-rspec's specs --- .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 1c633fda0..cece27ae9 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -129,6 +129,7 @@ jobs: echo "gem 'solargraph', path: '../solargraph'" >> Gemfile bundle config path ${{ env.BUNDLE_PATH }} bundle install --jobs 4 --retry 3 + bundle exec appraisal install - name: Configure .solargraph.yml run: | cd ../solargraph-rspec @@ -137,11 +138,11 @@ jobs: run: | cd ../solargraph-rspec rspec_gems=$(bundle exec ruby -r './lib/solargraph-rspec' -e 'puts Solargraph::Rspec::Gems.gem_names.join(" ")' 2>/dev/null | tail -n1) - bundle exec solargraph gems $rspec_gems + bundle exec appraisal solargraph gems $rspec_gems - name: Run specs run: | cd ../solargraph-rspec - bundle exec rspec --format progress + bundle exec appraisal rspec --format progress run_solargraph_rails_specs: # check out solargraph-rails as well as this project, and point the former to use the latter as a local gem From d3c0185512a13d3d1f164522825ba2864e0dcdbd Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 5 Oct 2025 16:39:23 -0400 Subject: [PATCH 571/930] Use appraisal gem while running solargraph-rspec specs This change matches the change in https://github.com/lekemula/solargraph-rspec/pull/27 that now requires use of the appraisal gem to run soalrgraph-rspec's specs --- .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 1c633fda0..cece27ae9 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -129,6 +129,7 @@ jobs: echo "gem 'solargraph', path: '../solargraph'" >> Gemfile bundle config path ${{ env.BUNDLE_PATH }} bundle install --jobs 4 --retry 3 + bundle exec appraisal install - name: Configure .solargraph.yml run: | cd ../solargraph-rspec @@ -137,11 +138,11 @@ jobs: run: | cd ../solargraph-rspec rspec_gems=$(bundle exec ruby -r './lib/solargraph-rspec' -e 'puts Solargraph::Rspec::Gems.gem_names.join(" ")' 2>/dev/null | tail -n1) - bundle exec solargraph gems $rspec_gems + bundle exec appraisal solargraph gems $rspec_gems - name: Run specs run: | cd ../solargraph-rspec - bundle exec rspec --format progress + bundle exec appraisal rspec --format progress run_solargraph_rails_specs: # check out solargraph-rails as well as this project, and point the former to use the latter as a local gem From e376dc543cb793aa1f166ba1cb9850a1e984b174 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 5 Oct 2025 16:39:23 -0400 Subject: [PATCH 572/930] Use appraisal gem while running solargraph-rspec specs This change matches the change in https://github.com/lekemula/solargraph-rspec/pull/27 that now requires use of the appraisal gem to run soalrgraph-rspec's specs --- .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 1c633fda0..cece27ae9 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -129,6 +129,7 @@ jobs: echo "gem 'solargraph', path: '../solargraph'" >> Gemfile bundle config path ${{ env.BUNDLE_PATH }} bundle install --jobs 4 --retry 3 + bundle exec appraisal install - name: Configure .solargraph.yml run: | cd ../solargraph-rspec @@ -137,11 +138,11 @@ jobs: run: | cd ../solargraph-rspec rspec_gems=$(bundle exec ruby -r './lib/solargraph-rspec' -e 'puts Solargraph::Rspec::Gems.gem_names.join(" ")' 2>/dev/null | tail -n1) - bundle exec solargraph gems $rspec_gems + bundle exec appraisal solargraph gems $rspec_gems - name: Run specs run: | cd ../solargraph-rspec - bundle exec rspec --format progress + bundle exec appraisal rspec --format progress run_solargraph_rails_specs: # check out solargraph-rails as well as this project, and point the former to use the latter as a local gem From a3ed92e4f7a61b66143a7d901003b73aa6c7f0be Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 17:34:03 -0400 Subject: [PATCH 573/930] Spec updates --- spec/type_checker/levels/alpha_spec.rb | 53 ++++++++++++++++++++++++ spec/type_checker/levels/strong_spec.rb | 55 ------------------------- 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 824810038..1eabfba1e 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -71,5 +71,58 @@ def catalog expect(checker.problems.map(&:message)).to eq([]) end + + it 'accepts ivar assignments and references with no intermediate calls as safe' do + pending 'flow-sensitive typing improvements' + + checker = type_checker(%( + class Foo + def initialize + # @type [Integer, nil] + @foo = nil + end + + # @return [void] + def twiddle + @foo = nil if rand if rand > 0.5 + end + + # @return [Integer] + def bar + @foo = 123 + out = @foo.round + twiddle + out + end + end + )) + + expect(checker.problems.map(&:message)).to be_empty + end + + it 'knows that ivar references with intermediate calls are not safe' do + checker = type_checker(%( + class Foo + def initialize + # @type [Integer, nil] + @foo = nil + end + + # @return [void] + def twiddle + @foo = nil if rand if rand > 0.5 + end + + # @return [Integer] + def bar + @foo = 123 + twiddle + @foo.round + end + end + )) + + expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round"]) + end end end diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 1a51a9f62..d66c74e83 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -627,60 +627,5 @@ def baz(foo) expect(checker.problems.map(&:location).map(&:range).map(&:start)).to be_empty end - - xit 'accepts ivar assignments and references with no intermediate calls as safe' do - pending 'support within flow-sensitive typing' - - checker = type_checker(%( - class Foo - def initialize - # @type [Integer, nil] - @foo = nil - end - - # @return [void] - def twiddle - @foo = nil if rand if rand > 0.5 - end - - # @return [Integer] - def bar - @foo = 123 - out = @foo.round - twiddle - out - end - end - )) - - pending 'flow-sensitive typing improvements' - - expect(checker.problems.map(&:message)).to be_empty - end - - it 'knows that ivar references with intermediate calls are not safe' do - checker = type_checker(%( - class Foo - def initialize - # @type [Integer, nil] - @foo = nil - end - - # @return [void] - def twiddle - @foo = nil if rand if rand > 0.5 - end - - # @return [Integer] - def bar - @foo = 123 - twiddle - @foo.round - end - end - )) - - expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round"]) - end end end From 4baadb502974a09245c104faee410edb53098bcc Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 17:40:16 -0400 Subject: [PATCH 574/930] Keep typechecking Ruby versions the same --- .github/workflows/plugins.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index cece27ae9..b2d458ef8 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -23,7 +23,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: '3.4' # keep same as typecheck.yml bundler-cache: true - uses: awalsh128/cache-apt-pkgs-action@latest with: @@ -43,7 +43,7 @@ jobs: - name: Install gem types run: bundle exec rbs collection update - name: Ensure typechecking still works - run: bundle exec solargraph typecheck --level typed + run: bundle exec solargraph typecheck --level strong - name: Ensure specs still run run: bundle exec rake spec rails: @@ -54,7 +54,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: '3.4' # keep same as typecheck.yml bundler-cache: false - uses: awalsh128/cache-apt-pkgs-action@latest with: @@ -72,7 +72,7 @@ jobs: - name: Install gem types run: bundle exec rbs collection update - name: Ensure typechecking still works - run: bundle exec solargraph typecheck --level typed + run: bundle exec solargraph typecheck --level strong - name: Ensure specs still run run: bundle exec rake spec rspec: @@ -83,7 +83,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: '3.4' # keep same as typecheck.yml bundler-cache: false - uses: awalsh128/cache-apt-pkgs-action@latest with: @@ -101,7 +101,7 @@ jobs: - name: Install gem types run: bundle exec rbs collection update - name: Ensure typechecking still works - run: bundle exec solargraph typecheck --level typed + run: bundle exec solargraph typecheck --level strong - name: Ensure specs still run run: bundle exec rake spec From da508cb3fc1a916ef694b35d610cfd4a1bd78479 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 20:27:55 -0400 Subject: [PATCH 575/930] Annotation fixes --- lib/solargraph/api_map.rb | 3 +++ lib/solargraph/pin_cache.rb | 2 ++ lib/solargraph/source/chain/call.rb | 1 + lib/solargraph/source/change.rb | 1 + 4 files changed, 7 insertions(+) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index eaa64b756..9b826fc97 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -658,8 +658,11 @@ def super_and_sub?(sup, sub) # @todo If two literals are different values of the same type, it would # make more sense for super_and_sub? to return true, but there are a # few callers that currently expect this to be false. + # @sg-ignore We should understand reassignment of variable to new type return false if sup.literal? && sub.literal? && sup.to_s != sub.to_s + # @sg-ignore We should understand reassignment of variable to new type sup = sup.simplify_literals.to_s + # @sg-ignore We should understand reassignment of variable to new type sub = sub.simplify_literals.to_s return true if sup == sub sc_fqns = sub diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index b3c162a15..2fa48d0fa 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -219,6 +219,7 @@ def save file, pins end # @param path_segments [Array] + # @param out [IO, nil] # @return [void] def uncache *path_segments, out: nil path = File.join(*path_segments) @@ -229,6 +230,7 @@ def uncache *path_segments, out: nil end # @return [void] + # @param out [IO, nil] # @param path_segments [Array] def uncache_by_prefix *path_segments, out: nil path = File.join(*path_segments) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index bd8382851..0688643b4 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -242,6 +242,7 @@ def extra_return_type docstring, context def find_method_pin(name_pin) method_pin = name_pin until method_pin.is_a?(Pin::Method) + # @sg-ignore Reassignment as a function of itself issue method_pin = method_pin.closure return if method_pin.nil? end diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index 65c47c7e0..2f6d6ea17 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -61,6 +61,7 @@ def repair text off = Position.to_offset(text, range.start) match = result[0, off].match(/[.:]+\z/) if match + # @sg-ignore Reassignment as a function of itself issue result = result[0, off].sub(/#{match[0]}\z/, ' ' * match[0].length) + result[off..-1] end result From 6ba610023ad0c4858a3130d29fd087d2905f93e2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 20:42:11 -0400 Subject: [PATCH 576/930] Annotation fixes --- lib/solargraph/api_map/store.rb | 4 ++++ lib/solargraph/language_server/host.rb | 1 + lib/solargraph/language_server/host/sources.rb | 1 + lib/solargraph/pin/closure.rb | 2 ++ lib/solargraph/pin/common.rb | 2 ++ lib/solargraph/pin/local_variable.rb | 1 + lib/solargraph/source_map.rb | 2 ++ lib/solargraph/source_map/mapper.rb | 4 ++-- lib/solargraph/workspace.rb | 1 + lib/solargraph/workspace/config.rb | 1 + lib/solargraph/workspace/require_paths.rb | 1 + 11 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index d52bbf71a..b43ae580e 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -240,9 +240,13 @@ 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? + # @param ref [String] refs.map(&:type).map(&:to_s).each do |ref| + # @sg-ignore We should understand reassignment of variable to new type next if ref.nil? || ref.empty? || visited.include?(ref) + # @sg-ignore We should understand reassignment of variable to new type ancestors << ref + # @sg-ignore We should understand reassignment of variable to new type queue << ref end end diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index 53da20175..8ac9f7b22 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -300,6 +300,7 @@ def prepare directory, name = nil end end + # @sg-ignore Need to validate config # @return [String] def command_path options['commandPath'] || 'solargraph' diff --git a/lib/solargraph/language_server/host/sources.rb b/lib/solargraph/language_server/host/sources.rb index da0c63b93..01aa47ad4 100644 --- a/lib/solargraph/language_server/host/sources.rb +++ b/lib/solargraph/language_server/host/sources.rb @@ -55,6 +55,7 @@ def update uri, updater # @raise [FileNotFoundError] if the URI does not match an open source. # # @param uri [String] + # @sg-ignore Need a better type for 'raise' # @return [Solargraph::Source] def find uri open_source_hash[uri] || raise(Solargraph::FileNotFoundError, "Host could not find #{uri}") diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index a7b37e01b..b0b42108f 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -44,6 +44,8 @@ def context end end + # @sg-ignore Solargraph::Pin::Closure#binder return type could not be inferred + # @return [Solargraph::ComplexType] def binder @binder || context end diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 062099ee4..8129490e5 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -40,6 +40,8 @@ def namespace context.namespace.to_s end + # @sg-ignore Solargraph::Pin::Common#binder return type could + # not be inferred # @return [ComplexType] def binder @binder || context diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 2203e5dd5..0581aefe2 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -147,6 +147,7 @@ def probe api_map private + # @return [ComplexType, nil] attr_reader :exclude_return_type # @param tag1 [String] diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 895e6d6b8..f36bb5624 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -34,6 +34,8 @@ def locals # @param source [Source] def initialize source @source = source + # @type [Array, nil] + @convention_pins = nil conventions_environ.merge Convention.for_local(self) unless filename.nil? # FIXME: unmemoizing the document_symbols in case it was called and memoized from any of conventions above diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 5fdcb9fe6..0280ace8d 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -17,7 +17,7 @@ class Mapper # Generate the data. # # @param source [Source] - # @return [Array] + # @return [Array(Array, Array)] def map source @source = source @filename = source.filename @@ -46,7 +46,7 @@ def unmap filename, code class << self # @param source [Source] - # @return [Array] + # @return [Array(Array, Array)] def map source return new.unmap(source.filename, source.code) unless source.parsed? new.map source diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 07cf26f09..691d9b78d 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -137,6 +137,7 @@ def synchronize! updater source_hash[updater.filename] = source_hash[updater.filename].synchronize(updater) end + # @sg-ignore Need to validate config # @return [String] def command_path server['commandPath'] || 'solargraph' diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 3e30e5d74..32018a587 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -78,6 +78,7 @@ def required # An array of load paths for required paths. # + # @sg-ignore Need to validate config # @return [Array] def require_paths raw_data['require_paths'] || [] diff --git a/lib/solargraph/workspace/require_paths.rb b/lib/solargraph/workspace/require_paths.rb index c8eea161b..10dce4053 100644 --- a/lib/solargraph/workspace/require_paths.rb +++ b/lib/solargraph/workspace/require_paths.rb @@ -83,6 +83,7 @@ def require_path_from_gemspec_file gemspec_file_path return [] if hash.empty? hash['paths'].map { |path| File.join(base, path) } rescue StandardError => e + # @sg-ignore Should handle redefinition of types in simple contexts Solargraph.logger.warn "Error reading #{gemspec_file_path}: [#{e.class}] #{e.message}" [] end From d67294bff80ac5c33dd481f2c826969af5f62def Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 20:45:43 -0400 Subject: [PATCH 577/930] Restore --- lib/solargraph/pin/delegated_method.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index 2474ffe2c..d430ccc1b 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -78,7 +78,8 @@ def resolve_method api_map unless resolver # @sg-ignore Need to add nil check here - msg = Solargraph.logger.warn "Delegated receiver for #{path} was resolved to nil from `#{print_chain(@receiver_chain)}'" + Solargraph.logger.warn \ + "Delegated receiver for #{path} was resolved to nil from `#{print_chain(@receiver_chain)}'" return end From 47815235bfbdb691e24ad88532152a382911d986 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 20:50:28 -0400 Subject: [PATCH 578/930] Fix merge issue --- lib/solargraph/source/chain/call.rb | 1 + lib/solargraph/source_map/clip.rb | 10 ---------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 0a2e6a333..6f2975877 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -51,6 +51,7 @@ def with_block? def resolve api_map, name_pin, locals return super_pins(api_map, name_pin) if word == 'super' return yield_pins(api_map, name_pin) if word == 'yield' + found = api_map.var_at_location(locals, word, name_pin, location) if head? return inferred_pins([found], api_map, name_pin, locals) unless found.nil? binder = name_pin.binder diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index 861992470..aeec7e027 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -14,7 +14,6 @@ def initialize api_map, cursor closure_pin = closure # @sg-ignore Need to add nil check here closure_pin.rebind(api_map) if closure_pin.is_a?(Pin::Block) && !Solargraph::Range.from_node(closure_pin.receiver).contain?(cursor.range.start) - @in_block = nil end # @return [Array] Relevant pins for infering the type of the Cursor's position @@ -78,15 +77,6 @@ def gates closure.gates end - # @sg-ignore need boolish support for ? methods - def in_block? - return @in_block unless @in_block.nil? - @in_block = begin - tree = cursor.source.tree_at(cursor.position.line, cursor.position.column) - Parser.is_ast_node?(tree[1]) && [:block, :ITER].include?(tree[1].type) - end - end - # @param phrase [String] # @return [Array] def translate phrase From e15c04d2a013be780059e485113033bd5a2d3f23 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 21:29:44 -0400 Subject: [PATCH 579/930] Fix issue with &. --- lib/solargraph/complex_type.rb | 4 +++- lib/solargraph/source/chain/call.rb | 10 +++++++++- spec/type_checker/levels/alpha_spec.rb | 13 +++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index e6e504bf3..3d1598c44 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -311,7 +311,9 @@ def nullable? # @return [ComplexType] def without_nil - ComplexType.new(@items.reject(&:nil_type?)) + new_items = @items.reject(&:nil_type?) + return ComplexType::UNDEFINED if new_items.empty? + ComplexType.new(new_items) end # @return [Array] diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index fc00450c0..8cdefbbec 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -53,7 +53,15 @@ def resolve api_map, name_pin, locals found = api_map.var_at_location(locals, word, name_pin, location) if head? return inferred_pins([found], api_map, name_pin, locals) unless found.nil? - pin_groups = name_pin.binder.each_unique_type.map do |context| + binder = name_pin.binder + # this is a q_call - i.e., foo&.bar - assume result of call + # will be nil or result as if binder were not nil - + # chain.rb#maybe_nil will add the nil type later, we just + # need to worry about the not-nil case + + # @sg-ignore Need to handle duck-typed method calls on union types + binder = binder.without_nil if nullable? + pin_groups = binder.each_unique_type.map do |context| ns_tag = context.namespace == '' ? '' : context.namespace_type.tag stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) [stack.first].compact diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 1eabfba1e..cdba7bc72 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -124,5 +124,18 @@ def bar expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round"]) end + + it 'understands &. in return position' do + checker = type_checker(%( + class Baz + # @param bar [String, nil] + # @return [String] + def foo bar + bar&.upcase || 'undefined' + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end end end From 59a3bec7aacd3d87fcd7c85eb3bdaf0aa599c934 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 21:36:53 -0400 Subject: [PATCH 580/930] Fix merge --- spec/type_checker/levels/alpha_spec.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 1eb0c01ec..cdba7bc72 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -72,19 +72,6 @@ def catalog expect(checker.problems.map(&:message)).to eq([]) end - it 'understands &. in return position' do - checker = type_checker(%( - class Baz - # @param bar [String, nil] - # @return [String] - def foo bar - bar&.upcase || 'undefined' - end - end - )) - expect(checker.problems.map(&:message)).to be_empty - end - it 'accepts ivar assignments and references with no intermediate calls as safe' do pending 'flow-sensitive typing improvements' From c03d238cd401fc50e554a3e68dd70cbd671f9322 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 21:40:33 -0400 Subject: [PATCH 581/930] Fix merge --- spec/parser/flow_sensitive_typing_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index b91237d2c..df6d63b65 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -197,6 +197,22 @@ class Repro < ReproBase; end expect(clip.infer.to_s).to eq('Repro') end + it 'uses is_a? in a "break unless" statement in a while to refine types' do + source = Solargraph::Source.load_string(%( + class ReproBase; end + class Repro < ReproBase; end + # @type [ReproBase] + value = bar + while !is_done() + break unless value.is_a? Repro + value + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 8]) + expect(clip.infer.to_s).to eq('Repro') + end + it 'uses unless is_a? in a ".each" block to refine types' do source = Solargraph::Source.load_string(%( # @type [Array] From 7a60720c4c3270d01d755a8f5fcf7b622b8b812c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 21:43:07 -0400 Subject: [PATCH 582/930] Drop debugging code --- lib/solargraph/workspace/config.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 247f10c85..32018a587 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -120,9 +120,8 @@ def max_files # @return [String] def global_config_path - out = ENV['SOLARGRAPH_GLOBAL_CONFIG'] || - File.join(Dir.home, '.config', 'solargraph', 'config.yml') - out + ENV['SOLARGRAPH_GLOBAL_CONFIG'] || + File.join(Dir.home, '.config', 'solargraph', 'config.yml') end # @return [String] From cf9a72a484ccaacfcfd9f816ad2442e1afffc935 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 25 Oct 2025 21:46:33 -0400 Subject: [PATCH 583/930] Drop accidental file add --- .../parser_gem/node_processors/or_node.rb | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 lib/solargraph/parser/parser_gem/node_processors/or_node.rb diff --git a/lib/solargraph/parser/parser_gem/node_processors/or_node.rb b/lib/solargraph/parser/parser_gem/node_processors/or_node.rb deleted file mode 100644 index 78f3a087a..000000000 --- a/lib/solargraph/parser/parser_gem/node_processors/or_node.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module Solargraph - module Parser - module ParserGem - module NodeProcessors - class OrNode < Parser::NodeProcessor::Base - include ParserGem::NodeMethods - - def process - process_children - - # TODO: Write spec for below - - # FlowSensitiveTyping.new(locals, - # enclosing_breakable_pin, - # enclosing_compound_statement_pin).process_or(node) - end - end - end - end - end -end From f699970a5b2b119cfb23eac57048438e2461ec8f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 10:05:00 -0400 Subject: [PATCH 584/930] Standardize on Ruby 3.4 for typechecking --- lib/solargraph/pin/delegated_method.rb | 3 +-- lib/solargraph/pin_cache.rb | 1 - lib/solargraph/rbs_map.rb | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index d430ccc1b..738bb45a4 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -78,8 +78,7 @@ def resolve_method api_map unless resolver # @sg-ignore Need to add nil check here - Solargraph.logger.warn \ - "Delegated receiver for #{path} was resolved to nil from `#{print_chain(@receiver_chain)}'" + Solargraph.logger.warn "Delegated receiver for #{path} was resolved to nil from `#{print_chain(@receiver_chain)}'" return end diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index e5d17d456..402ae7737 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -226,7 +226,6 @@ def uncache *path_segments, out: nil path = File.join(*path_segments) if File.exist?(path) FileUtils.rm_rf path, secure: true - # @sg-ignore Need to add nil check here out.puts "Clearing pin cache in #{path}" unless out.nil? end end diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index 2ed6a5ab3..d4bb55cd4 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -160,9 +160,7 @@ def add_library loader, library, version end # @return [String] - # @sg-ignore Need to add nil check here def short_name - # @sg-ignore Need to add nil check here self.class.name.split('::').last end end From 3f471133d31e2d4a39c94cc6b2927ce1da4d6c35 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 10:06:16 -0400 Subject: [PATCH 585/930] Standardize on Ruby 3.4 for typechecking --- lib/solargraph/source_map/data.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/solargraph/source_map/data.rb b/lib/solargraph/source_map/data.rb index f96d2c6b2..4cda3630e 100644 --- a/lib/solargraph/source_map/data.rb +++ b/lib/solargraph/source_map/data.rb @@ -8,11 +8,10 @@ def initialize source @source = source # @type [Array, nil] @pins = nil - # @return [Array, nil] + # @type [Array, nil] @locals = nil end - # @sg-ignore flow sensitive typing needs to handle ivars # @return [Array] def pins generate @@ -21,7 +20,6 @@ def pins @pins || empty_pins end - # @sg-ignore flow sensitive typing needs to handle ivars # @return [Array] def locals generate From c56f26de1a469ad73cdc20ce9c3a75af091e9c52 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 10:09:15 -0400 Subject: [PATCH 586/930] Standardize on Ruby 3.4 for typechecking --- lib/solargraph/pin/closure.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index d40c44a26..44ef806ff 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -44,8 +44,7 @@ def context end end - # @sg-ignore Solargraph::Pin::Closure#binder return type could not be inferred - # @return [Solargraph::ComplexType] + # @return [ComplexType, ComplexType::UniqueType] def binder @binder || context end From 6ff7332f8abae849ee874138fadb4516ccb74090 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 10:21:11 -0400 Subject: [PATCH 587/930] Remove @sg-ignore --- lib/solargraph/yardoc.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 0afdf1482..09bcd4586 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -23,7 +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 Unrecognized keyword argument chdir to Open3.capture2e 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}" } From 08816d8ab6e37fec50b4b0702e98642a2d3b9b24 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 10:27:39 -0400 Subject: [PATCH 588/930] Drop unused file --- lib/solargraph/pin/if.rb | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 lib/solargraph/pin/if.rb diff --git a/lib/solargraph/pin/if.rb b/lib/solargraph/pin/if.rb deleted file mode 100644 index 35b8a9bfc..000000000 --- a/lib/solargraph/pin/if.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module Solargraph - module Pin - class If < Base - include CompoundStatementable - - # @param receiver [Parser::AST::Node, nil] - # @param node [Parser::AST::Node, nil] - # @param context [ComplexType, nil] - # @param args [::Array] - def initialize node: nil, **splat - super(**splat) - @node = node - end - end - end -end From bb6265f65a4089ea2c5999ec8141127d2959c542 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 11:20:06 -0400 Subject: [PATCH 589/930] Remove Pin::If --- .../parser/parser_gem/node_processors/if_node.rb | 9 --------- lib/solargraph/pin.rb | 1 - 2 files changed, 10 deletions(-) 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 7d346ffed..e59fa4baa 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb @@ -13,15 +13,6 @@ def process FlowSensitiveTyping.new(locals, enclosing_breakable_pin, enclosing_compound_statement_pin).process_if(node) - - pins.push Solargraph::Pin::If.new( - location: get_node_location(node), - closure: region.closure, - node: node, - comments: comments_for(node), - source: :parser, - ) - process_children region end end end diff --git a/lib/solargraph/pin.rb b/lib/solargraph/pin.rb index aa68aa4b1..016550966 100644 --- a/lib/solargraph/pin.rb +++ b/lib/solargraph/pin.rb @@ -35,7 +35,6 @@ module Pin autoload :KeywordParam, 'solargraph/pin/keyword_param' autoload :Search, 'solargraph/pin/search' autoload :Breakable, 'solargraph/pin/breakable' - autoload :If, 'solargraph/pin/if' autoload :Until, 'solargraph/pin/until' autoload :While, 'solargraph/pin/while' autoload :Callable, 'solargraph/pin/callable' From 9cbd11e3cd038f51e8a4a8edbdca5a53060707ce Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 12:16:22 -0400 Subject: [PATCH 590/930] Handle removing a type in a downcast if --- .../parser/flow_sensitive_typing.rb | 52 ++++++++++--------- spec/parser/flow_sensitive_typing_spec.rb | 2 - 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 8d81c4a00..425644aa6 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -203,18 +203,14 @@ class << self private # @param pin [Pin::LocalVariable] - # @param downcast_type_name [String, :not_nil] # @param presence [Range] + # @param downcast_type [ComplexType, nil] + # @param downcast_not_type [ComplexType, nil] # # @return [void] - def add_downcast_local(pin, downcast_type_name, presence) - return_type = if downcast_type_name == :not_nil - pin.return_type - else - ComplexType.parse(downcast_type_name) - end - exclude_return_type = downcast_type_name == :not_nil ? ComplexType::NIL : nil - + def add_downcast_local(pin, presence:, downcast_type:, downcast_not_type:) + downcast_type ||= pin.return_type if pin.return_type&.defined? + downcast_not_type ||= pin.exclude_return_type if pin.exclude_return_type&.defined? # @todo Create pin#update method new_pin = Solargraph::Pin::LocalVariable.new( presence_certain: true, @@ -224,15 +220,16 @@ def add_downcast_local(pin, downcast_type_name, presence) assignment: pin.assignment, comments: pin.comments, presence: presence, - return_type: return_type, - exclude_return_type: exclude_return_type, + return_type: downcast_type, + exclude_return_type: downcast_not_type, + presence_certain: true, source: :flow_sensitive_typing ) new_pin.reset_generated! locals.push(new_pin) end - # @param facts_by_pin [Hash{Pin::LocalVariable => Array String}>}] + # @param facts_by_pin [Hash{Pin::LocalVariable => Array ComplexType}>}] # @param presences [Array] # # @return [void] @@ -242,13 +239,13 @@ def process_facts(facts_by_pin, presences) # facts_by_pin.each_pair do |pin, facts| facts.each do |fact| - downcast_type_name = fact.fetch(:type, nil) - nilp = fact.fetch(:nil, nil) - not_nilp = fact.fetch(:not_nil, nil) + downcast_type = fact.fetch(:type, nil) + downcast_not_type = fact.fetch(:not_type, nil) presences.each do |presence| - add_downcast_local(pin, downcast_type_name, presence) unless downcast_type_name.nil? - add_downcast_local(pin, 'nil', presence) if nilp == true - add_downcast_local(pin, :not_nil, presence) if not_nilp == true + add_downcast_local(pin, + presence: presence, + downcast_type: downcast_type, + downcast_not_type: downcast_not_type) end end end @@ -330,15 +327,16 @@ def process_isa(isa_node, true_presences, false_presences) pin = find_local(variable_name, isa_position) return unless pin + # @type Hash{Pin::LocalVariable => Array ComplexType}>} if_true = {} if_true[pin] ||= [] - if_true[pin] << { type: isa_type_name } + if_true[pin] << { type: ComplexType.parse(isa_type_name) } process_facts(if_true, true_presences) + # @type Hash{Pin::LocalVariable => Array ComplexType}>} if_false = {} if_false[pin] ||= [] - # @todo: should add support for not_type - # if_true[pin] << { not_type: isa_type_name } + if_false[pin] << { not_type: ComplexType.parse(isa_type_name) } process_facts(if_false, false_presences) end @@ -366,14 +364,16 @@ def process_nilp(nilp_node, true_presences, false_presences) pin = find_local(variable_name, nilp_position) return unless pin + # @type Hash{Pin::LocalVariable => Array ComplexType}>} if_true = {} if_true[pin] ||= [] - if_true[pin] << { nil: true } + if_true[pin] << { type: ComplexType::NIL } process_facts(if_true, true_presences) + # @type Hash{Pin::LocalVariable => Array ComplexType}>} if_false = {} if_false[pin] ||= [] - if_false[pin] << { not_nil: true } + if_false[pin] << { not_type: ComplexType::NIL } process_facts(if_false, false_presences) end @@ -426,14 +426,16 @@ def process_variable(node, true_presences, false_presences) pin = find_local(variable_name, var_position) return unless pin + # @type Hash{Pin::LocalVariable => Array ComplexType}>} if_true = {} if_true[pin] ||= [] - if_true[pin] << { not_nil: true } + if_true[pin] << { not_type: ComplexType::NIL } process_facts(if_true, true_presences) + # @type Hash{Pin::LocalVariable => Array ComplexType}>} if_false = {} if_false[pin] ||= [] - if_false[pin] << { nil: true } + if_false[pin] << { type: ComplexType::NIL } process_facts(if_false, false_presences) end diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index b8c7be6e7..85655fb90 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -38,8 +38,6 @@ def verify_repro(repr) end end ), 'test.rb') - pending 'FlowSensitiveTyping improvements' - api_map = Solargraph::ApiMap.new.map(source) clip = api_map.clip_at('test.rb', [7, 10]) expect(clip.infer.to_s).to eq('Repro1') From 4139eca26aa173701150199a1b03c0c0c55c353b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 12:30:18 -0400 Subject: [PATCH 591/930] Fix RuboCop issue --- lib/solargraph/parser/flow_sensitive_typing.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 425644aa6..ac25963ce 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -213,7 +213,6 @@ def add_downcast_local(pin, presence:, downcast_type:, downcast_not_type:) downcast_not_type ||= pin.exclude_return_type if pin.exclude_return_type&.defined? # @todo Create pin#update method new_pin = Solargraph::Pin::LocalVariable.new( - presence_certain: true, location: pin.location, closure: pin.closure, name: pin.name, From aaa2e7821fb823b6f135c05df62abe32c617a9be Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 13:13:19 -0400 Subject: [PATCH 592/930] Add spec for change --- spec/type_checker/levels/alpha_spec.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index cdba7bc72..52fcf4843 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -137,5 +137,25 @@ def foo bar )) expect(checker.problems.map(&:message)).to be_empty end + + it 'can infer types based on || and &&' do + checker = type_checker(%( + class Baz + # @param bar [String, nil] + # @return [Boolean, String] + def foo bar + !bar || bar.upcase + end + + # @param bar [String, nil] + # @return [String, nil] + def bing bar + bar && bar.upcase + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end end end From 4e73d5de9f339aa2044aa323bcdbba153f2c7104 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 13:37:20 -0400 Subject: [PATCH 593/930] Drop unneeded .rooted_tags --- 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 91c456871..58ed5a009 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -344,7 +344,7 @@ def call_problems # @sg-ignore Need to add nil check here unless closest.generic? || ignored_pins.include?(found) if closest.defined? - result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest.rooted_tags}") + 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 From 6f31d6852d7cf49299c77dffd5957087b5ecc44c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 13:40:24 -0400 Subject: [PATCH 594/930] Drop unneeded change --- lib/solargraph/source_map/mapper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 67a38c38a..4202fbb4f 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -24,8 +24,8 @@ def map source @code = source.code @comments = source.comments @pins, @locals = Parser.map(source) - @pins.each { |p| p.source ||= :code } - @locals.each { |l| l.source ||= :code } + @pins.each { |p| p.source = :code } + @locals.each { |l| l.source = :code } process_comment_directives [@pins, @locals] # rescue Exception => e From 12691f0e910148272f64e31d255f88bd4554041f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 13:41:57 -0400 Subject: [PATCH 595/930] Drop unneeded change --- lib/solargraph/source_map/data.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/solargraph/source_map/data.rb b/lib/solargraph/source_map/data.rb index 4cda3630e..113b0a1f2 100644 --- a/lib/solargraph/source_map/data.rb +++ b/lib/solargraph/source_map/data.rb @@ -15,16 +15,12 @@ def initialize source # @return [Array] def pins generate - # @type [Array] - empty_pins = [] - @pins || empty_pins + @pins || [] end # @return [Array] def locals generate - # @type [Array] - empty_locals = [] @locals || [] end From 174076adba9418830854f87bb1c7b901e18d397f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 14:10:22 -0400 Subject: [PATCH 596/930] Drop unneeded code --- lib/solargraph/complex_type/unique_type.rb | 8 ++++++-- lib/solargraph/type_checker/rules.rb | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index a1df14a98..0eebd50c2 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -274,7 +274,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? @@ -342,9 +342,12 @@ def downcast_to_literal_if_possible def resolve_generics_from_context generics_to_resolve, context_type, resolved_generic_values: {} if name == ComplexType::GENERIC_TAG_NAME type_param = subtypes.first&.name - return self unless type_param && generics_to_resolve.include?(type_param) + # @sg-ignore flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) + return self unless generics_to_resolve.include?(type_param) + # @sg-ignore flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) unless context_type.nil? || !resolved_generic_values[type_param].nil? new_binding = true + # @sg-ignore flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) resolved_generic_values[type_param] = context_type end if new_binding @@ -352,6 +355,7 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge complex_type.resolve_generics_from_context(generics_to_resolve, nil, resolved_generic_values: resolved_generic_values) end end + # @sg-ignore flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) return resolved_generic_values[type_param] || self end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 307ce43ed..31831db57 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -60,7 +60,7 @@ def require_inferred_type_params? # pending code fixes (~280): # - # @todo 267: Need to add nil check here + # @todo 264: Need to add nil check here # @todo 9: Need to validate config # @todo 3: Translate to something flow sensitive typing understands # @todo 2: Need a downcast here @@ -70,6 +70,7 @@ def require_inferred_type_params? # @todo 51 flow sensitive typing needs to handle ivars # @todo 9: Should handle redefinition of types in simple contexts # @todo 6: need boolish support for ? methods + # @todo 4: flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) # @todo 4: (*) flow sensitive typing needs better handling of ||= on lvars # @todo 4: Need to look at Tuple#include? handling # @todo 4: literal arrays in this module turn into ::Solargraph::Source::Chain::Array @@ -87,7 +88,6 @@ def require_inferred_type_params? # @todo 1: ComplexType::UniqueType needs without_nil # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' # @todo 1: flow sensitive typing needs to eliminate literal from union with return if foo == :bar - # @todo 1: flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) # @todo 1: Flow sensitive typing could figure out this is not nil at both entrypoints def require_all_unique_types_match_expected? rank >= LEVELS[:strong] From 664613ba56e3273b9a2c675f53fa2d761ca1d927 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 14:12:14 -0400 Subject: [PATCH 597/930] Drop unneeded code --- lib/solargraph/complex_type/unique_type.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 0eebd50c2..3a4cf60f3 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -343,7 +343,7 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge if name == ComplexType::GENERIC_TAG_NAME type_param = subtypes.first&.name # @sg-ignore flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) - return self unless generics_to_resolve.include?(type_param) + return self unless generics_to_resolve.include? type_param # @sg-ignore flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) unless context_type.nil? || !resolved_generic_values[type_param].nil? new_binding = true From 27cd225ed18073c8fe1e50e22cdd4d03339803aa Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 14:18:15 -0400 Subject: [PATCH 598/930] Ensure nullable? is on both UniqueType and ComplexType --- lib/solargraph/complex_type/unique_type.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 6b941d76d..68dafcb5f 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -248,6 +248,10 @@ def can_assign?(api_map, atype) out end + def nullable? + nil_type? + end + # @return [UniqueType] def downcast_to_literal_if_possible SINGLE_SUBTYPE.fetch(rooted_tag, self) From 168880d14dd11cd60874cad366ef726b391a03aa Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 14:26:10 -0400 Subject: [PATCH 599/930] Drop unneeded code --- 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 58ed5a009..ce47a8bd0 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -234,7 +234,8 @@ def ignored_pins def variable_type_tag_problems result = [] all_variables.each do |pin| - if pin.return_type&.defined? + # @sg-ignore Need to add nil check here + if pin.return_type.defined? declared = pin.typify(api_map) next if declared.duck_type? if declared.defined? From 77d72f3e4907bd68c0630129b8dde70c30ef2401 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 26 Oct 2025 14:32:11 -0400 Subject: [PATCH 600/930] Drop unneeded change --- lib/solargraph/source_map/data.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/solargraph/source_map/data.rb b/lib/solargraph/source_map/data.rb index 113b0a1f2..28d3d3a69 100644 --- a/lib/solargraph/source_map/data.rb +++ b/lib/solargraph/source_map/data.rb @@ -6,10 +6,6 @@ class Data # @param source [Solargraph::Source] def initialize source @source = source - # @type [Array, nil] - @pins = nil - # @type [Array, nil] - @locals = nil end # @return [Array] From 89cd8b98ae0b256c9e2908dddf1d42c569a86945 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 31 Oct 2025 07:29:07 -0400 Subject: [PATCH 601/930] Refactor --- lib/solargraph/parser/flow_sensitive_typing.rb | 4 ++-- lib/solargraph/parser/node_processor/base.rb | 4 ++-- .../parser/parser_gem/node_processors/begin_node.rb | 9 +++++++++ lib/solargraph/pin.rb | 4 ++-- lib/solargraph/pin/breakable.rb | 2 -- lib/solargraph/pin/closure.rb | 2 +- ...pound_statementable.rb => compound_statement.rb} | 13 +++++++++---- lib/solargraph/pin/method.rb | 1 - lib/solargraph/pin/until.rb | 2 +- lib/solargraph/pin/while.rb | 2 +- lib/solargraph/type_checker.rb | 2 +- 11 files changed, 28 insertions(+), 17 deletions(-) rename lib/solargraph/pin/{compound_statementable.rb => compound_statement.rb} (84%) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 42143d6a4..198f2bb15 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -5,7 +5,7 @@ class FlowSensitiveTyping # @param locals [Array] # @param enclosing_breakable_pin [Solargraph::Pin::Breakable, nil] - # @param enclosing_compound_statement_pin [Solargraph::Pin::CompoundStatementable, nil] + # @param enclosing_compound_statement_pin [Solargraph::Pin::CompoundStatement, nil] def initialize(locals, enclosing_breakable_pin, enclosing_compound_statement_pin) @locals = locals @enclosing_breakable_pin = enclosing_breakable_pin @@ -118,7 +118,7 @@ def process_if(if_node, true_ranges = [], false_ranges = []) end end - unless enclosing_compound_statement_pin.nil? + unless enclosing_compound_statement_pin.node.nil? rest_of_returnable_body = Range.new(get_node_end_position(if_node), get_node_end_position(enclosing_compound_statement_pin.node)) diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index 1a79bea65..594fbc1da 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -57,9 +57,9 @@ def enclosing_breakable_pin end # @sg-ignore downcast output of Enumerable#select - # @return [Solargraph::Pin::CompoundStatementable, nil] + # @return [Solargraph::Pin::CompoundStatement, nil] def enclosing_compound_statement_pin - pins.select{|pin| pin.is_a?(Pin::CompoundStatementable) && pin.location&.range&.contain?(position)}.last + pins.select{|pin| pin.is_a?(Pin::CompoundStatement) && pin.location&.range&.contain?(position)}.last end # @param subregion [Region] diff --git a/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb b/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb index b52b9d3c6..426c751f6 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb @@ -6,6 +6,15 @@ module ParserGem module NodeProcessors class BeginNode < Parser::NodeProcessor::Base def process + # Note: we intentionally don't create a CompoundStatement + # pin here, as this is not necessarily a control flow + # block - e.g., a begin...end without rescue or ensure + # should be treated by flow-sensitive typing as if the + # begin and end didn't exist at all. As such, we create + # the CompoundStatement pins around the things which + # actually result in control flow changes - like + # if/while/rescue/etc + process_children end end diff --git a/lib/solargraph/pin.rb b/lib/solargraph/pin.rb index 016550966..6cd6fcaf9 100644 --- a/lib/solargraph/pin.rb +++ b/lib/solargraph/pin.rb @@ -38,8 +38,8 @@ module Pin autoload :Until, 'solargraph/pin/until' autoload :While, 'solargraph/pin/while' autoload :Callable, 'solargraph/pin/callable' - autoload :CompoundStatementable, - 'solargraph/pin/compound_statementable' + autoload :CompoundStatement, + 'solargraph/pin/compound_statement' ROOT_PIN = Pin::Namespace.new(type: :class, name: '', closure: nil, source: :pin_rb) end diff --git a/lib/solargraph/pin/breakable.rb b/lib/solargraph/pin/breakable.rb index 5de67cf4a..7cf6df9ab 100644 --- a/lib/solargraph/pin/breakable.rb +++ b/lib/solargraph/pin/breakable.rb @@ -3,8 +3,6 @@ module Pin # Mix-in for pins which enclose code which the 'break' statement # works with-in - e.g., blocks, when, until, ... module Breakable - include CompoundStatementable - # @return [Parser::AST::Node] attr_reader :node diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index a7b37e01b..347e0229e 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -2,7 +2,7 @@ module Solargraph module Pin - class Closure < Base + class Closure < CompoundStatement # @return [::Symbol] :class or :instance attr_reader :scope diff --git a/lib/solargraph/pin/compound_statementable.rb b/lib/solargraph/pin/compound_statement.rb similarity index 84% rename from lib/solargraph/pin/compound_statementable.rb rename to lib/solargraph/pin/compound_statement.rb index debad6615..4598d677a 100644 --- a/lib/solargraph/pin/compound_statementable.rb +++ b/lib/solargraph/pin/compound_statement.rb @@ -39,12 +39,17 @@ module Pin # Just because statement #1 in a sequence is executed, it doesn't # mean that future ones will. Consider the effect of # break/next/return/raise/etc. on control flow. - module CompoundStatementable - # @return [Parser::AST::Node] + class CompoundStatement < Pin::Base attr_reader :node - # @return [Location, nil] - attr_reader :location + # @param receiver [Parser::AST::Node, nil] + # @param node [Parser::AST::Node, nil] + # @param context [ComplexType, nil] + # @param args [::Array] + def initialize node: nil, **splat + super(**splat) + @node = node + end end end end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 871708253..011f096f6 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -6,7 +6,6 @@ module Pin # class Method < Callable include Solargraph::Parser::NodeMethods - include CompoundStatementable # @return [::Symbol] :public, :private, or :protected attr_reader :visibility diff --git a/lib/solargraph/pin/until.rb b/lib/solargraph/pin/until.rb index 67823532b..7e050fea6 100644 --- a/lib/solargraph/pin/until.rb +++ b/lib/solargraph/pin/until.rb @@ -2,7 +2,7 @@ module Solargraph module Pin - class Until < Base + class Until < CompoundStatement include Breakable # @param receiver [Parser::AST::Node, nil] diff --git a/lib/solargraph/pin/while.rb b/lib/solargraph/pin/while.rb index e380aadd9..ac8c31c97 100644 --- a/lib/solargraph/pin/while.rb +++ b/lib/solargraph/pin/while.rb @@ -2,7 +2,7 @@ module Solargraph module Pin - class While < Base + class While < CompoundStatement include Breakable # @param receiver [Parser::AST::Node, nil] diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 4600767b5..1ca0bc602 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -192,6 +192,7 @@ def ignored_pins def variable_type_tag_problems result = [] all_variables.each do |pin| + # @sg-ignore Need to add nil check here if pin.return_type.defined? declared = pin.typify(api_map) next if declared.duck_type? @@ -308,7 +309,6 @@ def call_problems def argument_problems_for chain, api_map, closure_pin, locals, location result = [] base = chain - # @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) From 6b2afc996e47d303116e6542fdecbc55b7341838 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 31 Oct 2025 07:31:17 -0400 Subject: [PATCH 602/930] Fix comment style --- .../parser_gem/node_processors/begin_node.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb b/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb index 426c751f6..19e53a681 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb @@ -6,13 +6,13 @@ module ParserGem module NodeProcessors class BeginNode < Parser::NodeProcessor::Base def process - # Note: we intentionally don't create a CompoundStatement - # pin here, as this is not necessarily a control flow - # block - e.g., a begin...end without rescue or ensure - # should be treated by flow-sensitive typing as if the - # begin and end didn't exist at all. As such, we create - # the CompoundStatement pins around the things which - # actually result in control flow changes - like + # We intentionally don't create a CompoundStatement pin + # here, as this is not necessarily a control flow block - + # e.g., a begin...end without rescue or ensure should be + # treated by flow-sensitive typing as if the begin and end + # didn't exist at all. As such, we create the + # CompoundStatement pins around the things which actually + # result in control flow changes - like # if/while/rescue/etc process_children From 3d390267e69c4cf484ddfb37d2595180514f5b7e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 31 Oct 2025 07:41:11 -0400 Subject: [PATCH 603/930] Update Gemfile.lock in solargraph-rspec --- .github/workflows/plugins.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index cece27ae9..03617884a 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -94,6 +94,12 @@ jobs: echo 'gem "solargraph-rspec"' >> .Gemfile bundle install bundle update rbs + # /opt/hostedtoolcache/Ruby/3.4.7/x64/lib/ruby/3.4.0/bundler/runtime.rb:317:in + # 'Bundler::Runtime#check_for_activated_spec!': You have + # already activated date 3.5.0, but your Gemfile requires date + # 3.4.1. Prepending `bundle exec` to your command may solve + # this. (Gem::LoadError) + bundle update date - name: Configure to use plugins run: | bundle exec solargraph config From bd075c52624daf9de433746857a1850eac888b77 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 31 Oct 2025 07:43:52 -0400 Subject: [PATCH 604/930] Drop @sg-ignore --- lib/solargraph/parser/node_processor/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index 594fbc1da..76f61c5e4 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -56,7 +56,7 @@ def enclosing_breakable_pin pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location&.range&.contain?(position)}.last end - # @sg-ignore downcast output of Enumerable#select + # @todo downcast output of Enumerable#select # @return [Solargraph::Pin::CompoundStatement, nil] def enclosing_compound_statement_pin pins.select{|pin| pin.is_a?(Pin::CompoundStatement) && pin.location&.range&.contain?(position)}.last From 63196306991ff25d1baebf5eed50373713693053 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 31 Oct 2025 07:47:01 -0400 Subject: [PATCH 605/930] Update Gemfile.lock in solargraph-rspec --- .github/workflows/plugins.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 03617884a..ae8355bf7 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -94,12 +94,6 @@ jobs: echo 'gem "solargraph-rspec"' >> .Gemfile bundle install bundle update rbs - # /opt/hostedtoolcache/Ruby/3.4.7/x64/lib/ruby/3.4.0/bundler/runtime.rb:317:in - # 'Bundler::Runtime#check_for_activated_spec!': You have - # already activated date 3.5.0, but your Gemfile requires date - # 3.4.1. Prepending `bundle exec` to your command may solve - # this. (Gem::LoadError) - bundle update date - name: Configure to use plugins run: | bundle exec solargraph config @@ -135,7 +129,13 @@ jobs: echo "gem 'solargraph', path: '../solargraph'" >> Gemfile bundle config path ${{ env.BUNDLE_PATH }} bundle install --jobs 4 --retry 3 - bundle exec appraisal install + # /opt/hostedtoolcache/Ruby/3.4.7/x64/lib/ruby/3.4.0/bundler/runtime.rb:317:in + # 'Bundler::Runtime#check_for_activated_spec!': You have + # already activated date 3.5.0, but your Gemfile requires date + # 3.4.1. Prepending `bundle exec` to your command may solve + # this. (Gem::LoadError) + bundle update date + bundle exec appraisal install - name: Configure .solargraph.yml run: | cd ../solargraph-rspec @@ -143,6 +143,7 @@ jobs: - name: Solargraph generate RSpec gems YARD and RBS pins run: | cd ../solargraph-rspec + rspec_gems=$(bundle exec ruby -r './lib/solargraph-rspec' -e 'puts Solargraph::Rspec::Gems.gem_names.join(" ")' 2>/dev/null | tail -n1) bundle exec appraisal solargraph gems $rspec_gems - name: Run specs From 1b12f8f742171ac2872f6ec97e2c21eb21b6f8aa Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 31 Oct 2025 07:57:09 -0400 Subject: [PATCH 606/930] Update Gemfile.lock in solargraph-rspec --- .github/workflows/plugins.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index ae8355bf7..dffa58628 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -125,17 +125,18 @@ jobs: bundler-cache: false - name: Install gems run: | - cd ../solargraph-rspec - echo "gem 'solargraph', path: '../solargraph'" >> Gemfile - bundle config path ${{ env.BUNDLE_PATH }} - bundle install --jobs 4 --retry 3 + cd ../solargraph-rspec + echo "gem 'solargraph', path: '../solargraph'" >> Gemfile + bundle config path ${{ env.BUNDLE_PATH }} + bundle install --jobs 4 --retry 3 + bundle exec appraisal install # /opt/hostedtoolcache/Ruby/3.4.7/x64/lib/ruby/3.4.0/bundler/runtime.rb:317:in # 'Bundler::Runtime#check_for_activated_spec!': You have # already activated date 3.5.0, but your Gemfile requires date # 3.4.1. Prepending `bundle exec` to your command may solve # this. (Gem::LoadError) - bundle update date bundle exec appraisal install + bundle exec appraisal update date - name: Configure .solargraph.yml run: | cd ../solargraph-rspec From 8c5c5fb623dcd70380814aa57176b8e92d44b6d8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 31 Oct 2025 08:01:08 -0400 Subject: [PATCH 607/930] Better handling of 'return if' --- .../parser_gem/node_processors/if_node.rb | 35 +++++++++++++++++-- spec/parser/flow_sensitive_typing_spec.rb | 4 --- 2 files changed, 33 insertions(+), 6 deletions(-) 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 e59fa4baa..9b0bbd978 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb @@ -8,11 +8,42 @@ class IfNode < Parser::NodeProcessor::Base include ParserGem::NodeMethods def process - process_children - FlowSensitiveTyping.new(locals, enclosing_breakable_pin, enclosing_compound_statement_pin).process_if(node) + condition_node = node.children[0] + if condition_node + pins.push Solargraph::Pin::CompoundStatement.new( + location: get_node_location(condition_node), + closure: region.closure, + node: condition_node, + source: :parser, + ) + NodeProcessor.process(condition_node, region, pins, locals) + end + then_node = node.children[1] + if then_node + pins.push Solargraph::Pin::CompoundStatement.new( + location: get_node_location(then_node), + closure: region.closure, + node: then_node, + source: :parser, + ) + NodeProcessor.process(then_node, region, pins, locals) + end + + else_node = node.children[2] + if else_node + pins.push Solargraph::Pin::CompoundStatement.new( + location: get_node_location(else_node), + closure: region.closure, + node: else_node, + source: :parser, + ) + NodeProcessor.process(else_node, region, pins, locals) + end + + true end end end diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 6953656b8..33e29f53b 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -695,8 +695,6 @@ def bar(baz: nil) clip = api_map.clip_at('test.rb', [8, 12]) expect(clip.infer.rooted_tags).to eq('::Boolean') - pending('better scoping of return if in unless') - clip = api_map.clip_at('test.rb', [10, 10]) expect(clip.infer.rooted_tags).to eq('::Boolean, nil') end @@ -826,8 +824,6 @@ def bar(baz: nil) clip = api_map.clip_at('test.rb', [6, 44]) expect(clip.infer.rooted_tags).to eq('::Boolean') - pending('better scoping of return if in ternary operator') - clip = api_map.clip_at('test.rb', [6, 51]) expect(clip.infer.rooted_tags).to eq('::Boolean, nil') From aff5fbf55bb14f020ffaca27ef9d099dd802c578 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 31 Oct 2025 08:16:34 -0400 Subject: [PATCH 608/930] Better handling of 'case when' --- .../parser/parser_gem/node_processors.rb | 2 ++ .../parser_gem/node_processors/when_node.rb | 23 +++++++++++++++++++ spec/parser/flow_sensitive_typing_spec.rb | 2 -- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 lib/solargraph/parser/parser_gem/node_processors/when_node.rb diff --git a/lib/solargraph/parser/parser_gem/node_processors.rb b/lib/solargraph/parser/parser_gem/node_processors.rb index e2cb828da..e80970344 100644 --- a/lib/solargraph/parser/parser_gem/node_processors.rb +++ b/lib/solargraph/parser/parser_gem/node_processors.rb @@ -27,6 +27,7 @@ module NodeProcessors autoload :SymNode, 'solargraph/parser/parser_gem/node_processors/sym_node' autoload :ResbodyNode, 'solargraph/parser/parser_gem/node_processors/resbody_node' autoload :UntilNode, 'solargraph/parser/parser_gem/node_processors/until_node' + autoload :WhenNode, 'solargraph/parser/parser_gem/node_processors/when_node' autoload :WhileNode, 'solargraph/parser/parser_gem/node_processors/while_node' autoload :AndNode, 'solargraph/parser/parser_gem/node_processors/and_node' end @@ -65,6 +66,7 @@ module NodeProcessor register :until, ParserGem::NodeProcessors::UntilNode register :while, ParserGem::NodeProcessors::WhileNode register :and, ParserGem::NodeProcessors::AndNode + register :when, ParserGem::NodeProcessors::WhenNode end end end diff --git a/lib/solargraph/parser/parser_gem/node_processors/when_node.rb b/lib/solargraph/parser/parser_gem/node_processors/when_node.rb new file mode 100644 index 000000000..b2b11dec1 --- /dev/null +++ b/lib/solargraph/parser/parser_gem/node_processors/when_node.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Solargraph + module Parser + module ParserGem + module NodeProcessors + class WhenNode < Parser::NodeProcessor::Base + include ParserGem::NodeMethods + + def process + pins.push Solargraph::Pin::CompoundStatement.new( + location: get_node_location(node), + closure: region.closure, + node: node, + source: :parser, + ) + process_children + end + end + end + end + end +end diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 33e29f53b..01d263cb3 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -795,8 +795,6 @@ def bar(baz: nil) clip = api_map.clip_at('test.rb', [8, 12]) expect(clip.infer.rooted_tags).to eq('::Boolean') - pending('better scoping of return if in case/when') - clip = api_map.clip_at('test.rb', [10, 12]) expect(clip.infer.rooted_tags).to eq('::Boolean, nil') From 9bc22d4f1e6e09e759a8bec9b135472b5712e50e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 1 Nov 2025 09:36:30 -0400 Subject: [PATCH 609/930] Drop @sg-ignore --- lib/solargraph/type_checker.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 952dca7c0..29f1ebc73 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -228,7 +228,6 @@ def ignored_pins def variable_type_tag_problems result = [] all_variables.each do |pin| - # @sg-ignore Need to add nil check here if pin.return_type.defined? declared = pin.typify(api_map) next if declared.duck_type? From ba66987a91ab7ac77c973c5a3dd424fcc8020e45 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 1 Nov 2025 12:37:48 -0400 Subject: [PATCH 610/930] Support intersection types See https://en.wikipedia.org/wiki/Intersection_type --- .rubocop_todo.yml | 15 +- lib/solargraph/complex_type.rb | 34 ++++ lib/solargraph/complex_type/unique_type.rb | 41 +++- .../parser/flow_sensitive_typing.rb | 23 ++- lib/solargraph/pin/base_variable.rb | 188 +++++++++++++++++- lib/solargraph/pin/local_variable.rb | 44 ++-- lib/solargraph/pin/parameter.rb | 13 +- spec/pin/base_variable_spec.rb | 15 ++ 8 files changed, 330 insertions(+), 43 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 83339e756..bb1d0cf9f 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' @@ -1230,7 +1229,12 @@ Style/TrailingCommaInArrayLiteral: # Configuration parameters: EnforcedStyleForMultiline. # SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma Style/TrailingCommaInHashLiteral: - Enabled: false + Exclude: + - 'lib/solargraph/pin/base_variable.rb' + - 'lib/solargraph/pin/callable.rb' + - 'lib/solargraph/pin/closure.rb' + - 'lib/solargraph/pin/parameter.rb' + - 'lib/solargraph/rbs_map/conversions.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, AllowedMethods. @@ -1278,12 +1282,7 @@ YARD/MismatchName: Enabled: false YARD/TagTypeSyntax: - Exclude: - - 'lib/solargraph/api_map/constants.rb' - - '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/complex_type.rb b/lib/solargraph/complex_type.rb index 669a66900..6539ae59d 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -271,6 +271,40 @@ def rooted? @items.all?(&:rooted?) end + # @param exclude_types [ComplexType, nil] + # @param api_map [ApiMap] + # @return [ComplexType, self] + def exclude exclude_types, api_map + return self if exclude_types.nil? + + types = items - exclude_types.items + types = [ComplexType::UniqueType::UNDEFINED] if types.empty? + ComplexType.new(types) + end + + # @see https://en.wikipedia.org/wiki/Intersection_type + # + # @param intersection_type [ComplexType, ComplexType::UniqueType, nil] + # @param api_map [ApiMap] + # @return [self, ComplexType::UniqueType] + def intersect_with intersection_type, api_map + return self if intersection_type.nil? + return intersection_type if undefined? + types = [] + # try to find common types via conformance + items.each do |ut| + intersection_type.each do |int_type| + if ut.can_assign?(api_map, int_type) + types << int_type + elsif int_type.can_assign?(api_map, ut) + types << ut + end + end + end + types = [ComplexType::UniqueType::UNDEFINED] if types.empty? + ComplexType.new(types) + end + protected # @return [ComplexType] diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 05a585dcf..dc386cd24 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -109,6 +109,40 @@ def simplify_literals end end + # @param exclude_types [ComplexType, nil] + # @param api_map [ApiMap] + # @return [ComplexType, self] + def exclude exclude_types, api_map + return self if exclude_types.nil? + + types = items - exclude_types.items + types = [ComplexType::UniqueType::UNDEFINED] if types.empty? + ComplexType.new(types) + end + + # @see https://en.wikipedia.org/wiki/Intersection_type + # + # @param intersection_type [ComplexType, ComplexType::UniqueType, nil] + # @param api_map [ApiMap] + # @return [self, ComplexType] + def intersect_with intersection_type, api_map + return self if intersection_type.nil? + return intersection_type if undefined? + types = [] + # try to find common types via conformance + items.each do |ut| + intersection_type.each do |int_type| + if ut.can_assign?(api_map, int_type) + types << int_type + elsif int_type.can_assign?(api_map, ut) + types << ut + end + end + end + types = [ComplexType::UniqueType::UNDEFINED] if types.empty? + ComplexType.new(types) + end + def literal? non_literal_name != name end @@ -237,7 +271,7 @@ def generic? end # @param api_map [ApiMap] The ApiMap that performs qualification - # @param atype [ComplexType] type which may be assigned to this type + # @param atype [ComplexType, self] 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 @@ -248,6 +282,11 @@ def can_assign?(api_map, atype) out end + # @yieldreturn [Boolean] + def all? &block + block.yield self + end + # @return [UniqueType] def downcast_to_literal_if_possible SINGLE_SUBTYPE.fetch(rooted_tag, self) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 41ce6eeaf..213f8a779 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -120,11 +120,12 @@ def self.visible_pins(pins, name, closure, location) private # @param pin [Pin::LocalVariable] - # @param downcast_type_name [String] # @param presence [Range] + # @param downcast_type [ComplexType, nil] + # @param downcast_not_type [ComplexType, nil] # # @return [void] - def add_downcast_local(pin, downcast_type_name, presence) + def add_downcast_local(pin, presence:, downcast_type:, downcast_not_type:) # @todo Create pin#update method new_pin = Solargraph::Pin::LocalVariable.new( location: pin.location, @@ -133,14 +134,15 @@ def add_downcast_local(pin, downcast_type_name, presence) assignment: pin.assignment, comments: pin.comments, presence: presence, - return_type: ComplexType.try_parse(downcast_type_name), - presence_certain: true, + intersection_return_type: downcast_type, + exclude_return_type: downcast_not_type, + return_type: pin.return_type, source: :flow_sensitive_typing ) locals.push(new_pin) end - # @param facts_by_pin [Hash{Pin::LocalVariable => Array String}>}] + # @param facts_by_pin [Hash{Pin::LocalVariable => Array ComplexType}>}] # @param presences [Array] # # @return [void] @@ -150,9 +152,13 @@ def process_facts(facts_by_pin, presences) # facts_by_pin.each_pair do |pin, facts| facts.each do |fact| - downcast_type_name = fact.fetch(:type) + downcast_type = fact.fetch(:type, nil) + downcast_not_type = fact.fetch(:not_type, nil) presences.each do |presence| - add_downcast_local(pin, downcast_type_name, presence) + add_downcast_local(pin, + presence: presence, + downcast_type: downcast_type, + downcast_not_type: downcast_not_type) end end end @@ -218,9 +224,10 @@ def process_isa(isa_node, true_presences) pin = find_local(variable_name, isa_position) return unless pin + # @type Hash{Pin::LocalVariable => Array ComplexType}>} if_true = {} if_true[pin] ||= [] - if_true[pin] << { type: isa_type_name } + if_true[pin] << { type: ComplexType.parse(isa_type_name) } process_facts(if_true, true_presences) end diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 764c1fb39..cea325fcb 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -13,12 +13,37 @@ class BaseVariable < Base # @param return_type [ComplexType, nil] # @param assignment [Parser::AST::Node, nil] - def initialize assignment: nil, return_type: nil, **splat + # @param exclude_return_type [ComplexType, nil] Ensure any + # return type returned will never include any of these unique + # types in the unique types of its complex type. + # + # Example: If a return type is 'Float | Integer | nil' and the + # exclude_return_type is 'Integer', the resulting return + # type will be 'Float | nil' because Integer is excluded. + # @param intersection_return_type [ComplexType, nil] Ensure each unique + # return type is compatible with at least one element of this + # complex type. If a ComplexType used as a return type is an + # union type - we can return any of these - these are + # intersection types - everything we return needs to meet at least + # one of these unique types. + # + # Example: If a return type is 'Numeric | nil' and the + # intersection_return_type is 'Float | nil', the resulting return + # type will be 'Float | nil' because Float is compatible + # with Numeric and nil is compatible with nil. + # @see https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types + # @see https://en.wikipedia.org/wiki/Intersection_type#TypeScript_example + # @param mass_assignment [Array(Parser::AST::Node, Integer), nil] + def initialize assignment: nil, mass_assignment: nil, return_type: nil, + intersection_return_type: nil, exclude_return_type: nil, + **splat super(**splat) @assignment = assignment # @type [nil, ::Array(Parser::AST::Node, Integer)] @mass_assignment = nil @return_type = return_type + @intersection_return_type = intersection_return_type + @exclude_return_type = exclude_return_type end def combine_with(other, attrs={}) @@ -26,10 +51,32 @@ def combine_with(other, attrs={}) assignment: assert_same(other, :assignment), mass_assignment: assert_same(other, :mass_assignment), return_type: combine_return_type(other), + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 + intersection_return_type: combine_types(other, :intersection_return_type), + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 + exclude_return_type: combine_types(other, :exclude_return_type), }) super(other, attrs) end + def reset_generated! + @return_type_minus_exclusions = nil + super + end + + def inner_desc + super + ", intersection_return_type=#{intersection_return_type&.rooted_tags.inspect}, exclude_return_type=#{exclude_return_type&.rooted_tags.inspect}" + end + + # @param other [self] + # + # @return [Array(AST::Node, Integer), nil] + def combine_mass_assignment(other) + # @todo pick first non-nil arbitrarily - we don't yet support + # mass assignment merging + mass_assignment || other.mass_assignment + end + def completion_item_kind Solargraph::LanguageServer::CompletionItemKinds::VARIABLE end @@ -39,10 +86,6 @@ def symbol_kind Solargraph::LanguageServer::SymbolKinds::VARIABLE end - def return_type - @return_type ||= generate_complex_type - end - def nil_assignment? # this will always be false - should it be return_type == # ComplexType::NIL or somesuch? @@ -78,13 +121,16 @@ def return_types_from_node(parent_node, api_map) end # @param api_map [ApiMap] - # @return [ComplexType] + # @return [ComplexType, ComplexType::UniqueType] def probe api_map unless @assignment.nil? types = return_types_from_node(@assignment, api_map) - return ComplexType.new(types.uniq) unless types.empty? + return adjust_type api_map, ComplexType.new(types.uniq) unless types.empty? end + # @todo should handle merging types from mass assignments as + # well so that we can do better flow sensitive typing with + # multiple assignments unless @mass_assignment.nil? mass_node, index = @mass_assignment types = return_types_from_node(mass_node, api_map) @@ -95,7 +141,10 @@ def probe api_map type.all_params.first end end.compact! - return ComplexType.new(types.uniq) unless types.empty? + + return ComplexType::UNDEFINED if types.empty? + + return adjust_type api_map, ComplexType.new(types.uniq).qualify(api_map, *gates) end ComplexType::UNDEFINED @@ -111,13 +160,132 @@ def type_desc "#{super} = #{assignment&.type.inspect}" end + # @return [ComplexType, nil] + def return_type + generate_complex_type || @return_type || intersection_return_type || ComplexType::UNDEFINED + end + + def typify api_map + raw_return_type = super + + adjust_type(api_map, raw_return_type) + end + + # @sg-ignore need boolish support for ? methods + def presence_certain? + exclude_return_type || intersection_return_type + end + private - # @return [ComplexType] + attr_reader :exclude_return_type, :intersection_return_type + + # @param api_map [ApiMap] + # @param raw_return_type [ComplexType, ComplexType::UniqueType] + # + # @return [ComplexType, ComplexType::UniqueType] + def adjust_type(api_map, raw_return_type) + qualified_exclude = exclude_return_type&.qualify(api_map, *(closure&.gates || [''])) + minus_exclusions = raw_return_type.exclude qualified_exclude, api_map + qualified_intersection = intersection_return_type&.qualify(api_map, *(closure&.gates || [''])) + minus_exclusions.intersect_with qualified_intersection, api_map + end + + # @param other [self] + # @return [Pin::Closure, nil] + def combine_closure(other) + return closure if self.closure == other.closure + + # choose first defined, as that establishes the scope of the variable + if closure.nil? || other.closure.nil? + Solargraph.assert_or_log(:varible_closure_missing) do + "One of the local variables being combined is missing a closure: " \ + "#{self.inspect} vs #{other.inspect}" + end + return closure || other.closure + end + + if closure.location.nil? || other.closure.location.nil? + return closure.location.nil? ? other.closure : closure + end + + # if filenames are different, this will just pick one + # @sg-ignore flow sensitive typing needs to handle ivars + return closure if closure.location <= other.closure.location + + other.closure + end + + # See if this variable is visible within 'other_closure' + # + # @param other_closure [Pin::Closure] + # @return [Boolean] + def visible_in_closure? other_closure + needle = closure + return false if closure.nil? + haystack = other_closure + + cursor = haystack + + until cursor.nil? + if cursor.is_a?(Pin::Method) && closure.context.tags == 'Class<>' + # methods can't see local variables declared in their + # parent closure + return false + end + + if cursor.binder.namespace == needle.binder.namespace + return true + end + + if cursor.return_type == needle.context + return true + end + + if scope == :instance && cursor.is_a?(Pin::Namespace) + # classes and modules can't see local variables declared + # in their parent closure, so stop here + return false + end + + cursor = cursor.closure + end + false + end + + # @param other [self] + # @return [ComplexType, nil] + def combine_return_type(other) + combine_types(other, :return_type) + end + + # @param other [self] + # @param attr [::Symbol] + # + # @return [ComplexType, nil] + def combine_types(other, attr) + # @type [ComplexType, nil] + type1 = send(attr) + # @type [ComplexType, nil] + type2 = other.send(attr) + if type1 && type2 + types = (type1.items + type2.items).uniq + ComplexType.new(types) + else + type1 || type2 + end + end + + # @return [::Symbol] + def scope + :instance + end + + # @return [ComplexType, nil] def generate_complex_type tag = docstring.tag(:type) return ComplexType.try_parse(*tag.types) unless tag.nil? || tag.types.nil? || tag.types.empty? - ComplexType.new + nil end end end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 9eae6cc6f..81baca396 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -6,26 +6,32 @@ class LocalVariable < BaseVariable # @return [Range] attr_reader :presence - def presence_certain? - @presence_certain - end - - # @param assignment [AST::Node, nil] # @param presence [Range, nil] - # @param presence_certain [Boolean] # @param splat [Hash] - def initialize assignment: nil, presence: nil, presence_certain: false, **splat + def initialize presence: nil, + **splat super(**splat) - @assignment = assignment @presence = presence - @presence_certain = presence_certain + end + + # @param api_map [ApiMap] + # @return [ComplexType] + def probe api_map + if presence_certain? && return_type&.defined? + # flow sensitive typing has already figured out this type + # has been downcast - use the type it figured out + return adjust_type api_map, return_type.qualify(api_map, *gates) + end + + super + end + + def inner_desc + super + ", presence=#{presence.inspect}" end def combine_with(other, attrs={}) - new_attrs = { - assignment: assert_same(other, :assignment), - presence_certain: assert_same(other, :presence_certain?), - }.merge(attrs) + new_attrs = {}.merge(attrs) # @sg-ignore Wrong argument type for # Solargraph::Pin::Base#assert_same: other expected # Solargraph::Pin::Base, received self @@ -36,12 +42,22 @@ def combine_with(other, attrs={}) # @param other_closure [Pin::Closure] # @param other_loc [Location] + # @sg-ignore Need to add nil check here def visible_at?(other_closure, other_loc) + # @sg-ignore Need to add nil check here location.filename == other_loc.filename && - presence.include?(other_loc.range.start) && + presence&.include?(other_loc.range.start) && + # @sg-ignore Need to add nil check here match_named_closure(other_closure, closure) end + # @param other_loc [Location] + def starts_at?(other_loc) + location&.filename == other_loc.filename && + presence && + presence.start == other_loc.range.start + end + def to_rbs (name || '(anon)') + ' ' + (return_type&.to_rbs || 'untyped') end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 947513689..797f7b5da 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -166,8 +166,13 @@ def index # @param api_map [ApiMap] def typify api_map - return return_type.qualify(api_map, *closure.gates) unless return_type.undefined? - closure.is_a?(Pin::Block) ? typify_block_param(api_map) : typify_method_param(api_map) + new_type = super + return new_type if new_type.defined? + + # sniff based on param tags + new_type = closure.is_a?(Pin::Block) ? typify_block_param(api_map) : typify_method_param(api_map) + + adjust_type api_map, new_type end # @param atype [ComplexType] @@ -187,6 +192,10 @@ def documentation private + def generate_complex_type + nil + end + # @return [YARD::Tags::Tag, nil] def param_tag params = closure.docstring.tags(:param) diff --git a/spec/pin/base_variable_spec.rb b/spec/pin/base_variable_spec.rb index 8c462bff3..7a72670e5 100644 --- a/spec/pin/base_variable_spec.rb +++ b/spec/pin/base_variable_spec.rb @@ -44,4 +44,19 @@ def bar expect(type.to_rbs).to eq('(1 | nil)') expect(type.simplify_literals.to_rbs).to eq('(::Integer | ::NilClass)') end + + it "understands parameters aren't affected by @type" do + code = %( + # @return [Proc] + def foo + # @type [Proc] + # @param layout [Boolean] + @render_method = proc { |layout = false| + 123 if layout + } + end + ) + checker = Solargraph::TypeChecker.load_string(code, 'test.rb', :alpha) + expect(checker.problems.map(&:message)).to eq([]) + end end From e584d33b4a1a5aeffe6f9b01e582fb5c10802d8e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 1 Nov 2025 13:22:14 -0400 Subject: [PATCH 611/930] Fix up param type --- lib/solargraph/rbs_map/conversions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 3e777f726..32800b254 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -726,7 +726,7 @@ def type_tag(type_name, type_args = []) build_type(type_name, type_args).tags end - # @param type [RBS::Types::Bases::Base] + # @param type [Object] # @return [String] def other_type_to_tag type if type.is_a?(RBS::Types::Optional) From 077ddd94ee42c943a1ec713de8f270879918efde Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 1 Nov 2025 13:31:56 -0400 Subject: [PATCH 612/930] Fix merge --- lib/solargraph/api_map/store.rb | 3 --- lib/solargraph/parser/flow_sensitive_typing.rb | 3 ++- lib/solargraph/parser/parser_gem/node_methods.rb | 2 ++ lib/solargraph/workspace/require_paths.rb | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index b43ae580e..480b77525 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -242,11 +242,8 @@ def get_ancestors(fqns) next if refs.nil? # @param ref [String] refs.map(&:type).map(&:to_s).each do |ref| - # @sg-ignore We should understand reassignment of variable to new type next if ref.nil? || ref.empty? || visited.include?(ref) - # @sg-ignore We should understand reassignment of variable to new type ancestors << ref - # @sg-ignore We should understand reassignment of variable to new type queue << ref end end diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 94b525635..93a7649fe 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -210,7 +210,7 @@ class << self # @return [void] def add_downcast_local(pin, presence:, downcast_type:, downcast_not_type:) # @todo Create pin#update method - new_pin = pin.class.new( + new_pin = Pin::LocalVariable.new( location: pin.location, closure: pin.closure, name: pin.name, @@ -223,6 +223,7 @@ def add_downcast_local(pin, presence:, downcast_type:, downcast_not_type:) source: :flow_sensitive_typing ) new_pin.reset_generated! + new_pin = pin.combine_with(new_pin) locals.push(new_pin) end diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 59d705120..b92dfe575 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -119,6 +119,8 @@ def convert_hash node result end + # @sg-ignore Wrong argument type for AST::Node.new: type + # expected AST::_ToSym, received :nil NIL_NODE = ::Parser::AST::Node.new(:nil) # @param node [Parser::AST::Node] diff --git a/lib/solargraph/workspace/require_paths.rb b/lib/solargraph/workspace/require_paths.rb index 10dce4053..c8eea161b 100644 --- a/lib/solargraph/workspace/require_paths.rb +++ b/lib/solargraph/workspace/require_paths.rb @@ -83,7 +83,6 @@ def require_path_from_gemspec_file gemspec_file_path return [] if hash.empty? hash['paths'].map { |path| File.join(base, path) } rescue StandardError => e - # @sg-ignore Should handle redefinition of types in simple contexts Solargraph.logger.warn "Error reading #{gemspec_file_path}: [#{e.class}] #{e.message}" [] end From 95b3cc003e5fca9bafecfc61c9c18cc350db342e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 1 Nov 2025 14:09:22 -0400 Subject: [PATCH 613/930] Update metrics --- lib/solargraph/type_checker/rules.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index c05c02c0f..ed3ee5263 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -60,22 +60,22 @@ def require_inferred_type_params? # pending code fixes (~280): # - # Need to add nil check here + # @todo 263: Need to add nil check herea # @todo 9: Need to validate config # @todo 3: Translate to something flow sensitive typing understands # @todo 2: Need a downcast here # # flow-sensitive typing could handle (~100): # - # @todo 51 flow sensitive typing needs to handle ivars + # @todo 47: flow sensitive typing needs to handle ivars # @todo 9: Should handle redefinition of types in simple contexts # @todo 6: need boolish support for ? methods + # @todo 5: literal arrays in this module turn into ::Solargraph::Source::Chain::Array # @todo 4: flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) # @todo 4: (*) flow sensitive typing needs better handling of ||= on lvars - # @todo 4: Need to look at Tuple#include? handling - # @todo 4: literal arrays in this module turn into ::Solargraph::Source::Chain::Array # @todo 3: downcast output of Enumerable#select # @todo 3: flow sensitive typing needs to handle 'raise if' + # @todo 2: Need to look at Tuple#include? handling # @todo 2: should warn on nil dereference below # @todo 2: Should better support meaning of '&' in RBS # @todo 2: (*) flow sensitive typing needs to handle "if foo = bar" From d021086c8c56bf8ba4f3bf74847d98a88b6f1858 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 1 Nov 2025 14:10:34 -0400 Subject: [PATCH 614/930] RuboCop fixes --- lib/solargraph/pin/base_variable.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 5b7eb853e..eb6ed9215 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -45,10 +45,10 @@ class BaseVariable < Base # @see https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types # @see https://en.wikipedia.org/wiki/Intersection_type#TypeScript_example # @param mass_assignment [Array(Parser::AST::Node, Integer), nil] - def initialize assignment: nil, assignments: [], mass_assignment: nil, - presence: nil, return_type: nil, - intersection_return_type: nil, exclude_return_type: nil, - **splat + def initialize assignment: nil, assignments: [], mass_assignment: nil, + presence: nil, return_type: nil, + intersection_return_type: nil, exclude_return_type: nil, + **splat super(**splat) @assignments = (assignment.nil? ? [] : [assignment]) + assignments # @type [nil, ::Array(Parser::AST::Node, Integer)] @@ -77,7 +77,7 @@ def combine_with(other, attrs={}) return_type: combine_return_type(other), presence: combine_presence(other), intersection_return_type: combine_types(other, :intersection_return_type), - exclude_return_type: combine_types(other, :exclude_return_type), + exclude_return_type: combine_types(other, :exclude_return_type) }) super(other, new_attrs) end From c409c9a0882ceca5403e1a3035bd90581bd5932a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 1 Nov 2025 14:15:52 -0400 Subject: [PATCH 615/930] Clean up diff --- lib/solargraph/pin/base_variable.rb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index eb6ed9215..f2a7a2971 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -15,9 +15,6 @@ class BaseVariable < Base attr_reader :presence # @param return_type [ComplexType, nil] - # @param exclude_return_type [ComplexType, nil] Ensure any return - # type returned will never include these unique types in the - # unique types of its complex type # @param assignment [Parser::AST::Node, nil] First assignment # that was made to this variable # @param assignments [Array] Possible @@ -65,10 +62,6 @@ def reset_generated! super end - def inner_desc - super + ", presence=#{presence.inspect}, exclude_return_type=#{exclude_return_type.inspect}, assignments=#{assignments}" - end - def combine_with(other, attrs={}) new_assignments = combine_assignments(other) new_attrs = attrs.merge({ @@ -109,7 +102,7 @@ def reset_generated! end def inner_desc - super + ", intersection_return_type=#{intersection_return_type&.rooted_tags.inspect}, exclude_return_type=#{exclude_return_type&.rooted_tags.inspect}" + super + ", presence=#{presence.inspect}, intersection_return_type=#{intersection_return_type&.rooted_tags.inspect}, exclude_return_type=#{exclude_return_type&.rooted_tags.inspect}, assignments=#{assignments}" end def completion_item_kind From b8cf7a5da89131d1a926af25b951adf4f54c12df Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 1 Nov 2025 14:16:41 -0400 Subject: [PATCH 616/930] Drop return_type_minus_exclusions --- lib/solargraph/pin/base_variable.rb | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index f2a7a2971..9fa6e55f7 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -57,7 +57,6 @@ def initialize assignment: nil, assignments: [], mass_assignment: nil, end def reset_generated! - @return_type_minus_exclusions = nil @assignment = nil super end @@ -96,11 +95,6 @@ def combine_assignments(other) (other.assignments + assignments).uniq end - def reset_generated! - @return_type_minus_exclusions = nil - super - end - def inner_desc super + ", presence=#{presence.inspect}, intersection_return_type=#{intersection_return_type&.rooted_tags.inspect}, exclude_return_type=#{exclude_return_type&.rooted_tags.inspect}, assignments=#{assignments}" end @@ -347,20 +341,6 @@ def visible_in_closure? other_closure false end - # @param raw_return_type [ComplexType, nil] - # @return [ComplexType, nil] - def return_type_minus_exclusions(raw_return_type) - @return_type_minus_exclusions ||= - if exclude_return_type && raw_return_type - types = raw_return_type.items - exclude_return_type.items - types = [ComplexType::UniqueType::UNDEFINED] if types.empty? - ComplexType.new(types) - else - raw_return_type - end - @return_type_minus_exclusions - end - # @param other [self] # @return [ComplexType, nil] def combine_return_type(other) From af70c09053083d31b672e40ebbd9437d9f41c5c5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 1 Nov 2025 14:18:27 -0400 Subject: [PATCH 617/930] Drop use of presence_certain? --- lib/solargraph/pin/base_variable.rb | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 9fa6e55f7..5ea0147e6 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -341,20 +341,6 @@ def visible_in_closure? other_closure false end - # @param other [self] - # @return [ComplexType, nil] - def combine_return_type(other) - if presence_certain? && return_type&.defined? - # flow sensitive typing has already figured out this type - # has been downcast - use the type it figured out - return return_type - end - if other.presence_certain? && other.return_type&.defined? - return other.return_type - end - combine_types(other, :return_type) - end - # @param other [self] # @return [ComplexType, nil] def combine_return_type(other) From bfe7f4e73f627433cbeb9851072490cdb046e314 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 1 Nov 2025 16:54:49 -0400 Subject: [PATCH 618/930] Add downcast method, another falsey type --- .../parser/flow_sensitive_typing.rb | 20 +++++-------------- lib/solargraph/pin/base_variable.rb | 19 ++++++++++++++++-- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 9a81f71c1..9f19b78f1 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -249,20 +249,10 @@ def self.visible_pins(pins, name, closure, location) # # @return [void] def add_downcast_local(pin, presence:, downcast_type:, downcast_not_type:) - # @todo Create pin#update method - new_pin = Solargraph::Pin::LocalVariable.new( - location: pin.location, - closure: pin.closure, - name: pin.name, - assignment: pin.assignment, - comments: pin.comments, - presence: presence, - intersection_return_type: downcast_type, - exclude_return_type: downcast_not_type, - return_type: pin.return_type, - source: :flow_sensitive_typing - ) - new_pin.reset_generated! + new_pin = pin.downcast(exclude_return_type: downcast_not_type, + intersection_return_type: downcast_type, + source: :flow_sensitive_typing, + presence: presence) locals.push(new_pin) end @@ -458,7 +448,7 @@ def process_variable(node, true_presences, false_presences) if_false = {} if_false[pin] ||= [] - if_false[pin] << { type: ComplexType::NIL } + if_false[pin] << { type: ComplexType.parse('nil, false') } process_facts(if_false, false_presences) end diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index cea325fcb..60cb20bbf 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -46,6 +46,17 @@ def initialize assignment: nil, mass_assignment: nil, return_type: nil, @exclude_return_type = exclude_return_type end + def downcast exclude_return_type: nil, intersection_return_type: nil, + presence: presence, source: source + result = dup + result.exclude_return_type = exclude_return_type + result.intersection_return_type = intersection_return_type + result.source = source + result.presence = presence + result.reset_generated! + result + end + def combine_with(other, attrs={}) attrs.merge({ assignment: assert_same(other, :assignment), @@ -176,9 +187,13 @@ def presence_certain? exclude_return_type || intersection_return_type end - private + protected - attr_reader :exclude_return_type, :intersection_return_type + attr_accessor :exclude_return_type, :intersection_return_type + + attr_writer :presence + + private # @param api_map [ApiMap] # @param raw_return_type [ComplexType, ComplexType::UniqueType] From 19a81fe14010d50facc6169df7c9040825779712 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 1 Nov 2025 16:57:21 -0400 Subject: [PATCH 619/930] Fix types --- lib/solargraph/pin/base_variable.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 60cb20bbf..8615c60ea 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -46,8 +46,12 @@ def initialize assignment: nil, mass_assignment: nil, return_type: nil, @exclude_return_type = exclude_return_type end + # @param exclude_return_type [ComplexType, nil] + # @param intersection_return_type [ComplexType, nil] + # @param presence [Range] + # @param source [Symbol] def downcast exclude_return_type: nil, intersection_return_type: nil, - presence: presence, source: source + presence:, source: self.source result = dup result.exclude_return_type = exclude_return_type result.intersection_return_type = intersection_return_type From 090099b51a1afaae804ad7338e89e0cecfe8daea Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 1 Nov 2025 17:00:04 -0400 Subject: [PATCH 620/930] Fix types --- lib/solargraph/pin/base_variable.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 8615c60ea..5c4a646cd 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -46,12 +46,12 @@ def initialize assignment: nil, mass_assignment: nil, return_type: nil, @exclude_return_type = exclude_return_type end + # @param presence [Range] # @param exclude_return_type [ComplexType, nil] # @param intersection_return_type [ComplexType, nil] - # @param presence [Range] # @param source [Symbol] - def downcast exclude_return_type: nil, intersection_return_type: nil, - presence:, source: self.source + def downcast presence:, exclude_return_type: nil, intersection_return_type: nil, + source: self.source result = dup result.exclude_return_type = exclude_return_type result.intersection_return_type = intersection_return_type From a2cffcc400b24cb4e96ea278233409b88f597e5b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 2 Nov 2025 08:26:24 -0500 Subject: [PATCH 621/930] Fix tags --- lib/solargraph/pin/base_variable.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 5c4a646cd..518be8a83 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -49,7 +49,9 @@ def initialize assignment: nil, mass_assignment: nil, return_type: nil, # @param presence [Range] # @param exclude_return_type [ComplexType, nil] # @param intersection_return_type [ComplexType, nil] - # @param source [Symbol] + # @param source [::Symbol] + # + # @return [self] def downcast presence:, exclude_return_type: nil, intersection_return_type: nil, source: self.source result = dup @@ -195,6 +197,7 @@ def presence_certain? attr_accessor :exclude_return_type, :intersection_return_type + # @return [Range] attr_writer :presence private From b78965b99402183ab54de972a784440596d88e37 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 3 Nov 2025 09:21:11 -0500 Subject: [PATCH 622/930] Don't set binder to avoid trigger solargraph-rspec bug --- .../parser/parser_gem/node_processors/def_node.rb | 11 ++++------- lib/solargraph/pin/base.rb | 4 ---- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/def_node.rb b/lib/solargraph/parser/parser_gem/node_processors/def_node.rb index 309de01a1..c4e4f9a70 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/def_node.rb @@ -8,13 +8,12 @@ class DefNode < Parser::NodeProcessor::Base def process name = node.children[0].to_s scope = region.scope || (region.closure.is_a?(Pin::Singleton) ? :class : :instance) - method_binder = scope == :instance ? region.closure.binder.namespace_type : region.closure.binder + method_context = scope == :instance ? region.closure.binder.namespace_type : region.closure.binder methpin = Solargraph::Pin::Method.new( location: get_node_location(node), closure: region.closure, name: name, - context: method_binder, - binder: method_binder, + context: method_context, comments: comments_for(node), scope: scope, visibility: scope == :instance && name == 'initialize' ? :private : region.visibility, @@ -26,8 +25,7 @@ def process location: methpin.location, closure: methpin.closure, name: methpin.name, - context: region.closure.binder, - binder: region.closure.binder, + context: method_context, comments: methpin.comments, scope: :class, visibility: :public, @@ -39,8 +37,7 @@ def process location: methpin.location, closure: methpin.closure, name: methpin.name, - context: region.closure.binder, - binder: region.closure.binder, + context: method_context, comments: methpin.comments, scope: :instance, visibility: :private, diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 6ff60e2cd..7acf9d039 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -649,10 +649,6 @@ def all_location_text end end - # @return [void] - def reset_generated! - end - protected # @return [Boolean] From ed2381371b008165a1c2fdb992bcac7bda1d6a23 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 3 Nov 2025 09:25:02 -0500 Subject: [PATCH 623/930] Reproduce solargraph-rspec rspec failure --- .github/workflows/plugins.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index b5984f3cb..2619b9ff8 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -143,6 +143,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: + # solargraph-rails supports Ruby 3.0+ ruby-version: '3.0' bundler-cache: false From 769041b0dac0ec5024b1582abb507a2a740b27cd Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 3 Nov 2025 09:36:46 -0500 Subject: [PATCH 624/930] Add set -x to debug --- .github/workflows/plugins.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index cece27ae9..1d698f43a 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -125,6 +125,8 @@ jobs: bundler-cache: false - name: Install gems run: | + set -x + cd ../solargraph-rspec echo "gem 'solargraph', path: '../solargraph'" >> Gemfile bundle config path ${{ env.BUNDLE_PATH }} From a4ad163c38e3e4555d4628f85d4514f604060ee0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 3 Nov 2025 09:51:12 -0500 Subject: [PATCH 625/930] Use a Ruby version tested by solargraph-rspec --- .github/workflows/plugins.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 1d698f43a..6de580b2f 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -121,7 +121,8 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.4 + # solargraph-rspec's tests run against 3.1, 3.2 and 3.3 + ruby-version: '3.1' bundler-cache: false - name: Install gems run: | From e6dcf0970cc02d64063663b4859e2889a381a717 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 3 Nov 2025 09:56:04 -0500 Subject: [PATCH 626/930] Mark item fixed --- spec/pin/base_variable_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/pin/base_variable_spec.rb b/spec/pin/base_variable_spec.rb index 8c07e9361..2a49ac9f1 100644 --- a/spec/pin/base_variable_spec.rb +++ b/spec/pin/base_variable_spec.rb @@ -46,8 +46,6 @@ def bar end it "understands proc kwarg parameters aren't affected by @type" do - pending 'better kwarg handling in type checker' - code = %( # @return [Proc] def foo From c5df6965b99536d7dc0efde20e19ada24bcf34ed Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 3 Nov 2025 10:07:01 -0500 Subject: [PATCH 627/930] Add appraisal workaround --- .github/workflows/plugins.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 6de580b2f..24ca3f836 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -133,6 +133,14 @@ jobs: bundle config path ${{ env.BUNDLE_PATH }} bundle install --jobs 4 --retry 3 bundle exec appraisal install + # @todo some kind of appraisal/bundle conflict? + # https://github.com/castwide/solargraph/actions/runs/19038710934/job/54369767122?pr=1116 + # /home/runner/work/solargraph/solargraph-rspec/vendor/bundle/ruby/3.1.0/gems/bundler-2.6.9/lib/bundler/runtime.rb:317:in + # `check_for_activated_spec!': You have already activated date + # 3.5.0, but your Gemfile requires date 3.4.1. Prepending + # `bundle exec` to your command may solve + # this. (Gem::LoadError) + bundle exec appraisal update date - name: Configure .solargraph.yml run: | cd ../solargraph-rspec From 7cefc1cabe4533f8265c18baa717bca422da75ed Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 3 Nov 2025 10:08:14 -0500 Subject: [PATCH 628/930] Add resgression spec --- spec/pin/base_variable_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/pin/base_variable_spec.rb b/spec/pin/base_variable_spec.rb index 8c462bff3..2a49ac9f1 100644 --- a/spec/pin/base_variable_spec.rb +++ b/spec/pin/base_variable_spec.rb @@ -44,4 +44,19 @@ def bar expect(type.to_rbs).to eq('(1 | nil)') expect(type.simplify_literals.to_rbs).to eq('(::Integer | ::NilClass)') end + + it "understands proc kwarg parameters aren't affected by @type" do + code = %( + # @return [Proc] + def foo + # @type [Proc] + # @param layout [Boolean] + @render_method = proc { |layout = false| + 123 if layout + } + end + ) + checker = Solargraph::TypeChecker.load_string(code, 'test.rb', :alpha) + expect(checker.problems.map(&:message)).to eq([]) + end end From cccd47a5174ea3ceabd612c60de8701efe62c164 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 11 Nov 2025 20:58:40 -0500 Subject: [PATCH 629/930] Fix solargraph-rspec specs --- lib/solargraph/convention/data_definition.rb | 2 +- lib/solargraph/convention/struct_definition.rb | 3 ++- .../parser_gem/node_processors/block_node.rb | 17 ++++++++++++++--- lib/solargraph/parser/region.rb | 8 ++++++-- lib/solargraph/pin/base.rb | 3 +++ lib/solargraph/pin/block.rb | 13 +++++++++++-- lib/solargraph/pin/namespace.rb | 6 ++++++ lib/solargraph/source/chain.rb | 10 +++++----- .../source/chain/instance_variable.rb | 2 +- lib/solargraph/source_map/clip.rb | 2 +- spec/source/chain/instance_variable_spec.rb | 6 +++++- spec/source_map/clip_spec.rb | 6 +----- 12 files changed, 56 insertions(+), 22 deletions(-) diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index 8efe27932..90b5a64c3 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -71,7 +71,7 @@ def process comments: attribute_comments(attribute_node, attribute_name)) end - process_children region.update(closure: nspin, visibility: :public) + process_children region.update(closure: nspin, scope: :instance, visibility: :public) false end diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index b34ae5494..2da32c18d 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -96,7 +96,8 @@ def process end end - process_children region.update(closure: nspin, visibility: :public) + # the child here is the struct body, which uses Class as the binder + process_children region.update(closure: nspin, scope: :instance, visibility: :public) false end 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 c77713fad..3fb81ca33 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/block_node.rb @@ -13,14 +13,23 @@ def process scope = region.scope || region.closure.context.scope if other_class_eval? clazz_name = unpack_name(node.children[0].children[0]) - binder = ComplexType.try_parse("Class<#{clazz_name}>") + # instance variables should come from the Class type + context = ComplexType.try_parse("Class<#{clazz_name}>") + # when resolving method calls inside this block, we + # should be working inside Class, not T - that's the + # whole point of 'class_eval'. e.g., def foo defines it + # as an instance method in the class, like Foo.def + # instead of Foo.new.def + binder = context + scope = :class end + binder ||= region.binder block_pin = Solargraph::Pin::Block.new( location: location, closure: region.closure, node: node, - context: binder, + context: context, binder: binder, receiver: node.children[0], comments: comments_for(node), @@ -28,7 +37,9 @@ def process source: :parser ) pins.push block_pin - process_children region.update(closure: block_pin) + # A 'def' inside creates an instance method + child_scope = :instance + process_children region.update(closure: block_pin, scope: child_scope, binder: binder) end private diff --git a/lib/solargraph/parser/region.rb b/lib/solargraph/parser/region.rb index a6559bc8a..1844156fa 100644 --- a/lib/solargraph/parser/region.rb +++ b/lib/solargraph/parser/region.rb @@ -9,6 +9,8 @@ class Region # @return [Pin::Closure] attr_reader :closure + attr_reader :binder + # @return [Symbol] attr_reader :scope @@ -24,13 +26,14 @@ class Region # @param source [Source] # @param namespace [String] # @param closure [Pin::Closure, nil] + # @param binder [ComplexType, ComplexType::SimpleType, nil] # @param scope [Symbol, nil] # @param visibility [Symbol] # @param lvars [Array] def initialize source: Solargraph::Source.load_string(''), closure: nil, + binder: nil, scope: nil, visibility: :public, lvars: [] @source = source - # @closure = closure @closure = closure || Pin::Namespace.new(name: '', location: source.location, source: :parser) @scope = scope @visibility = visibility @@ -49,10 +52,11 @@ def filename # @param visibility [Symbol, nil] # @param lvars [Array, nil] # @return [Region] - def update closure: nil, scope: nil, visibility: nil, lvars: nil + def update closure: nil, binder: nil, scope: nil, visibility: nil, lvars: nil Region.new( source: source, closure: closure || self.closure, + binder: binder || closure&.binder || self.closure.binder, scope: scope || self.scope, visibility: visibility || self.visibility, lvars: lvars || self.lvars diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 7acf9d039..41f4f3a88 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -175,6 +175,9 @@ def reset_generated! # Same with @directives, @macros, @maybe_directives, which # regenerate docstring @deprecated = nil + @context = nil + @binder = nil + @path = nil reset_conversions end diff --git a/lib/solargraph/pin/block.rb b/lib/solargraph/pin/block.rb index 7a63ccc13..bd9cf8a52 100644 --- a/lib/solargraph/pin/block.rb +++ b/lib/solargraph/pin/block.rb @@ -21,8 +21,9 @@ def initialize receiver: nil, binder: nil, args: [], context: nil, node: nil, ** @receiver = receiver @context = context @return_type = ComplexType.parse('::Proc') - @rebind = binder if binder + @binder = binder if binder @node = node + @name = '' end # @param api_map [ApiMap] @@ -32,7 +33,14 @@ def rebind api_map end def binder - @rebind&.defined? ? @rebind : closure.binder + out = @rebind if @rebind&.defined? + out ||= @binder + out ||= closure.binder + end + + def context + @context = @rebind if @rebind&.defined? + super end # @param yield_types [::Array] @@ -89,6 +97,7 @@ def maybe_rebind api_map chain = Parser.chain(receiver, location.filename, node) locals = api_map.source_map(location.filename).locals_at(location) + closure.rebind api_map receiver_pin = chain.define(api_map, closure, locals).first return ComplexType::UNDEFINED unless receiver_pin diff --git a/lib/solargraph/pin/namespace.rb b/lib/solargraph/pin/namespace.rb index 95bd1089a..ca6cd6f84 100644 --- a/lib/solargraph/pin/namespace.rb +++ b/lib/solargraph/pin/namespace.rb @@ -48,6 +48,12 @@ def initialize type: :class, visibility: :public, gates: [''], name: '', **splat @name = name end + def reset_generated! + @return_type = nil + @full_context = nil + @path = nil + end + def to_rbs "#{@type.to_s} #{return_type.all_params.first.to_rbs}#{rbs_generics}".strip end diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index e941a347d..e8fef73f2 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -78,11 +78,11 @@ def base # # @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 - # as the self type used for any method calls in head position. + # @param name_pin [Pin::Base] A pin representing the closure 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 as the self + # type used for any method calls in head position. # # Requirements for name_pin: # diff --git a/lib/solargraph/source/chain/instance_variable.rb b/lib/solargraph/source/chain/instance_variable.rb index ea09f5578..08d71455c 100644 --- a/lib/solargraph/source/chain/instance_variable.rb +++ b/lib/solargraph/source/chain/instance_variable.rb @@ -5,7 +5,7 @@ class Source class Chain class InstanceVariable < Link def resolve api_map, name_pin, locals - api_map.get_instance_variable_pins(name_pin.binder.namespace, name_pin.binder.scope).select{|p| p.name == word} + api_map.get_instance_variable_pins(name_pin.context.namespace, name_pin.context.scope).select{|p| p.name == word} end end end diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index f7e76b711..904f81e93 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -199,7 +199,7 @@ def code_complete if cursor.word.start_with?('@@') return package_completions(api_map.get_class_variable_pins(context_pin.full_context.namespace)) elsif cursor.word.start_with?('@') - return package_completions(api_map.get_instance_variable_pins(closure.binder.namespace, closure.binder.scope)) + return package_completions(api_map.get_instance_variable_pins(closure.full_context.namespace, closure.context.scope)) elsif cursor.word.start_with?('$') return package_completions(api_map.get_global_variable_pins) end diff --git a/spec/source/chain/instance_variable_spec.rb b/spec/source/chain/instance_variable_spec.rb index 8326a66d2..4838faf8f 100644 --- a/spec/source/chain/instance_variable_spec.rb +++ b/spec/source/chain/instance_variable_spec.rb @@ -11,7 +11,11 @@ expect(pins.length).to eq(1) expect(pins.first.name).to eq('@foo') expect(pins.first.context.scope).to eq(:instance) - pins = link.resolve(api_map, closure, []) + # Lookup context is Class to find the civar + name_pin = Solargraph::Pin::ProxyType.anonymous(closure.binder, + # Closure is the class + closure: closure) + pins = link.resolve(api_map, name_pin, []) expect(pins.length).to eq(1) expect(pins.first.name).to eq('@foo') expect(pins.first.context.scope).to eq(:class) diff --git a/spec/source_map/clip_spec.rb b/spec/source_map/clip_spec.rb index c0a60fff0..dd865ebde 100644 --- a/spec/source_map/clip_spec.rb +++ b/spec/source_map/clip_spec.rb @@ -679,17 +679,13 @@ def initialize @foo._ end end - Foo.define_method(:test2) do - @foo._ - define_method(:test4) { @foo._ } # only handle Module#define_method, other pin is ignored.. - end Foo.class_eval do define_method(:test5) { @foo._ } end ), 'test.rb') api_map = Solargraph::ApiMap.new api_map.map source - [[4, 39], [7, 15], [11, 13], [12, 37], [15, 37]].each do |loc| + [[4, 39], [7, 15], [11, 37]].each do |loc| clip = api_map.clip_at('test.rb', loc) paths = clip.complete.pins.map(&:path) expect(paths).to include('String#upcase'), -> { %(expected #{paths} at #{loc} to include "String#upcase") } From d8ef859cee55f233bb0c5c3a6d49238cd2b4f584 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 11 Nov 2025 21:05:26 -0500 Subject: [PATCH 630/930] Fix RuboCop issues --- lib/solargraph/parser/region.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/parser/region.rb b/lib/solargraph/parser/region.rb index 1844156fa..f700038b0 100644 --- a/lib/solargraph/parser/region.rb +++ b/lib/solargraph/parser/region.rb @@ -24,17 +24,16 @@ class Region attr_reader :lvars # @param source [Source] - # @param namespace [String] # @param closure [Pin::Closure, nil] - # @param binder [ComplexType, ComplexType::SimpleType, nil] + # @param binder [ComplexType, ComplexType::UniqueType, nil] # @param scope [Symbol, nil] # @param visibility [Symbol] # @param lvars [Array] def initialize source: Solargraph::Source.load_string(''), closure: nil, - binder: nil, - scope: nil, visibility: :public, lvars: [] + binder: nil, scope: nil, visibility: :public, lvars: [] @source = source @closure = closure || Pin::Namespace.new(name: '', location: source.location, source: :parser) + @binder = binder @scope = scope @visibility = visibility @lvars = lvars @@ -48,6 +47,7 @@ def filename # Generate a new Region with the provided attribute changes. # # @param closure [Pin::Closure, nil] + # @param binder [ComplexType, ComplexType::UniqueType, nil] # @param scope [Symbol, nil] # @param visibility [Symbol, nil] # @param lvars [Array, nil] From f7bc6241258eb632862f6dfe44313856c8e6495e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 11 Nov 2025 21:26:10 -0500 Subject: [PATCH 631/930] Use dev branch for solargraph-rspec for now --- .github/workflows/plugins.yml | 6 +++++- lib/solargraph/pin/common.rb | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 24ca3f836..a298a0eab 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -116,7 +116,11 @@ jobs: - name: clone https://github.com/lekemula/solargraph-rspec/ run: | cd .. - git clone https://github.com/lekemula/solargraph-rspec.git + # git clone https://github.com/lekemula/solargraph-rspec.git + + # pending https://github.com/lekemula/solargraph-rspec/pull/30 + git clone https://github.com/apiology/solargraph-rspec.git + git checkout reset_closures cd solargraph-rspec - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 062099ee4..4397a707c 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -11,6 +11,16 @@ module Common # @return [Location] attr_reader :location + def location=(value) + @location = value + reset_generated! + end + + def closure=(value) + @closure = value + reset_generated! + end + # @sg-ignore Solargraph::Pin::Common#closure return type could not be inferred # @return [Pin::Closure, nil] def closure From 461ab2d6af6e2c90c44586c64e60c2c6c10d6ce5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 11 Nov 2025 21:31:39 -0500 Subject: [PATCH 632/930] Fix GHA --- .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 a298a0eab..86fe788a1 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -120,8 +120,8 @@ jobs: # pending https://github.com/lekemula/solargraph-rspec/pull/30 git clone https://github.com/apiology/solargraph-rspec.git - git checkout reset_closures cd solargraph-rspec + git checkout reset_closures - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From b71a1841c6bd2390f7a20f930a2fd179579c1499 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 11 Nov 2025 21:37:42 -0500 Subject: [PATCH 633/930] Fix linting issues --- lib/solargraph/pin/common.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 4397a707c..c37638c30 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -6,16 +6,23 @@ module Common # @!method source # @abstract # @return [Source, nil] + # @!method reset_generated! + # @abstract + # @return [void] # @type @closure [Pin::Closure, nil] # @return [Location] attr_reader :location + # @param value [Location] + # @return [void] def location=(value) @location = value reset_generated! end + # @param value [Pin::Closure] + # @return [void] def closure=(value) @closure = value reset_generated! From 9a4b8899da19204da2dd7e10b93e3cf2acc5eceb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 11 Nov 2025 22:06:03 -0500 Subject: [PATCH 634/930] Add solargraph update --- .github/workflows/plugins.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 86fe788a1..a2434c9c1 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -152,6 +152,7 @@ jobs: - name: Solargraph generate RSpec gems YARD and RBS pins run: | cd ../solargraph-rspec + bundle exec appraisal rbs collection update rspec_gems=$(bundle exec ruby -r './lib/solargraph-rspec' -e 'puts Solargraph::Rspec::Gems.gem_names.join(" ")' 2>/dev/null | tail -n1) bundle exec appraisal solargraph gems $rspec_gems - name: Run specs From 069ce930fa29bd3a48c980d4da070857db841566 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 11 Nov 2025 22:17:14 -0500 Subject: [PATCH 635/930] Fix appraisal in plugins.yml --- .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 a2434c9c1..3970f95d3 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -153,7 +153,7 @@ jobs: run: | cd ../solargraph-rspec bundle exec appraisal rbs collection update - rspec_gems=$(bundle exec ruby -r './lib/solargraph-rspec' -e 'puts Solargraph::Rspec::Gems.gem_names.join(" ")' 2>/dev/null | tail -n1) + rspec_gems=$(bundle exec appraisal ruby -r './lib/solargraph-rspec' -e 'puts Solargraph::Rspec::Gems.gem_names.join(" ")' 2>/dev/null | tail -n1) bundle exec appraisal solargraph gems $rspec_gems - name: Run specs run: | From fae78ad0d2d2e8fb7fc49bce9d8510f82fc5f8f3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 11 Nov 2025 22:25:39 -0500 Subject: [PATCH 636/930] Catch Gem::Requirement::BadRequirementError --- lib/solargraph/shell.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index a005f600b..f193aba43 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -149,6 +149,10 @@ def gems *names do_cache spec, api_map rescue Gem::MissingSpecError warn "Gem '#{name}' not found" + rescue Gem::Requirement::BadRequirementError => e + warn "Gem '#{name}' failed while loading" + warn e.message + warn e.backtrace.join("\n") end STDERR.puts "Documentation cached for #{names.count} gems." end From 0d877a4c8e8f952e905ae647010a8e6cf8b89817 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 12 Nov 2025 10:29:06 -0500 Subject: [PATCH 637/930] Bump Ruy versioin for solargraph-rspec --- .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 3970f95d3..b9aec8bdb 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -125,8 +125,9 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - # solargraph-rspec's tests run against 3.1, 3.2 and 3.3 - ruby-version: '3.1' + # solargraph-rspec's tests run against 3.1, 3.2 and 3.3, but + # fail on 3.1 as of 2025-11-12 + ruby-version: '3.2' bundler-cache: false - name: Install gems run: | From f886fed4f0ec5d4df5013b110ae9074e81968627 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 12 Nov 2025 10:34:20 -0500 Subject: [PATCH 638/930] Fix build failure via rubygems update --- .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 b9aec8bdb..eaedc4bf5 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -125,9 +125,8 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - # solargraph-rspec's tests run against 3.1, 3.2 and 3.3, but - # fail on 3.1 as of 2025-11-12 - ruby-version: '3.2' + ruby-version: '3.1' + rubygems: latest bundler-cache: false - name: Install gems run: | From 331f56b1c17ad03d7e4174bb0087e3a350471ef7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 12 Nov 2025 10:41:25 -0500 Subject: [PATCH 639/930] Bump rspec-rails just as solargraph-rails' GHA does --- .github/workflows/plugins.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index eaedc4bf5..fa704e2ae 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -145,6 +145,10 @@ jobs: # `bundle exec` to your command may solve # this. (Gem::LoadError) bundle exec appraisal update date + # For some reason on ruby 3.1 it defaults to an old version: 1.3.2 + # https://github.com/lekemula/solargraph-rspec/actions/runs/17814581205/job/50645370316?pr=22 + # We update manually to the latest + bundle exec appraisal update rspec-rails - name: Configure .solargraph.yml run: | cd ../solargraph-rspec From 7021433b2419580baffa6a2f7e3a04829dc492f0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 12 Nov 2025 17:13:28 -0500 Subject: [PATCH 640/930] Fix merge --- lib/solargraph/pin/base_variable.rb | 10 ++++------ lib/solargraph/pin/local_variable.rb | 9 --------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 433351c29..f40175e90 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -45,13 +45,11 @@ def combine_with(other, attrs={}) # @param other [self] # - # @return [Array(Parser::AST::Node, Integer), nil] - # - # @sg-ignore - # Solargraph::Pin::BaseVariable#combine_mass_assignment return - # type could not be inferred + # @return [Array(AST::Node, Integer), nil] def combine_mass_assignment(other) - assert_same(other, :mass_assignment) + # @todo pick first non-nil arbitrarily - we don't yet support + # mass assignment merging + mass_assignment || other.mass_assignment end # @return [Parser::AST::Node, nil] diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 72b85b6d5..4b97feb21 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -96,15 +96,6 @@ def combine_return_type(other) combine_types(other, :return_type) end - # @param other [self] - # - # @return [Array(AST::Node, Integer), nil] - def combine_mass_assignment(other) - # @todo pick first non-nil arbitrarily - we don't yet support - # mass assignment merging - mass_assignment || other.mass_assignment - end - def probe api_map if presence_certain? && return_type&.defined? # flow sensitive typing has already probed this type - use From 3161f917b1103d7f2241348e6e3e2ee4dc60d83f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 12 Nov 2025 17:26:52 -0500 Subject: [PATCH 641/930] Clarify expectation of instance variable lookup from context, but method lookup from binder --- lib/solargraph/source/chain.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index e8fef73f2..e24b11c92 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -87,16 +87,16 @@ def base # Requirements for name_pin: # # * name_pin.context: This should be a type representing the - # namespace where we can look up non-local variables and - # method names. If it is a Class, we will look up - # :class scoped methods/variables. + # namespace where we can look up non-local variables. If + # it is a Class, we will look up :class scoped + # instance variables. # # * name_pin.binder: Used for method call lookups only # (Chain::Call links). For method calls as the first # element in the chain, 'name_pin.binder' should be the # same as name_pin.context above. For method calls later - # in the chain (e.g., 'b' in a.b.c), it should represent - # 'a'. + # in the chain, it changes. (e.g., for 'b' in a.b.c, it + # should represent the type of 'a'). # # @param locals [::Array] Any local # variables / method parameters etc visible by the statement From a3fc979de374b9b1b5836dfe9fe83c06bfafce32 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 13 Nov 2025 08:30:48 -0500 Subject: [PATCH 642/930] Remove unneeded changes --- lib/solargraph/convention/data_definition.rb | 2 +- .../convention/struct_definition.rb | 3 +- .../parser_gem/node_processors/block_node.rb | 7 +--- .../parser_gem/node_processors/def_node.rb | 3 ++ lib/solargraph/parser/region.rb | 10 +---- lib/solargraph/pin/base.rb | 4 -- lib/solargraph/pin/base_variable.rb | 42 +++++++------------ lib/solargraph/pin/common.rb | 14 +++---- 8 files changed, 29 insertions(+), 56 deletions(-) diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index 90b5a64c3..8efe27932 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -71,7 +71,7 @@ def process comments: attribute_comments(attribute_node, attribute_name)) end - process_children region.update(closure: nspin, scope: :instance, visibility: :public) + process_children region.update(closure: nspin, visibility: :public) false end diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index 2da32c18d..b34ae5494 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -96,8 +96,7 @@ def process end end - # the child here is the struct body, which uses Class as the binder - process_children region.update(closure: nspin, scope: :instance, visibility: :public) + process_children region.update(closure: nspin, visibility: :public) false end 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 3fb81ca33..f4c0ff430 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/block_node.rb @@ -14,6 +14,7 @@ def process if other_class_eval? clazz_name = unpack_name(node.children[0].children[0]) # instance variables should come from the Class type + # - i.e., treated as class instance variables context = ComplexType.try_parse("Class<#{clazz_name}>") # when resolving method calls inside this block, we # should be working inside Class, not T - that's the @@ -21,10 +22,8 @@ def process # as an instance method in the class, like Foo.def # instead of Foo.new.def binder = context - scope = :class end - binder ||= region.binder block_pin = Solargraph::Pin::Block.new( location: location, closure: region.closure, @@ -37,9 +36,7 @@ def process source: :parser ) pins.push block_pin - # A 'def' inside creates an instance method - child_scope = :instance - process_children region.update(closure: block_pin, scope: child_scope, binder: binder) + process_children region.update(closure: block_pin) end private diff --git a/lib/solargraph/parser/parser_gem/node_processors/def_node.rb b/lib/solargraph/parser/parser_gem/node_processors/def_node.rb index c4e4f9a70..1b9fd442d 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/def_node.rb @@ -8,6 +8,9 @@ class DefNode < Parser::NodeProcessor::Base def process name = node.children[0].to_s scope = region.scope || (region.closure.is_a?(Pin::Singleton) ? :class : :instance) + # specify context explicitly instead of relying on + # closure, as they may differ (e.g., defs inside + # class_eval) method_context = scope == :instance ? region.closure.binder.namespace_type : region.closure.binder methpin = Solargraph::Pin::Method.new( location: get_node_location(node), diff --git a/lib/solargraph/parser/region.rb b/lib/solargraph/parser/region.rb index f700038b0..279ad0e57 100644 --- a/lib/solargraph/parser/region.rb +++ b/lib/solargraph/parser/region.rb @@ -9,8 +9,6 @@ class Region # @return [Pin::Closure] attr_reader :closure - attr_reader :binder - # @return [Symbol] attr_reader :scope @@ -25,15 +23,13 @@ class Region # @param source [Source] # @param closure [Pin::Closure, nil] - # @param binder [ComplexType, ComplexType::UniqueType, nil] # @param scope [Symbol, nil] # @param visibility [Symbol] # @param lvars [Array] def initialize source: Solargraph::Source.load_string(''), closure: nil, - binder: nil, scope: nil, visibility: :public, lvars: [] + scope: nil, visibility: :public, lvars: [] @source = source @closure = closure || Pin::Namespace.new(name: '', location: source.location, source: :parser) - @binder = binder @scope = scope @visibility = visibility @lvars = lvars @@ -47,16 +43,14 @@ def filename # Generate a new Region with the provided attribute changes. # # @param closure [Pin::Closure, nil] - # @param binder [ComplexType, ComplexType::UniqueType, nil] # @param scope [Symbol, nil] # @param visibility [Symbol, nil] # @param lvars [Array, nil] # @return [Region] - def update closure: nil, binder: nil, scope: nil, visibility: nil, lvars: nil + def update closure: nil, scope: nil, visibility: nil, lvars: nil Region.new( source: source, closure: closure || self.closure, - binder: binder || closure&.binder || self.closure.binder, scope: scope || self.scope, visibility: visibility || self.visibility, lvars: lvars || self.lvars diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 41f4f3a88..38773ebfe 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -175,9 +175,6 @@ def reset_generated! # Same with @directives, @macros, @maybe_directives, which # regenerate docstring @deprecated = nil - @context = nil - @binder = nil - @path = nil reset_conversions end @@ -579,7 +576,6 @@ def proxy return_type result = dup result.return_type = return_type result.proxied = true - result.reset_generated! result end diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index f40175e90..21f8bf3e2 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -131,7 +131,7 @@ def probe api_map type.all_params.first end end.compact! - return ComplexType.new(types.uniq).qualify(api_map, *gates) unless types.empty? + return ComplexType.new(types.uniq) unless types.empty? end ComplexType::UNDEFINED @@ -149,40 +149,28 @@ def type_desc private - # See if this variable is visible within 'other_closure' + # See if this variable is visible within 'viewing_closure' # - # @param other_closure [Pin::Closure] + # @param viewing_closure [Pin::Closure] # @return [Boolean] - def visible_in_closure? other_closure - needle = closure - haystack = other_closure + def visible_in_closure? viewing_closure + # if we're declared at top level, we can't be seen from within + # methods declared tere + return false if viewing_closure.is_a?(Pin::Method) && closure.context.tags == 'Class<>' - cursor = haystack + return true if viewing_closure.binder.namespace == closure.binder.namespace - until cursor.nil? - if cursor.is_a?(Pin::Method) && closure.context.tags == 'Class<>' - # methods can't see local variables declared in their - # parent closure - return false - end + return true if viewing_closure.return_type == closure.context - if cursor.binder.namespace == needle.binder.namespace - return true - end + # classes and modules can't see local variables declared + # in their parent closure, so stop here + return false if scope == :instance && viewing_closure.is_a?(Pin::Namespace) - if cursor.return_type == needle.context - return true - end + parent_of_viewing_closure = viewing_closure.closure - if scope == :instance && cursor.is_a?(Pin::Namespace) - # classes and modules can't see local variables declared - # in their parent closure, so stop here - return false - end + return false if parent_of_viewing_closure.nil? - cursor = cursor.closure - end - false + visible_in_closure?(parent_of_viewing_closure) end # @param other [self] diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index c37638c30..90bb434cd 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -12,20 +12,16 @@ module Common # @type @closure [Pin::Closure, nil] # @return [Location] - attr_reader :location - - # @param value [Location] - # @return [void] - def location=(value) - @location = value - reset_generated! - end + attr_accessor :location # @param value [Pin::Closure] # @return [void] def closure=(value) @closure = value - reset_generated! + # remove cached values generated from closure + @context = nil + @binder = nil + @path = nil end # @sg-ignore Solargraph::Pin::Common#closure return type could not be inferred From 54b65b8a8f0cbb05a2dd499da4bb8ee0ead5cc66 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 14 Nov 2025 08:37:09 -0500 Subject: [PATCH 643/930] Drop Block's binder param --- .../parser/parser_gem/node_processors/block_node.rb | 7 ------- lib/solargraph/pin/block.rb | 3 +-- 2 files changed, 1 insertion(+), 9 deletions(-) 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 f4c0ff430..0fb787eb1 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/block_node.rb @@ -16,12 +16,6 @@ def process # instance variables should come from the Class type # - i.e., treated as class instance variables context = ComplexType.try_parse("Class<#{clazz_name}>") - # when resolving method calls inside this block, we - # should be working inside Class, not T - that's the - # whole point of 'class_eval'. e.g., def foo defines it - # as an instance method in the class, like Foo.def - # instead of Foo.new.def - binder = context scope = :class end block_pin = Solargraph::Pin::Block.new( @@ -29,7 +23,6 @@ def process closure: region.closure, node: node, context: context, - binder: binder, receiver: node.children[0], comments: comments_for(node), scope: scope, diff --git a/lib/solargraph/pin/block.rb b/lib/solargraph/pin/block.rb index bd9cf8a52..951486d5b 100644 --- a/lib/solargraph/pin/block.rb +++ b/lib/solargraph/pin/block.rb @@ -34,8 +34,7 @@ def rebind api_map def binder out = @rebind if @rebind&.defined? - out ||= @binder - out ||= closure.binder + out ||= super end def context From dc5237b12139090d1939c1ee784d099ace020277 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 14 Nov 2025 08:39:26 -0500 Subject: [PATCH 644/930] Drop Method's binder param --- lib/solargraph/pin/method.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 94d254ddc..f93dd06d2 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -23,10 +23,8 @@ class Method < Callable # @param signatures [::Array, nil] # @param anon_splat [Boolean] # @param context [ComplexType, nil] - # @param binder [ComplexType, nil] def initialize visibility: :public, explicit: true, block: :undefined, node: nil, attribute: false, signatures: nil, anon_splat: false, - context: nil, binder: nil, - **splat + context: nil, **splat super(**splat) @visibility = visibility @explicit = explicit From cdc6de74c93d7e580ee6876aa86136400765300e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 14 Nov 2025 08:40:38 -0500 Subject: [PATCH 645/930] Drop binder --- lib/solargraph/parser/parser_gem/node_processors/block_node.rb | 1 - 1 file changed, 1 deletion(-) 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 0fb787eb1..e653fd95e 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/block_node.rb @@ -9,7 +9,6 @@ class BlockNode < Parser::NodeProcessor::Base def process location = get_node_location(node) - binder = nil scope = region.scope || region.closure.context.scope if other_class_eval? clazz_name = unpack_name(node.children[0].children[0]) From f2db36519c7535c1df773da6ae782c4e1c578111 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 14 Nov 2025 08:44:33 -0500 Subject: [PATCH 646/930] Drop binder --- lib/solargraph/pin/block.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/solargraph/pin/block.rb b/lib/solargraph/pin/block.rb index 951486d5b..8f36589f9 100644 --- a/lib/solargraph/pin/block.rb +++ b/lib/solargraph/pin/block.rb @@ -12,16 +12,14 @@ class Block < Callable attr_reader :node # @param receiver [Parser::AST::Node, nil] - # @param binder [Pin::Namespace, nil] # @param node [Parser::AST::Node, nil] # @param context [ComplexType, nil] # @param args [::Array] - def initialize receiver: nil, binder: nil, args: [], context: nil, node: nil, **splat + def initialize receiver: nil, args: [], context: nil, node: nil, **splat super(**splat, parameters: args) @receiver = receiver @context = context @return_type = ComplexType.parse('::Proc') - @binder = binder if binder @node = node @name = '' end From 786375e3c1072a27c29ebcb817f718b0e04be856 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 14 Nov 2025 08:47:42 -0500 Subject: [PATCH 647/930] Drop closure rebinding --- lib/solargraph/pin/block.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/pin/block.rb b/lib/solargraph/pin/block.rb index 8f36589f9..75cb20fa2 100644 --- a/lib/solargraph/pin/block.rb +++ b/lib/solargraph/pin/block.rb @@ -94,7 +94,6 @@ def maybe_rebind api_map chain = Parser.chain(receiver, location.filename, node) locals = api_map.source_map(location.filename).locals_at(location) - closure.rebind api_map receiver_pin = chain.define(api_map, closure, locals).first return ComplexType::UNDEFINED unless receiver_pin From 17b120b30a824073419a00c5a7cc2f297d308016 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 14 Nov 2025 08:53:50 -0500 Subject: [PATCH 648/930] Drop reset_generated! --- lib/solargraph/pin/common.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 90bb434cd..9cee855f9 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -6,9 +6,6 @@ module Common # @!method source # @abstract # @return [Source, nil] - # @!method reset_generated! - # @abstract - # @return [void] # @type @closure [Pin::Closure, nil] # @return [Location] From 62a447c70a9fbf2bf6fe9ecd8b157332d132f7fe Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 14 Nov 2025 08:58:19 -0500 Subject: [PATCH 649/930] Drop binder --- lib/solargraph/pin/method.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index f93dd06d2..1cb506951 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -34,7 +34,6 @@ def initialize visibility: :public, explicit: true, block: :undefined, node: nil @signatures = signatures @anon_splat = anon_splat @context = context if context - @binder = binder if binder end # @return [Array] From 379daf23f163268f31cdfd324f5570f02618fa58 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 14 Nov 2025 09:03:54 -0500 Subject: [PATCH 650/930] Document logic --- lib/solargraph/api_map.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 2abda62d8..5ed82f408 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -362,6 +362,8 @@ def var_at_location(locals, name, closure, location) with_correct_name = locals.select { |pin| pin.name == name} with_presence = with_correct_name.reject { |pin| pin.presence.nil? } vars_at_location = with_presence.reject do |pin| + # visible_at? excludes the starting position, but we want to + # include it for this purpose (!pin.visible_at?(closure, location) && !pin.starts_at?(location)) end From fc4f00fb047ede2561afe01a5c0616644c4c94a6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 14 Nov 2025 09:11:39 -0500 Subject: [PATCH 651/930] Use reset_generated! instead, so future cases are handled --- lib/solargraph/pin/common.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 9cee855f9..04a886aa0 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -6,6 +6,9 @@ module Common # @!method source # @abstract # @return [Source, nil] + # @!method reset_generated! + # @abstract + # @return [void] # @type @closure [Pin::Closure, nil] # @return [Location] @@ -16,9 +19,7 @@ module Common def closure=(value) @closure = value # remove cached values generated from closure - @context = nil - @binder = nil - @path = nil + reset_generated! end # @sg-ignore Solargraph::Pin::Common#closure return type could not be inferred From 7be2cd10a59ddf98e523a46507014f18705a8cd6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 14 Nov 2025 09:18:46 -0500 Subject: [PATCH 652/930] Revert refactor --- lib/solargraph/source/chain/call.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 0688643b4..d7a1b492a 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -94,10 +94,7 @@ def inferred_pins pins, api_map, name_pin, locals match = ol.parameters.any?(&:restarg?) break end - arg_name_pin = Pin::ProxyType.anonymous(name_pin.context, - closure: name_pin.closure, - source: :chain) - atype = atypes[idx] ||= arg.infer(api_map, arg_name_pin, locals) + atype = atypes[idx] ||= arg.infer(api_map, Pin::ProxyType.anonymous(name_pin.context, source: :chain), locals) unless param.compatible_arg?(atype, api_map) || param.restarg? match = false break From 401b3e6b5e443df537ecc69834e8b511b959b33a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 14 Nov 2025 09:20:49 -0500 Subject: [PATCH 653/930] Reformat --- lib/solargraph/source/chain/call.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index d7a1b492a..1294e8136 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -321,8 +321,8 @@ def block_call_type(api_map, name_pin, locals) block_pin = find_block_pin(api_map) # We use the block pin as the closure, as the parameters - # here will only be defined inside the block itself and we need to be able to see them - + # here will only be defined inside the block itself and we + # need to be able to see them block.infer(api_map, block_pin, locals) end end From a7cc436a2338d4eef00d9f92119535612846813a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 09:14:18 -0500 Subject: [PATCH 654/930] Fix solargraph-rspec specs --- lib/solargraph/pin/base.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 38773ebfe..e8dc16780 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -175,6 +175,9 @@ def reset_generated! # Same with @directives, @macros, @maybe_directives, which # regenerate docstring @deprecated = nil + @context = nil + @binder = nil + @path = nil reset_conversions end From fd463fe5df33f71e5d86422eb6c1b39801550fa6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 09:50:04 -0500 Subject: [PATCH 655/930] Try rubygems: latest --- .github/workflows/plugins.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index b5984f3cb..0e5398eb9 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -55,6 +55,8 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: '3.0' + # See https://github.com/castwide/solargraph/actions/runs/19000135777/job/54265647107?pr=1119 + rubygems: latest bundler-cache: false - uses: awalsh128/cache-apt-pkgs-action@latest with: From 8bbd6f56307a0597546ee07a3a2848031d31580d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 09:54:47 -0500 Subject: [PATCH 656/930] Fix new Ruby 4.x issue --- .github/workflows/rspec.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index ecc3d9771..8bf12c47f 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -35,6 +35,8 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} + # see https://github.com/castwide/solargraph/actions/runs/19391419903/job/55485410493?pr=1119 + bundler: Gemfile.lock bundler-cache: false - name: Set rbs version run: echo "gem 'rbs', '${{ matrix.rbs-version }}'" >> .Gemfile From cd2fd9a62fc529da64295daee0de2a0561e143d5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 09:56:47 -0500 Subject: [PATCH 657/930] Fix new Ruby 4.x issue --- .github/workflows/rspec.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 8bf12c47f..b213c5ed0 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -36,7 +36,9 @@ jobs: with: ruby-version: ${{ matrix.ruby-version }} # see https://github.com/castwide/solargraph/actions/runs/19391419903/job/55485410493?pr=1119 - bundler: Gemfile.lock + # + # match version in Gemfile.lock and use same version below + bundler: 2.5.23 bundler-cache: false - name: Set rbs version run: echo "gem 'rbs', '${{ matrix.rbs-version }}'" >> .Gemfile @@ -48,7 +50,7 @@ jobs: run: echo "gem 'tsort'" >> .Gemfile - name: Install gems run: | - bundle install + bundle _2.5.23_ install bundle update rbs # use latest available for this Ruby version - name: Run tests run: bundle exec rake spec From ce65d5fa283312fa24f4c7d3175a4188a961b587 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 10:34:14 -0500 Subject: [PATCH 658/930] Separate out possible bug reproduction from original purpose of spec --- spec/convention/gemfile_spec.rb | 4 ++-- spec/type_checker/levels/strict_spec.rb | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/spec/convention/gemfile_spec.rb b/spec/convention/gemfile_spec.rb index 827da7993..ab6a5ef1b 100644 --- a/spec/convention/gemfile_spec.rb +++ b/spec/convention/gemfile_spec.rb @@ -24,7 +24,7 @@ def type_checker code it 'finds bad arguments to DSL methods' do checker = type_checker(%( - source File + source 123 gemspec bad_name: 'solargraph' @@ -35,7 +35,7 @@ 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) + 'Wrong argument type for Bundler::Dsl#source: source expected String, received 123'].sort) end it 'finds bad arguments to DSL ruby method' do diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 47bf45a2c..38b267780 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -5,6 +5,18 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strict) end + it 'understands Class is not the same as String' do + checker = type_checker(%( + # @param str [String] + # @return [void] + def foo str; end + + foo File + )) + expect(checker.problems.map(&:message)) + .to eq(['Wrong argument type for #foo: str expected String, received Class']) + end + it 'handles compatible interfaces with self types on call' do checker = type_checker(%( # @param a [Enumerable] From c6dbe4d0899255deaa596f08b92494f90ef2886d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 11:19:09 -0500 Subject: [PATCH 659/930] Trigger build --- .github/workflows/plugins.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 2619b9ff8..b5984f3cb 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -143,7 +143,6 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - # solargraph-rails supports Ruby 3.0+ ruby-version: '3.0' bundler-cache: false From 9d2686301d53487871584ab3cbb690c256e44114 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 11:22:22 -0500 Subject: [PATCH 660/930] Fix new bundler issue --- .github/workflows/rspec.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index ecc3d9771..b213c5ed0 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -35,6 +35,10 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} + # see https://github.com/castwide/solargraph/actions/runs/19391419903/job/55485410493?pr=1119 + # + # match version in Gemfile.lock and use same version below + bundler: 2.5.23 bundler-cache: false - name: Set rbs version run: echo "gem 'rbs', '${{ matrix.rbs-version }}'" >> .Gemfile @@ -46,7 +50,7 @@ jobs: run: echo "gem 'tsort'" >> .Gemfile - name: Install gems run: | - bundle install + bundle _2.5.23_ install bundle update rbs # use latest available for this Ruby version - name: Run tests run: bundle exec rake spec From 87ec9b35adfa75103bb0702282874a806ef7c054 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 11:42:41 -0500 Subject: [PATCH 661/930] Debug --- .github/workflows/rspec.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index b213c5ed0..073594aac 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -52,6 +52,7 @@ jobs: run: | bundle _2.5.23_ install bundle update rbs # use latest available for this Ruby version + bundle list - name: Run tests run: bundle exec rake spec undercover: From 6a14bd7e09899beaaa9ee0370a42366ee81596f2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 12:28:38 -0500 Subject: [PATCH 662/930] Turn off warning diagnostics in CLI --- bin/solargraph | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/solargraph b/bin/solargraph index d85561700..248dc42fd 100755 --- a/bin/solargraph +++ b/bin/solargraph @@ -1,5 +1,8 @@ #!/usr/bin/env ruby +# turn off warning diagnostics from Ruby +$VERBOSE=nil + require 'solargraph' Solargraph::Shell.start(ARGV) From 7e7604bdfc5d76af86c5b7f5b25ade3d2201b52e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 13:38:08 -0500 Subject: [PATCH 663/930] Debug --- .github/workflows/rspec.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 7b8ba9bfb..2743ca84f 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -61,6 +61,7 @@ jobs: bundle _2.5.23_ install bundle update rbs # use latest available for this Ruby version bundle list + bundle exec solargraph pin 'Bundler::Dsl#source' - name: Update types run: | bundle exec rbs collection update From 91d3cd4a981dcf651d1fa45ad78b96e2dd8bedbf Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 18:12:51 -0500 Subject: [PATCH 664/930] Fix pin merging bug --- lib/solargraph/pin/base_variable.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 764c1fb39..fbf22cbaa 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -12,8 +12,9 @@ class BaseVariable < Base attr_accessor :mass_assignment # @param return_type [ComplexType, nil] + # @param mass_assignment [::Array(Parser::AST::Node, Integer), nil] # @param assignment [Parser::AST::Node, nil] - def initialize assignment: nil, return_type: nil, **splat + def initialize assignment: nil, return_type: nil, mass_assignment: nil, **splat super(**splat) @assignment = assignment # @type [nil, ::Array(Parser::AST::Node, Integer)] @@ -22,12 +23,12 @@ def initialize assignment: nil, return_type: nil, **splat end def combine_with(other, attrs={}) - attrs.merge({ + new_attrs = attrs.merge({ assignment: assert_same(other, :assignment), mass_assignment: assert_same(other, :mass_assignment), return_type: combine_return_type(other), }) - super(other, attrs) + super(other, new_attrs) end def completion_item_kind From 652fcd218c0532455baf88c0f05295f862f3dbf3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 18:32:44 -0500 Subject: [PATCH 665/930] Add @sg-ignore --- lib/solargraph/pin/base_variable.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index fbf22cbaa..da1f15cb2 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -28,6 +28,7 @@ def combine_with(other, attrs={}) mass_assignment: assert_same(other, :mass_assignment), return_type: combine_return_type(other), }) + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 super(other, new_attrs) end From fe06cf3e2fe8e22e3efefe46fe85e9adaa0913f5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 19:08:24 -0500 Subject: [PATCH 666/930] Add @sg-ignore --- lib/solargraph/yardoc.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 09bcd4586..0afdf1482 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -23,6 +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 + # @sg-ignore Unrecognized keyword argument chdir to Open3.capture2e 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}" } From 9b6aa30353fb22baeee09ebaa573a96021d68db1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 19:13:25 -0500 Subject: [PATCH 667/930] Pull in solargraph-rspec fixes from another branch --- .github/workflows/plugins.yml | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 1c633fda0..fa704e2ae 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -116,19 +116,39 @@ jobs: - name: clone https://github.com/lekemula/solargraph-rspec/ run: | cd .. - git clone https://github.com/lekemula/solargraph-rspec.git + # git clone https://github.com/lekemula/solargraph-rspec.git + + # pending https://github.com/lekemula/solargraph-rspec/pull/30 + git clone https://github.com/apiology/solargraph-rspec.git cd solargraph-rspec + git checkout reset_closures - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.4 + ruby-version: '3.1' + rubygems: latest bundler-cache: false - name: Install gems run: | + set -x + cd ../solargraph-rspec echo "gem 'solargraph', path: '../solargraph'" >> Gemfile bundle config path ${{ env.BUNDLE_PATH }} bundle install --jobs 4 --retry 3 + bundle exec appraisal install + # @todo some kind of appraisal/bundle conflict? + # https://github.com/castwide/solargraph/actions/runs/19038710934/job/54369767122?pr=1116 + # /home/runner/work/solargraph/solargraph-rspec/vendor/bundle/ruby/3.1.0/gems/bundler-2.6.9/lib/bundler/runtime.rb:317:in + # `check_for_activated_spec!': You have already activated date + # 3.5.0, but your Gemfile requires date 3.4.1. Prepending + # `bundle exec` to your command may solve + # this. (Gem::LoadError) + bundle exec appraisal update date + # For some reason on ruby 3.1 it defaults to an old version: 1.3.2 + # https://github.com/lekemula/solargraph-rspec/actions/runs/17814581205/job/50645370316?pr=22 + # We update manually to the latest + bundle exec appraisal update rspec-rails - name: Configure .solargraph.yml run: | cd ../solargraph-rspec @@ -136,12 +156,13 @@ jobs: - name: Solargraph generate RSpec gems YARD and RBS pins run: | cd ../solargraph-rspec - rspec_gems=$(bundle exec ruby -r './lib/solargraph-rspec' -e 'puts Solargraph::Rspec::Gems.gem_names.join(" ")' 2>/dev/null | tail -n1) - bundle exec solargraph gems $rspec_gems + bundle exec appraisal rbs collection update + rspec_gems=$(bundle exec appraisal ruby -r './lib/solargraph-rspec' -e 'puts Solargraph::Rspec::Gems.gem_names.join(" ")' 2>/dev/null | tail -n1) + bundle exec appraisal solargraph gems $rspec_gems - name: Run specs run: | cd ../solargraph-rspec - bundle exec rspec --format progress + bundle exec appraisal rspec --format progress run_solargraph_rails_specs: # check out solargraph-rails as well as this project, and point the former to use the latter as a local gem From 19eae82aa99710ddf1882abd841406b270b3ec24 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 19:23:51 -0500 Subject: [PATCH 668/930] Drop @sg-ignores --- lib/solargraph/pin/base_variable.rb | 1 - lib/solargraph/shell.rb | 13 ------------- 2 files changed, 14 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index da1f15cb2..fbf22cbaa 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -28,7 +28,6 @@ def combine_with(other, attrs={}) mass_assignment: assert_same(other, :mass_assignment), return_type: combine_return_type(other), }) - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 super(other, new_attrs) end diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 046a74296..3b1c4952d 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -241,24 +241,17 @@ def list puts "#{workspace.filenames.length} files total." end - # @sg-ignore Unresolved call to desc desc 'pin [PATH]', 'Describe a pin', hide: true - # @sg-ignore Unresolved call to option option :rbs, type: :boolean, desc: 'Output the pin as RBS', default: false - # @sg-ignore Unresolved call to option option :typify, type: :boolean, desc: 'Output the calculated return type of the pin from annotations', default: false - # @sg-ignore Unresolved call to option option :references, type: :boolean, desc: 'Show references', default: false - # @sg-ignore Unresolved call to option option :probe, type: :boolean, desc: 'Output the calculated return type of the pin from annotations and inference', default: false - # @sg-ignore Unresolved call to option option :stack, type: :boolean, desc: 'Show entire stack of a method pin by including definitions in superclasses', default: false # @param path [String] The path to the method pin, e.g. 'Class#method' or 'Class.method' # @return [void] def pin path api_map = Solargraph::ApiMap.load_with_cache('.', $stderr) is_method = path.include?('#') || path.include?('.') - # @sg-ignore Unresolved call to options if is_method && options[:stack] scope, ns, meth = if path.include? '#' [:instance, *path.split('#', 2)] @@ -280,7 +273,6 @@ def pin path $stderr.puts "Pin not found for path '#{path}'" exit 1 when Pin::Namespace - # @sg-ignore Unresolved call to options if options[:references] superclass_tag = api_map.qualify_superclass(pin.return_type.tag) superclass_pin = api_map.get_path_pins(superclass_tag).first if superclass_tag @@ -289,12 +281,9 @@ def pin path 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 @@ -338,7 +327,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 @@ -349,7 +337,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 482671b6a1ff201c292d47b9bab1b15533037bb8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 19:26:16 -0500 Subject: [PATCH 669/930] Add @sg-ignore --- lib/solargraph/parser/parser_gem/node_methods.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 02f790c00..b77c4cd47 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 Wrong argument type for AST::Node.new: type expected AST::_ToSym, received :nil NIL_NODE = ::Parser::AST::Node.new(:nil) # @param node [Parser::AST::Node] From b1d0b21818319684d51cc9e303f24448de565f43 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 20:06:28 -0500 Subject: [PATCH 670/930] Drop @sg-ignore --- lib/solargraph/yardoc.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 0afdf1482..09bcd4586 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -23,7 +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 Unrecognized keyword argument chdir to Open3.capture2e 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}" } From 973b512b121a23bd53fc77c3d2c6caa718c511e2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 20:21:11 -0500 Subject: [PATCH 671/930] Use consistent ruby versions for typechecking --- .github/workflows/plugins.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index fa704e2ae..c7ad72cb4 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -23,7 +23,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: 3.4 bundler-cache: true - uses: awalsh128/cache-apt-pkgs-action@latest with: @@ -54,7 +54,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: 3.4 bundler-cache: false - uses: awalsh128/cache-apt-pkgs-action@latest with: @@ -83,7 +83,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: 3.4 bundler-cache: false - uses: awalsh128/cache-apt-pkgs-action@latest with: From 390d68a1122525904df6f8b393d359874bb33129 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 21:42:34 -0500 Subject: [PATCH 672/930] Remove @sg-ignores --- lib/solargraph/parser/parser_gem/node_methods.rb | 2 -- lib/solargraph/pin/closure.rb | 1 - lib/solargraph/pin/common.rb | 1 - 3 files changed, 4 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index b92dfe575..59d705120 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -119,8 +119,6 @@ def convert_hash node result end - # @sg-ignore Wrong argument type for AST::Node.new: type - # expected AST::_ToSym, received :nil NIL_NODE = ::Parser::AST::Node.new(:nil) # @param node [Parser::AST::Node] diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index bd372cc45..a644f3791 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -44,7 +44,6 @@ def context end end - # @sg-ignore Solargraph::Pin::Closure#binder return type could not be inferred # @return [Solargraph::ComplexType] def binder @binder || context diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index a7d4beb0d..646e79149 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -22,7 +22,6 @@ def closure=(value) reset_generated! end - # @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 From f9114dc87aa4dd92a556f90930921933a0e97c36 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 21:44:59 -0500 Subject: [PATCH 673/930] Add @sg-ignores --- lib/solargraph/api_map/store.rb | 3 +++ lib/solargraph/workspace/require_paths.rb | 1 + 2 files changed, 4 insertions(+) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 480b77525..b43ae580e 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -242,8 +242,11 @@ def get_ancestors(fqns) next if refs.nil? # @param ref [String] refs.map(&:type).map(&:to_s).each do |ref| + # @sg-ignore We should understand reassignment of variable to new type next if ref.nil? || ref.empty? || visited.include?(ref) + # @sg-ignore We should understand reassignment of variable to new type ancestors << ref + # @sg-ignore We should understand reassignment of variable to new type queue << ref end end diff --git a/lib/solargraph/workspace/require_paths.rb b/lib/solargraph/workspace/require_paths.rb index c8eea161b..10dce4053 100644 --- a/lib/solargraph/workspace/require_paths.rb +++ b/lib/solargraph/workspace/require_paths.rb @@ -83,6 +83,7 @@ def require_path_from_gemspec_file gemspec_file_path return [] if hash.empty? hash['paths'].map { |path| File.join(base, path) } rescue StandardError => e + # @sg-ignore Should handle redefinition of types in simple contexts Solargraph.logger.warn "Error reading #{gemspec_file_path}: [#{e.class}] #{e.message}" [] end From 680638dacf08b24ab56ec9fbb2d9a5c7a748509d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 22:33:17 -0500 Subject: [PATCH 674/930] Adjust @sg-ignores --- lib/solargraph/source/chain/call.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 7b515be2a..287d59f37 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -58,8 +58,6 @@ def resolve api_map, name_pin, locals # will be nil or result as if binder were not nil - # chain.rb#maybe_nil will add the nil type later, we just # need to worry about the not-nil case - - # @sg-ignore Need to handle duck-typed method calls on union types binder = binder.without_nil if nullable? pin_groups = binder.each_unique_type.map do |context| ns_tag = context.namespace == '' ? '' : context.namespace_type.tag @@ -256,7 +254,7 @@ def extra_return_type docstring, context def find_method_pin(name_pin) method_pin = name_pin until method_pin.is_a?(Pin::Method) - # @sg-ignore Reassignment as a function of itself issue + # @sg-ignore Need to support this in flow-sensitive typing method_pin = method_pin.closure return if method_pin.nil? end From 6f2e8bb9a45740437eb4bc1052b41be4aef2d56a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 22:38:31 -0500 Subject: [PATCH 675/930] Restore @sg-ignores --- lib/solargraph/source/chain/call.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 287d59f37..9adea1a1e 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -58,6 +58,8 @@ def resolve api_map, name_pin, locals # will be nil or result as if binder were not nil - # chain.rb#maybe_nil will add the nil type later, we just # need to worry about the not-nil case + + # @sg-ignore Need to handle duck-typed method calls on union types binder = binder.without_nil if nullable? pin_groups = binder.each_unique_type.map do |context| ns_tag = context.namespace == '' ? '' : context.namespace_type.tag From 4f94ef8c2a25f5fc80a7f38e69915022935aca24 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 22:48:36 -0500 Subject: [PATCH 676/930] Fix issue revealed by this PR's change --- lib/solargraph/pin/base_variable.rb | 5 +---- lib/solargraph/pin/local_variable.rb | 3 --- lib/solargraph/pin/parameter.rb | 1 - lib/solargraph/source/chain.rb | 11 +++++------ spec/type_checker/levels/strong_spec.rb | 24 ++++++++++++++++++++++-- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 21f8bf3e2..5d7f78620 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -31,15 +31,12 @@ def reset_generated! end def combine_with(other, attrs={}) - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 new_assignments = combine_assignments(other) new_attrs = attrs.merge({ assignments: new_assignments, - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 mass_assignment: combine_mass_assignment(other), return_type: combine_return_type(other), - }) - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 + }) super(other, new_attrs) end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 4b97feb21..a4c190261 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -23,12 +23,9 @@ def combine_with(other, attrs={}) # keep this as a parameter return other.combine_with(self, attrs) if other.is_a?(Parameter) && !self.is_a?(Parameter) - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 new_assignments = combine_assignments(other) new_attrs = attrs.merge({ - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 presence: combine_presence(other), - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 presence_certain: combine_presence_certain(other), }) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 4018d1201..af02a2f1c 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -42,7 +42,6 @@ def combine_with(other, attrs={}) asgn_code: asgn_code } end - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 super(other, new_attrs.merge(attrs)) end diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index e24b11c92..db89f271f 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -210,11 +210,11 @@ def to_s private # @param pins [::Array] - # @param context [Pin::Base] + # @param name_pin [Pin::Base] # @param api_map [ApiMap] # @param locals [::Enumerable] # @return [ComplexType] - def infer_from_definitions pins, context, api_map, locals + def infer_from_definitions pins, name_pin, api_map, locals # @type [::Array] types = [] unresolved_pins = [] @@ -233,7 +233,7 @@ def infer_from_definitions pins, context, api_map, locals # @todo even at strong, no typechecking complaint # happens when a [Pin::Base,nil] is passed into a method # that accepts only [Pin::Namespace] as an argument - type = type.resolve_generics(pin.closure, context.binder) + type = type.resolve_generics(pin.closure, name_pin.binder) end types << type else @@ -270,12 +270,11 @@ def infer_from_definitions pins, context, api_map, locals else ComplexType.new(types) end - if context.nil? || context.return_type.undefined? + if name_pin.nil? || name_pin.context.undefined? # up to downstream to resolve self type return type end - - type.self_to_type(context.return_type) + type.self_to_type(name_pin.context) end # @param type [ComplexType] diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 1f0812fe4..85408191f 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -234,8 +234,6 @@ def bar end it 'understands complex use of self' do - pending 'https://github.com/castwide/solargraph/pull/1050' - checker = type_checker(%( class A # @param other [self] @@ -679,5 +677,27 @@ def baz(foo) expect(checker.problems.map(&:location).map(&:range).map(&:start)).to be_empty end + + it 'resolves self correctly in chained method calls' do + checker = type_checker(%( + class Foo + # @param other [self] + # + # @return [Symbol, nil] + def bar(other) + # @type [Symbol, nil] + baz(other) + end + + # @param other [self] + # + # @sg-ignore Missing @return tag + # @return [undefined] + def baz(other); end + end + )) + + expect(checker.problems.map(&:message)).to be_empty + end end end From c7028dce7b44fa67149cbc1ec8c6521b57e31854 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 23:21:05 -0500 Subject: [PATCH 677/930] Mark spec as pending --- spec/pin/base_variable_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/pin/base_variable_spec.rb b/spec/pin/base_variable_spec.rb index 2a49ac9f1..c87c31884 100644 --- a/spec/pin/base_variable_spec.rb +++ b/spec/pin/base_variable_spec.rb @@ -46,6 +46,8 @@ def bar end it "understands proc kwarg parameters aren't affected by @type" do + pending "understanding restarg in block param in Block#typify_parameters" + code = %( # @return [Proc] def foo From 0d854cda9699d07273ca8fba62b28568f53cb7f5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 23:29:40 -0500 Subject: [PATCH 678/930] Mark spec as pending --- spec/pin/base_variable_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/pin/base_variable_spec.rb b/spec/pin/base_variable_spec.rb index c87c31884..03e6b1a11 100644 --- a/spec/pin/base_variable_spec.rb +++ b/spec/pin/base_variable_spec.rb @@ -45,7 +45,7 @@ def bar expect(type.simplify_literals.to_rbs).to eq('(::Integer | ::NilClass)') end - it "understands proc kwarg parameters aren't affected by @type" do + xit "understands proc kwarg parameters aren't affected by @type" do pending "understanding restarg in block param in Block#typify_parameters" code = %( From f8ded08e41ae362757f08e1ede6fd0d1e56cc1e9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 15 Nov 2025 23:44:01 -0500 Subject: [PATCH 679/930] Add @sg-ignores --- lib/solargraph/library.rb | 2 ++ lib/solargraph/pin/delegated_method.rb | 1 + 2 files changed, 3 insertions(+) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 5c7851201..b0afaad4e 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -303,7 +303,9 @@ def locate_ref location return nil if pin.nil? # @param full [String] return_if_match = proc do |full| + # @sg-ignore Need to handle restarg parameters in block parameters if source_map_hash.key?(full) + # @sg-ignore Need to handle restarg parameters in block parameters return Location.new(full, Solargraph::Range.from_to(0, 0, 0, 0)) end end diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index bcf5b5912..530804747 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 Need to handle restarg parameters in block parameters 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 ec29b25fd0f833ae778b750b9d9517c2726e9ba1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 00:13:45 -0500 Subject: [PATCH 680/930] Drop dead code --- lib/solargraph/pin/base_variable.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 16de9b7db..1a3488346 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -61,11 +61,6 @@ def combine_with(other, attrs={}) super(other, new_attrs) end - def reset_generated! - @return_type_minus_exclusions = nil - super - end - def inner_desc super + ", intersection_return_type=#{intersection_return_type&.rooted_tags.inspect}, exclude_return_type=#{exclude_return_type&.rooted_tags.inspect}" end From 57a0cf807fdf2b450d33ae31af670f7a1a64070e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 00:22:41 -0500 Subject: [PATCH 681/930] Fix merge --- lib/solargraph/source/chain/call.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 52b924751..5f2603ec4 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -94,6 +94,7 @@ def inferred_pins pins, api_map, name_pin, locals # passing a block, we want to find a signature that will # use it. If we didn't pass a block, the logic below will # reject it regardless + with_block, without_block = overloads.partition(&:block?) sorted_overloads = with_block + without_block # @type [Pin::Signature, nil] @@ -110,7 +111,8 @@ def inferred_pins pins, api_map, name_pin, locals break end arg_name_pin = Pin::ProxyType.anonymous(name_pin.context, - gates: name_pin.gates, closure: name_pin.closure, + closure: name_pin.closure, + 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? From 6fb550c077bcc6ea717ae2ffb4cc6f5b56f5cdfd Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 00:23:45 -0500 Subject: [PATCH 682/930] Fix merge --- lib/solargraph/source/chain/call.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 5f2603ec4..b0ca017bb 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -293,6 +293,7 @@ def super_pins api_map, name_pin def yield_pins api_map, name_pin method_pin = find_method_pin(name_pin) return [] unless method_pin + method_pin.signatures.map(&:block).compact.map do |signature_pin| return_type = signature_pin.return_type.qualify(api_map, *name_pin.gates) signature_pin.proxy(return_type) @@ -354,7 +355,6 @@ def block_call_type(api_map, name_pin, locals) return nil unless with_block? block_pin = find_block_pin(api_map) - # We use the block pin as the closure, as the parameters # here will only be defined inside the block itself and we # need to be able to see them From 8c1f056cf1deaab7bcdcbc776e64f7091ef46346 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 10:56:35 -0500 Subject: [PATCH 683/930] 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 f20072071..b4ca2e172 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -215,6 +215,7 @@ def method_param_type_problems_for pin # @param name [String] # @param data [Hash{Symbol => BasicObject}] params.each_pair do |name, data| + # @sg-ignore Need typed hashes # @type [ComplexType] type = data[:qualified] if type.undefined? From 4d0366b73243fc94211d39a6ba38b9db1907fb98 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 10:58:54 -0500 Subject: [PATCH 684/930] Use latest rubygems --- .github/workflows/plugins.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index b0ab6ecec..99bbc386a 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -180,6 +180,8 @@ jobs: # solargraph-rails supports Ruby 3.0+ ruby-version: '3.0' bundler-cache: false + # https://github.com/apiology/solargraph/actions/runs/19400815835/job/55508092473?pr=17 + rubygems: latest bundler: latest env: MATRIX_RAILS_VERSION: "7.0" From 963c13538f2e05650610d345a5df7dbd8d2a07e5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 11:30:28 -0500 Subject: [PATCH 685/930] Remove unused file This was replaced by yard_map/mapper/to_method.rb --- lib/solargraph/yard_map/to_method.rb | 88 ---------------------------- 1 file changed, 88 deletions(-) delete mode 100644 lib/solargraph/yard_map/to_method.rb diff --git a/lib/solargraph/yard_map/to_method.rb b/lib/solargraph/yard_map/to_method.rb deleted file mode 100644 index 3ecb7ac26..000000000 --- a/lib/solargraph/yard_map/to_method.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -module Solargraph - class YardMap - class ToMethod - module InnerMethods - module_function - - # @param code_object [YARD::CodeObjects::Base] - # @param location [Solargraph::Location] - # @param comments [String] - # @return [Array] - def get_parameters code_object, location, comments - return [] unless code_object.is_a?(YARD::CodeObjects::MethodObject) - # HACK: Skip `nil` and `self` parameters that are sometimes emitted - # for methods defined in C - # See https://github.com/castwide/solargraph/issues/345 - code_object.parameters.select { |a| a[0] && a[0] != 'self' }.map do |a| - Solargraph::Pin::Parameter.new( - location: location, - closure: self, - comments: comments, - name: arg_name(a), - presence: nil, - decl: arg_type(a), - asgn_code: a[1], - source: :yard_map - ) - end - end - - # @param a [Array] - # @return [String] - def arg_name a - a[0].gsub(/[^a-z0-9_]/i, '') - end - - # @param a [Array] - # @return [::Symbol] - def arg_type a - if a[0].start_with?('**') - :kwrestarg - elsif a[0].start_with?('*') - :restarg - elsif a[0].start_with?('&') - :blockarg - elsif a[0].end_with?(':') - a[1] ? :kwoptarg : :kwarg - elsif a[1] - :optarg - else - :arg - end - end - end - private_constant :InnerMethods - - include Helpers - - # @param code_object [YARD::CodeObjects::Base] - # @param name [String, nil] - # @param scope [Symbol, nil] - # @param visibility [Symbol, nil] - # @param closure [Solargraph::Pin::Base, nil] - # @param spec [Solargraph::Pin::Base, nil] - # @return [Solargraph::Pin::Method] - def make code_object, name = nil, scope = nil, visibility = nil, closure = nil, spec = nil - closure ||= Solargraph::Pin::Namespace.new( - name: code_object.namespace.to_s, - gates: [code_object.namespace.to_s] - ) - location = object_location(code_object, spec) - comments = code_object.docstring ? code_object.docstring.all.to_s : '' - Pin::Method.new( - location: location, - closure: closure, - name: name || code_object.name.to_s, - comments: comments, - scope: scope || code_object.scope, - visibility: visibility || code_object.visibility, - parameters: InnerMethods.get_parameters(code_object, location, comments), - explicit: code_object.is_explicit?, - source: :yard_map - ) - end - end - end -end From a12d959704c7151323ed022f4f6e3dcd957aa9c4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 12:46:06 -0500 Subject: [PATCH 686/930] Add failing spec --- spec/type_checker/levels/alpha_spec.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 781907d33..253efb3d3 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -73,5 +73,25 @@ def catalog expect(checker.problems.map(&:message)).to eq([]) end + + it 'can infer types based on || and &&' do + checker = type_checker(%( + class Baz + # @param bar [String, nil] + # @return [Boolean, String] + def foo bar + !bar || bar.upcase + end + + # @param bar [String, nil] + # @return [String, nil] + def bing bar + bar && bar.upcase + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end end end From 71f0e746174510663f2725aaa2e266b9157b6da5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 12:55:03 -0500 Subject: [PATCH 687/930] Add implementation --- .../parser/parser_gem/node_processors.rb | 4 +++- .../parser_gem/node_processors/or_node.rb | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 lib/solargraph/parser/parser_gem/node_processors/or_node.rb diff --git a/lib/solargraph/parser/parser_gem/node_processors.rb b/lib/solargraph/parser/parser_gem/node_processors.rb index e80970344..5f1634bba 100644 --- a/lib/solargraph/parser/parser_gem/node_processors.rb +++ b/lib/solargraph/parser/parser_gem/node_processors.rb @@ -30,6 +30,7 @@ module NodeProcessors autoload :WhenNode, 'solargraph/parser/parser_gem/node_processors/when_node' autoload :WhileNode, 'solargraph/parser/parser_gem/node_processors/while_node' autoload :AndNode, 'solargraph/parser/parser_gem/node_processors/and_node' + autoload :OrNode, 'solargraph/parser/parser_gem/node_processors/or_node' end end @@ -64,9 +65,10 @@ module NodeProcessor register :op_asgn, ParserGem::NodeProcessors::OpasgnNode register :sym, ParserGem::NodeProcessors::SymNode register :until, ParserGem::NodeProcessors::UntilNode + register :when, ParserGem::NodeProcessors::WhenNode register :while, ParserGem::NodeProcessors::WhileNode register :and, ParserGem::NodeProcessors::AndNode - register :when, ParserGem::NodeProcessors::WhenNode + register :or, ParserGem::NodeProcessors::OrNode end end end diff --git a/lib/solargraph/parser/parser_gem/node_processors/or_node.rb b/lib/solargraph/parser/parser_gem/node_processors/or_node.rb new file mode 100644 index 000000000..c77bad1d6 --- /dev/null +++ b/lib/solargraph/parser/parser_gem/node_processors/or_node.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Solargraph + module Parser + module ParserGem + module NodeProcessors + class OrNode < Parser::NodeProcessor::Base + include ParserGem::NodeMethods + + def process + process_children + + FlowSensitiveTyping.new(locals, + enclosing_breakable_pin, + enclosing_compound_statement_pin).process_or(node) + end + end + end + end + end +end From a7b7833dd015ff965875658c2c843121fd1af3b5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 14:09:11 -0500 Subject: [PATCH 688/930] Fix @sg-ignore issues --- lib/solargraph/pin/base_variable.rb | 5 +---- lib/solargraph/shell.rb | 15 --------------- lib/solargraph/source/chain/or.rb | 1 + 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index c5010dd20..cad335041 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -76,12 +76,9 @@ def combine_with(other, attrs={}) assignments: new_assignments, mass_assignment: combine_mass_assignment(other), return_type: combine_return_type(other), - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 intersection_return_type: combine_types(other, :intersection_return_type), - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 exclude_return_type: combine_types(other, :exclude_return_type), }) - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 super(other, new_attrs) end @@ -252,7 +249,7 @@ def combine_closure(other) end # if filenames are different, this will just pick one - # @sg-ignore flow sensitive typing needs to handle ivars + # @todo flow sensitive typing needs to handle ivars return closure if closure.location <= other.closure.location other.closure diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 1802ba746..eb4e65175 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -176,7 +176,6 @@ def reporters # @return [void] def typecheck *files directory = File.realpath(options[:directory]) - # @sg-ignore Unresolved call to options level = options[:level].to_sym rules = Solargraph::TypeChecker::Rules.new(level) api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout, loose_unions: rules.loose_unions?) @@ -189,7 +188,6 @@ def typecheck *files filecount = 0 time = Benchmark.measure { files.each do |file| - # @sg-ignore Unresolved call to options checker = TypeChecker.new(file, api_map: api_map, rules: rules, level: options[:level].to_sym) problems = checker.problems next if problems.empty? @@ -248,24 +246,17 @@ def list puts "#{workspace.filenames.length} files total." end - # @sg-ignore Unresolved call to desc desc 'pin [PATH]', 'Describe a pin', hide: true - # @sg-ignore Unresolved call to option option :rbs, type: :boolean, desc: 'Output the pin as RBS', default: false - # @sg-ignore Unresolved call to option option :typify, type: :boolean, desc: 'Output the calculated return type of the pin from annotations', default: false - # @sg-ignore Unresolved call to option option :references, type: :boolean, desc: 'Show references', default: false - # @sg-ignore Unresolved call to option option :probe, type: :boolean, desc: 'Output the calculated return type of the pin from annotations and inference', default: false - # @sg-ignore Unresolved call to option option :stack, type: :boolean, desc: 'Show entire stack of a method pin by including definitions in superclasses', default: false # @param path [String] The path to the method pin, e.g. 'Class#method' or 'Class.method' # @return [void] def pin path api_map = Solargraph::ApiMap.load_with_cache('.', $stderr) is_method = path.include?('#') || path.include?('.') - # @sg-ignore Unresolved call to options if is_method && options[:stack] scope, ns, meth = if path.include? '#' [:instance, *path.split('#', 2)] @@ -287,7 +278,6 @@ def pin path $stderr.puts "Pin not found for path '#{path}'" exit 1 when Pin::Namespace - # @sg-ignore Unresolved call to options if options[:references] superclass_tag = api_map.qualify_superclass(pin.return_type.tag) superclass_pin = api_map.get_path_pins(superclass_tag).first if superclass_tag @@ -296,12 +286,9 @@ def pin path 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 @@ -345,7 +332,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 @@ -356,7 +342,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 diff --git a/lib/solargraph/source/chain/or.rb b/lib/solargraph/source/chain/or.rb index fd56b52c0..884b06fd1 100644 --- a/lib/solargraph/source/chain/or.rb +++ b/lib/solargraph/source/chain/or.rb @@ -17,6 +17,7 @@ def resolve api_map, name_pin, locals types = @links.map { |link| link.infer(api_map, name_pin, locals) } combined_type = Solargraph::ComplexType.new(types) unless types.all?(&:nullable?) + # @sg-ignore Unresolved call to without_nil on Solargraph::ComplexType combined_type = combined_type.without_nil end From b524eec6a291ee9bed1ab0a9eceb7b43653cf813 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 14:12:32 -0500 Subject: [PATCH 689/930] Fix solargraph-rails specs issue --- .github/workflows/plugins.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 29d5616cc..5f294259d 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -181,6 +181,8 @@ jobs: ruby-version: '3.0' bundler-cache: false bundler: latest + # https://github.com/castwide/solargraph/actions/runs/19410641794/job/55531497679?pr=1123 + rubygems: latest env: MATRIX_RAILS_VERSION: "7.0" - name: Install gems From 5e5b6e500eca9505df5c2cac69400a4ce55aad6d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 14:19:45 -0500 Subject: [PATCH 690/930] Drop workaround --- .github/workflows/plugins.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index fa704e2ae..61217409f 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -116,12 +116,8 @@ jobs: - name: clone https://github.com/lekemula/solargraph-rspec/ run: | cd .. - # git clone https://github.com/lekemula/solargraph-rspec.git - - # pending https://github.com/lekemula/solargraph-rspec/pull/30 - git clone https://github.com/apiology/solargraph-rspec.git + git clone https://github.com/lekemula/solargraph-rspec.git cd solargraph-rspec - git checkout reset_closures - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From 1d7106e9eb1a62856a2cd654619633eb668dc7b6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 14:23:51 -0500 Subject: [PATCH 691/930] Use is_a? in a simple if() with a union to refine types --- lib/solargraph/parser/flow_sensitive_typing.rb | 6 ++++++ spec/parser/flow_sensitive_typing_spec.rb | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index b6fa07863..1737dff27 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -316,6 +316,12 @@ def process_isa(isa_node, true_presences, false_presences) if_true[pin] ||= [] if_true[pin] << { type: ComplexType.parse(isa_type_name) } process_facts(if_true, true_presences) + + # @type Hash{Pin::LocalVariable => Array ComplexType}>} + if_false = {} + if_false[pin] ||= [] + if_false[pin] << { not_type: ComplexType.parse(isa_type_name) } + process_facts(if_false, false_presences) end # @param nilp_node [Parser::AST::Node] diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 992f19999..30aabd32c 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -38,8 +38,6 @@ def verify_repro(repr) end end ), 'test.rb') - pending 'FlowSensitiveTyping improvements' - api_map = Solargraph::ApiMap.new.map(source) clip = api_map.clip_at('test.rb', [7, 10]) expect(clip.infer.to_s).to eq('Repro1') From 254714504622cff62c431dd8ea1bed2786cdc9be Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 14:45:22 -0500 Subject: [PATCH 692/930] Fix specs --- spec/parser/flow_sensitive_typing_spec.rb | 28 +++++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 30aabd32c..f35ee097d 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -195,6 +195,22 @@ class Repro < ReproBase; end expect(clip.infer.to_s).to eq('Repro') end + it 'uses is_a? in a "break unless" statement in a while to refine types' do + source = Solargraph::Source.load_string(%( + class ReproBase; end + class Repro < ReproBase; end + # @type [ReproBase] + value = bar + while !is_done() + break unless value.is_a? Repro + value + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [7, 8]) + expect(clip.infer.to_s).to eq('Repro') + end + it 'uses unless is_a? in a ".each" block to refine types' do source = Solargraph::Source.load_string(%( # @type [Array] @@ -338,8 +354,6 @@ def verify_repro(repr) clip = api_map.clip_at('test.rb', [4, 8]) expect(clip.infer.rooted_tags).to eq('::Integer, nil') - pending 'FlowSensitiveTyping improvements' - clip = api_map.clip_at('test.rb', [6, 10]) expect(clip.infer.rooted_tags).to eq('nil') @@ -555,10 +569,8 @@ def verify_repro(repr, throw_the_dice) clip = api_map.clip_at('test.rb', [6, 10]) expect(clip.infer.rooted_tags).to eq('::Integer') - pending('supporting else after && on varname') - clip = api_map.clip_at('test.rb', [8, 10]) - expect(clip.infer.rooted_tags).to eq('nil') + expect(clip.infer.rooted_tags).to eq('::Integer, nil') end it 'uses variable in a simple if() to refine types' do @@ -605,7 +617,7 @@ def verify_repro(repr = nil) expect(clip.infer.rooted_tags).to eq('10') clip = api_map.clip_at('test.rb', [7, 10]) - expect(clip.infer.rooted_tags).to eq('nil') + expect(clip.infer.rooted_tags).to eq('nil, false') end it 'uses .nil? in a return if() in an if to refine types using nil checks' do @@ -668,8 +680,6 @@ def bar(arr, baz: nil) clip = api_map.clip_at('test.rb', [6, 10]) expect(clip.infer.rooted_tags).to eq('::Boolean, nil') - pending('better scoping of return if in blocks') - clip = api_map.clip_at('test.rb', [9, 12]) expect(clip.infer.rooted_tags).to eq('::Boolean') @@ -700,8 +710,6 @@ def bar(baz: nil) clip = api_map.clip_at('test.rb', [8, 12]) expect(clip.infer.rooted_tags).to eq('::Boolean') - pending('better scoping of return if in unless') - clip = api_map.clip_at('test.rb', [10, 10]) expect(clip.infer.rooted_tags).to eq('::Boolean, nil') end From c411211db398c49864a4b20a959d4aff74ce88fb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 15:06:26 -0500 Subject: [PATCH 693/930] Use same ruby version for all typechecking for consistency --- .github/workflows/plugins.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 5e8cbf607..f0301298e 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -23,7 +23,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: 3.4 bundler-cache: true - uses: awalsh128/cache-apt-pkgs-action@latest with: @@ -54,7 +54,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: 3.4 bundler-cache: false - uses: awalsh128/cache-apt-pkgs-action@latest with: @@ -83,7 +83,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: 3.4 bundler-cache: false - uses: awalsh128/cache-apt-pkgs-action@latest with: From 9c8ed514c1e9de86c21de45fac0f46a51a71031e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 15:18:12 -0500 Subject: [PATCH 694/930] Rename rule to match future PR --- lib/solargraph/shell.rb | 5 ++++- lib/solargraph/type_checker.rb | 8 +++++--- lib/solargraph/type_checker/rules.rb | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 1a8514922..faa1b8d6b 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -175,7 +175,10 @@ def typecheck *files # @sg-ignore Unresolved call to options level = options[:level].to_sym rules = Solargraph::TypeChecker::Rules.new(level) - api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout, loose_unions: rules.loose_unions?) + api_map = + Solargraph::ApiMap.load_with_cache(directory, $stdout, + loose_unions: + rules.require_all_unique_types_match_expected_on_lhs?) probcount = 0 if files.empty? files = api_map.source_maps.map(&:filename) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index bec458e94..a6fa5a9c4 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -30,7 +30,7 @@ def initialize filename, api_map: nil, level: :normal, rules: Rules.new(level) # @todo Smarter directory resolution @rules = rules @api_map = api_map || Solargraph::ApiMap.load(File.dirname(filename), - loose_unions: rules.loose_unions?) + loose_unions: rules.require_all_unique_types_match_expected_on_lhs?) # @type [Array] @marked_ranges = [] @@ -65,7 +65,8 @@ class << self def load filename, level = :normal source = Solargraph::Source.load(filename) rules = Rules.new(level) - api_map = Solargraph::ApiMap.new(loose_unions: rules.loose_unions?) + api_map = Solargraph::ApiMap.new(loose_unions: + rules.require_all_unique_types_match_expected_on_lhs?) api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end @@ -77,7 +78,8 @@ def load filename, level = :normal def load_string code, filename = nil, level = :normal source = Solargraph::Source.load_string(code, filename) rules = Rules.new(level) - api_map = Solargraph::ApiMap.new(loose_unions: rules.loose_unions?) + api_map = Solargraph::ApiMap.new(loose_unions: + rules.require_all_unique_types_match_expected_on_lhs?) api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 4984b3efe..166568f5c 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -50,7 +50,7 @@ def must_tag_or_infer? rank > LEVELS[:typed] end - def loose_unions? + def require_all_unique_types_match_expected_on_lhs? rank < LEVELS[:alpha] end From 7ea7bfdbf9cb21bd1b652e8a4305c778c29034e2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 15:30:32 -0500 Subject: [PATCH 695/930] Fix polarity --- lib/solargraph/shell.rb | 2 +- lib/solargraph/type_checker.rb | 6 +++--- lib/solargraph/type_checker/rules.rb | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index cd0ba1a9b..f68e4160a 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -178,7 +178,7 @@ def typecheck *files api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout, loose_unions: - rules.require_all_unique_types_match_expected_on_lhs?) + !rules.require_all_unique_types_match_expected_on_lhs?) probcount = 0 if files.empty? files = api_map.source_maps.map(&:filename) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index a6fa5a9c4..f0338f13c 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -30,7 +30,7 @@ def initialize filename, api_map: nil, level: :normal, rules: Rules.new(level) # @todo Smarter directory resolution @rules = rules @api_map = api_map || Solargraph::ApiMap.load(File.dirname(filename), - loose_unions: rules.require_all_unique_types_match_expected_on_lhs?) + loose_unions: !rules.require_all_unique_types_match_expected_on_lhs?) # @type [Array] @marked_ranges = [] @@ -66,7 +66,7 @@ def load filename, level = :normal source = Solargraph::Source.load(filename) rules = Rules.new(level) api_map = Solargraph::ApiMap.new(loose_unions: - rules.require_all_unique_types_match_expected_on_lhs?) + !rules.require_all_unique_types_match_expected_on_lhs?) api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end @@ -79,7 +79,7 @@ def load_string code, filename = nil, level = :normal source = Solargraph::Source.load_string(code, filename) rules = Rules.new(level) api_map = Solargraph::ApiMap.new(loose_unions: - rules.require_all_unique_types_match_expected_on_lhs?) + !rules.require_all_unique_types_match_expected_on_lhs?) api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 166568f5c..a02f5c837 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -51,7 +51,7 @@ def must_tag_or_infer? end def require_all_unique_types_match_expected_on_lhs? - rank < LEVELS[:alpha] + rank >= LEVELS[:alpha] end def validate_tags? From e26572c20cbda11f932d0ad0bc81a21c96ae4a17 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 16 Nov 2025 16:07:39 -0500 Subject: [PATCH 696/930] Update metrics --- lib/solargraph/type_checker/rules.rb | 17 ++++++----------- lib/solargraph/workspace/config.rb | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index ed3ee5263..919a19d63 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,16 +58,16 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end - # pending code fixes (~280): + # pending code fixes (277): # - # @todo 263: Need to add nil check herea + # @todo 263: Need to add nil check here # @todo 9: Need to validate config # @todo 3: Translate to something flow sensitive typing understands # @todo 2: Need a downcast here # - # flow-sensitive typing could handle (~100): + # flow-sensitive typing could handle (96): # - # @todo 47: flow sensitive typing needs to handle ivars + # @todo 48: flow sensitive typing needs to handle ivars # @todo 9: Should handle redefinition of types in simple contexts # @todo 6: need boolish support for ? methods # @todo 5: literal arrays in this module turn into ::Solargraph::Source::Chain::Array @@ -76,19 +76,14 @@ def require_inferred_type_params? # @todo 3: downcast output of Enumerable#select # @todo 3: flow sensitive typing needs to handle 'raise if' # @todo 2: Need to look at Tuple#include? handling - # @todo 2: should warn on nil dereference below # @todo 2: Should better support meaning of '&' in RBS # @todo 2: (*) flow sensitive typing needs to handle "if foo = bar" # @todo 2: Need to handle duck-typed method calls on union types + # @todo 2: Need typed hashes + # @todo 1: should warn on nil dereference below # @todo 1: flow sensitive typing needs to create separate ranges for postfix if - # @todo 1: flow sensitive typing handling else from is_a? with union types # @todo 1: flow sensitive typing needs to handle constants - # @todo 1: To make JSON strongly typed we'll need a record syntax - # @todo 1: signature isn't down-selected - # @todo 1: ComplexType::UniqueType needs without_nil - # @todo 1: foo = 1; foo = 2 if bar? should be of type 'Integer', not 'Integer, nil' # @todo 1: flow sensitive typing needs to eliminate literal from union with return if foo == :bar - # @todo 1: Flow sensitive typing could figure out this is not nil at both entrypoints def require_all_unique_types_match_expected? rank >= LEVELS[:strong] end diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 32018a587..4fe2ec0e7 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -14,7 +14,7 @@ class Config # @return [String] attr_reader :directory - # @todo To make JSON strongly typed we'll need a record syntax + # @todo Need typed hashes # @return [Hash{String => undefined, nil}] attr_reader :raw_data From 8aef08b855f64ae9c720f176387c8b916f982376 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 20 Nov 2025 08:16:50 -0500 Subject: [PATCH 697/930] Accounting fixes --- lib/solargraph/api_map/source_to_yard.rb | 12 ++++++------ lib/solargraph/convention/data_definition.rb | 6 +++--- lib/solargraph/convention/struct_definition.rb | 6 +++--- .../message/extended/check_gem_version.rb | 2 +- lib/solargraph/pin/base.rb | 4 ++-- lib/solargraph/pin/base_variable.rb | 18 +++++++++--------- lib/solargraph/pin/common.rb | 3 ++- lib/solargraph/pin/conversions.rb | 1 + lib/solargraph/pin/method.rb | 4 ++-- lib/solargraph/pin/parameter.rb | 4 ++-- lib/solargraph/rbs_map.rb | 2 +- lib/solargraph/source/change.rb | 6 +++--- lib/solargraph/type_checker.rb | 2 +- lib/solargraph/type_checker/problem.rb | 2 ++ lib/solargraph/type_checker/rules.rb | 11 +++++++++-- 15 files changed, 47 insertions(+), 36 deletions(-) diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index 0683dfe55..a449afbb8 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -36,16 +36,16 @@ def rake_yard store end if pin.type == :class code_object_map[pin.path] ||= YARD::CodeObjects::ClassObject.new(root_code_object, pin.path) { |obj| - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) } else code_object_map[pin.path] ||= YARD::CodeObjects::ModuleObject.new(root_code_object, pin.path) { |obj| - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) } end @@ -73,9 +73,9 @@ def rake_yard store # @sg-ignore Need to add nil check here code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope) { |obj| - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs next if pin.location.nil? || pin.location.filename.nil? - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs obj.add_file pin.location.filename, pin.location.range.start.line } method_object = code_object_at(pin.path, YARD::CodeObjects::MethodObject) diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index f4c943427..193364061 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -17,7 +17,7 @@ def process type: :class, location: loc, closure: region.closure, - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs name: data_definition_node.class_name, comments: comments_for(node), visibility: :public, @@ -40,7 +40,7 @@ def process # Solargraph::SourceMap::Clip#complete_keyword_parameters does not seem to currently take into account [Pin::Method#signatures] hence we only one for :kwarg pins.push initialize_method_pin - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs data_definition_node.attributes.map do |attribute_node, attribute_name| initialize_method_pin.parameters.push( Pin::Parameter.new( @@ -53,7 +53,7 @@ def process end # define attribute readers and instance variables - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs data_definition_node.attributes.each do |attribute_node, attribute_name| name = attribute_name.to_s method_pin = Pin::Method.new( diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index fee661d84..7871dec00 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -17,7 +17,7 @@ def process type: :class, location: loc, closure: region.closure, - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs name: struct_definition_node.class_name, docstring: docstring, visibility: :public, @@ -40,7 +40,7 @@ def process pins.push initialize_method_pin - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs struct_definition_node.attributes.map do |attribute_node, attribute_name| initialize_method_pin.parameters.push( Pin::Parameter.new( @@ -54,7 +54,7 @@ def process end # define attribute accessors and instance variables - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs struct_definition_node.attributes.each do |attribute_node, attribute_name| [attribute_name, "#{attribute_name}="].each do |name| docs = docstring.tags.find { |t| t.tag_name == 'param' && t.name == attribute_name } 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 a9f9d22ec..b4f64df22 100644 --- a/lib/solargraph/language_server/message/extended/check_gem_version.rb +++ b/lib/solargraph/language_server/message/extended/check_gem_version.rb @@ -73,7 +73,7 @@ def process attr_reader :current # @return [Gem::Version] - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore Need to add nil check here def available if !@available && !@fetched @fetched = true diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 23f2915ba..e3db0c924 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -440,7 +440,7 @@ def erase_generics(generics_to_erase) # @return [String, nil] def filename return nil if location.nil? - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs location.filename end @@ -477,7 +477,7 @@ def best_location def nearly? other self.class == other.class && name == other.name && - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs (closure == other.closure || (closure && closure.nearly?(other.closure))) && (comments == other.comments || (((maybe_directives? == false && other.maybe_directives? == false) || compare_directives(directives, other.directives)) && diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 7966b275e..8abdf3a04 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -206,11 +206,11 @@ def return_type end # @param other_loc [Location] - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs def starts_at?(other_loc) location&.filename == other_loc.filename && presence && - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs presence.start == other_loc.range.start end @@ -228,7 +228,7 @@ def combine_presence(other) return other.presence end - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs Range.new([presence.start, other.presence.start].max, [presence.ending, other.presence.ending].min) end @@ -246,14 +246,14 @@ def combine_closure(other) return closure || other.closure end - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs if closure.location.nil? || other.closure.location.nil? - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs return closure.location.nil? ? other.closure : closure end # if filenames are different, this will just pick one - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs return closure if closure.location <= other.closure.location other.closure @@ -262,9 +262,9 @@ def combine_closure(other) # @param other_closure [Pin::Closure] # @param other_loc [Location] def visible_at?(other_closure, other_loc) - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs location.filename == other_loc.filename && - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs (!presence || presence.include?(other_loc.range.start)) && visible_in_closure?(other_closure) end @@ -321,7 +321,7 @@ def combine_closure(other) end # if filenames are different, this will just pick one - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs return closure if closure.location <= other.closure.location other.closure diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index e8f15b0e1..fc607e413 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -12,7 +12,8 @@ module Common # @type @closure [Pin::Closure, nil] # @type @binder [ComplexType, ComplexType::UniqueType, nil] - # @return [Location] + # @todo Missed nil violation + # @return [Location, nil] attr_accessor :location # @param value [Pin::Closure] diff --git a/lib/solargraph/pin/conversions.rb b/lib/solargraph/pin/conversions.rb index 43050fb94..5ad3573f7 100644 --- a/lib/solargraph/pin/conversions.rb +++ b/lib/solargraph/pin/conversions.rb @@ -43,6 +43,7 @@ def completion_item data: { path: path, return_type: return_type.tag, + # @sg-ignore flow sensitive typing needs to handle attrs location: (location ? location.to_hash : nil), deprecated: deprecated? } diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 1da38fca7..55abd1025 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -204,7 +204,7 @@ def generate_signature(parameters, return_type) comments: p.text, name: name, decl: decl, - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs presence: location ? location.range : nil, return_type: ComplexType.try_parse(*p.types), source: source @@ -415,7 +415,7 @@ def overloads comments: tag.docstring.all.to_s, name: name, decl: decl, - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs presence: location ? location.range : nil, return_type: param_type_from_name(tag, src.first), source: :overloads diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index df7b9f273..76dd0b8fb 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -50,7 +50,7 @@ def keyword? end def kwrestarg? - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs decl == :kwrestarg || (assignment && [:HASH, :hash].include?(assignment.type)) end @@ -207,7 +207,7 @@ def compatible_arg?(atype, api_map) ptype.generic? end - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs def documentation tag = param_tag return '' if tag.nil? || tag.text.nil? diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index d4bb55cd4..94bf65b50 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -51,7 +51,7 @@ def cache_key # @type [String, nil] data = nil if rbs_collection_config_path - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs lockfile_path = RBS::Collection::Config.to_lockfile_path(Pathname.new(rbs_collection_config_path)) if lockfile_path.exist? collection_config = RBS::Collection::Config.from_path lockfile_path diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index 1469fb83c..becee5191 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -31,11 +31,11 @@ def write text, nullable = false if nullable and !range.nil? and new_text.match(/[.\[{(@$:]$/) [':', '@'].each do |dupable| next unless new_text == dupable - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs offset = Position.to_offset(text, range.start) if text[offset - 1] == dupable p = Position.from_offset(text, offset - 1) - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs r = Change.new(Range.new(p, range.start), ' ') text = r.write(text) end @@ -60,7 +60,7 @@ def repair text fixed else result = commit text, fixed - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs off = Position.to_offset(text, range.start) # @sg-ignore Need to add nil check here match = result[0, off].match(/[.:]+\z/) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 5fb648f75..00b1a95d7 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -662,7 +662,7 @@ def param_details_from_stack(signature, method_pin_stack) # @param pin [Pin::Base] def internal? pin return false if pin.nil? - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs to handle attrs pin.location && api_map.bundled?(pin.location.filename) end diff --git a/lib/solargraph/type_checker/problem.rb b/lib/solargraph/type_checker/problem.rb index f182e6a75..8375a58ff 100644 --- a/lib/solargraph/type_checker/problem.rb +++ b/lib/solargraph/type_checker/problem.rb @@ -5,12 +5,14 @@ class TypeChecker # A problem reported by TypeChecker. # class Problem + # @todo Missed nil violation # @return [Solargraph::Location] attr_reader :location # @return [String] attr_reader :message + # @todo Missed nil violation # @return [Pin::Base] attr_reader :pin diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 919a19d63..28733605d 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -58,6 +58,11 @@ def require_inferred_type_params? rank >= LEVELS[:alpha] end + # + # False negatives: + # + # @todo 3: Missed nil violation + # # pending code fixes (277): # # @todo 263: Need to add nil check here @@ -67,12 +72,13 @@ def require_inferred_type_params? # # flow-sensitive typing could handle (96): # - # @todo 48: flow sensitive typing needs to handle ivars + # @todo 33: flow sensitive typing needs to handle attrs + # @todo 14: flow sensitive typing needs to handle ivars # @todo 9: Should handle redefinition of types in simple contexts # @todo 6: need boolish support for ? methods # @todo 5: literal arrays in this module turn into ::Solargraph::Source::Chain::Array + # @todo 4: flow sensitive typing needs better handling of ||= on lvars # @todo 4: flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) - # @todo 4: (*) flow sensitive typing needs better handling of ||= on lvars # @todo 3: downcast output of Enumerable#select # @todo 3: flow sensitive typing needs to handle 'raise if' # @todo 2: Need to look at Tuple#include? handling @@ -83,6 +89,7 @@ def require_inferred_type_params? # @todo 1: should warn on nil dereference below # @todo 1: flow sensitive typing needs to create separate ranges for postfix if # @todo 1: flow sensitive typing needs to handle constants + # @todo 1: flow sensitive typing needs to handle while # @todo 1: flow sensitive typing needs to eliminate literal from union with return if foo == :bar def require_all_unique_types_match_expected? rank >= LEVELS[:strong] From adf91425268ff0c5c97729e60ec761f88231c4d0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 20 Nov 2025 08:30:41 -0500 Subject: [PATCH 698/930] Drop unneeded presence validation --- lib/solargraph/api_map.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 1c2e9e58b..57e37fda0 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -359,20 +359,18 @@ def get_instance_variable_pins(namespace, scope = :instance) # preferring pins created by flow-sensitive typing when we have # them based on the Closure and Location. # - # @param locals [Array] + # @param candidates [Array] # @param name [String] # @param closure [Pin::Closure] # @param location [Location] # - # @return [Pin::LocalVariable, nil] - def var_at_location(locals, name, closure, location) - with_correct_name = locals.select { |pin| pin.name == name} - with_presence = with_correct_name.reject { |pin| pin.presence.nil? } - vars_at_location = with_presence.reject do |pin| + # @return [Pin::BaseVariable, nil] + def var_at_location(candidates, name, closure, location) + with_correct_name = candidates.select { |pin| pin.name == name} + vars_at_location = with_correct_name.reject do |pin| # visible_at? excludes the starting position, but we want to # include it for this purpose - (!pin.visible_at?(closure, location) && - !pin.starts_at?(location)) + (!pin.visible_at?(closure, location) && !pin.starts_at?(location)) end vars_at_location.inject(&:combine_with) From 125511ee49fcf8766b9c7ed9c33c030edd5c98a8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 20 Nov 2025 08:55:45 -0500 Subject: [PATCH 699/930] Add ivar support to flow sensitive typing --- .rubocop_todo.yml | 5 +- lib/solargraph/library.rb | 17 ++--- .../parser/flow_sensitive_typing.rb | 61 +++++++++++------ lib/solargraph/parser/node_processor.rb | 11 +-- lib/solargraph/parser/node_processor/base.rb | 9 ++- .../parser/parser_gem/class_methods.rb | 6 +- .../parser_gem/node_processors/and_node.rb | 1 + .../parser_gem/node_processors/if_node.rb | 7 +- .../parser_gem/node_processors/ivasgn_node.rb | 4 +- .../parser_gem/node_processors/masgn_node.rb | 4 +- .../parser_gem/node_processors/opasgn_node.rb | 4 +- .../parser_gem/node_processors/or_node.rb | 1 + .../parser_gem/node_processors/orasgn_node.rb | 2 +- .../node_processors/resbody_node.rb | 2 +- .../parser_gem/node_processors/send_node.rb | 10 +-- .../parser_gem/node_processors/while_node.rb | 1 + lib/solargraph/parser/region.rb | 8 +++ lib/solargraph/pin/base.rb | 1 - lib/solargraph/pin/closure.rb | 5 -- lib/solargraph/pin/delegated_method.rb | 3 + lib/solargraph/pin/keyword.rb | 4 -- lib/solargraph/pin/parameter.rb | 1 - .../source/chain/instance_variable.rb | 8 ++- lib/solargraph/source/chain/literal.rb | 1 - lib/solargraph/source_map/mapper.rb | 14 ++-- lib/solargraph/type_checker/rules.rb | 3 +- spec/parser/flow_sensitive_typing_spec.rb | 20 ++++++ spec/source/chain/instance_variable_spec.rb | 23 +++++-- spec/type_checker/levels/alpha_spec.rb | 53 --------------- spec/type_checker/levels/strong_spec.rb | 68 ++++++++++++++++++- 30 files changed, 216 insertions(+), 141 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6b1483356..9591fe911 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -483,6 +483,7 @@ Metrics/ModuleLength: Metrics/ParameterLists: Exclude: - 'lib/solargraph/api_map.rb' + - 'lib/solargraph/parser/node_processor.rb' - 'lib/solargraph/pin/callable.rb' - 'lib/solargraph/type_checker.rb' - 'lib/solargraph/yard_map/mapper/to_method.rb' @@ -825,7 +826,6 @@ Style/EmptyLambdaParameter: Style/EmptyMethod: Exclude: - 'lib/solargraph/language_server/message/client/register_capability.rb' - - 'lib/solargraph/pin/base.rb' - 'spec/fixtures/formattable.rb' - 'spec/fixtures/rdoc-lib/lib/example.rb' - 'spec/fixtures/workspace-with-gemfile/lib/thing.rb' @@ -1209,8 +1209,6 @@ Style/TrailingCommaInHashLiteral: Exclude: - 'lib/solargraph/pin/callable.rb' - 'lib/solargraph/pin/closure.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/pin/local_variable.rb' - 'lib/solargraph/rbs_map/conversions.rb' # This cop supports safe autocorrection (--autocorrect). @@ -1219,7 +1217,6 @@ Style/TrailingCommaInHashLiteral: Style/TrivialAccessors: Exclude: - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - - 'lib/solargraph/pin/keyword.rb' # This cop supports safe autocorrection (--autocorrect). Style/WhileUntilModifier: diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 36e79837e..e175929ac 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -33,6 +33,7 @@ def initialize workspace = Solargraph::Workspace.new, name = nil # @type [Source, nil] @current = nil @sync_count = 0 + @cache_progress = nil end def inspect @@ -57,11 +58,8 @@ def synchronized? # @param source [Source, nil] # @return [void] def attach source - # @sg-ignore flow sensitive typing needs to handle ivars if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename) - # @sg-ignore flow sensitive typing needs to handle ivars source_map_hash.delete @current.filename - # @sg-ignore flow sensitive typing needs to handle ivars source_map_external_require_hash.delete @current.filename @external_requires = nil end @@ -75,9 +73,7 @@ def attach source # # @param filename [String] # @return [Boolean] - # @sg-ignore flow sensitive typing needs to handle ivars def attached? filename - # @sg-ignore flow sensitive typing needs to handle ivars !@current.nil? && @current.filename == filename end alias open? attached? @@ -87,7 +83,6 @@ def attached? filename # @param filename [String] # @return [Boolean] True if the specified file was detached def detach filename - # @sg-ignore flow sensitive typing needs to handle ivars return false if @current.nil? || @current.filename != filename attach nil true @@ -453,7 +448,6 @@ def bench source_maps: source_map_hash.values, workspace: workspace, external_requires: external_requires, - # @sg-ignore flow sensitive typing needs to handle ivars live_map: @current ? source_map_hash[@current.filename] : nil ) end @@ -560,10 +554,8 @@ def api_map # # @raise [FileNotFoundError] if the file does not exist # @param filename [String] - # @sg-ignore flow sensitive typing needs to handle ivars # @return [Solargraph::Source] def read filename - # @sg-ignore flow sensitive typing needs to handle ivars return @current if @current && @current.filename == filename raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename) workspace.source(filename) @@ -657,19 +649,18 @@ def report_cache_progress gem_name, pending @total ||= pending # @sg-ignore flow sensitive typing needs better handling of ||= on lvars @total = pending if pending > @total - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs better handling of ||= on lvars finished = @total - pending - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs better handling of ||= on lvars pct = if @total.zero? 0 else - # @sg-ignore flow sensitive typing needs to handle ivars + # @sg-ignore flow sensitive typing needs better handling of ||= on lvars ((finished.to_f / @total.to_f) * 100).to_i end message = "#{gem_name}#{pending > 0 ? " (+#{pending})" : ''}" # " if @cache_progress - # @sg-ignore flow sensitive typing needs to handle ivars @cache_progress.report(message, pct) else @cache_progress = LanguageServer::Progress.new('Caching gem') diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index e21c4dcb4..6ec7f1003 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -3,11 +3,13 @@ module Parser class FlowSensitiveTyping include Solargraph::Parser::NodeMethods - # @param locals [Array] + # @param locals [Array] + # @param ivars [Array] # @param enclosing_breakable_pin [Solargraph::Pin::Breakable, nil] # @param enclosing_compound_statement_pin [Solargraph::Pin::CompoundStatement, nil] - def initialize(locals, enclosing_breakable_pin, enclosing_compound_statement_pin) + def initialize(locals, ivars, enclosing_breakable_pin, enclosing_compound_statement_pin) @locals = locals + @ivars = ivars @enclosing_breakable_pin = enclosing_breakable_pin @enclosing_compound_statement_pin = enclosing_compound_statement_pin end @@ -202,37 +204,43 @@ class << self private - # @param pin [Pin::LocalVariable] + # @param pin [Pin::BaseVariable] # @param presence [Range] # @param downcast_type [ComplexType, nil] # @param downcast_not_type [ComplexType, nil] # # @return [void] - def add_downcast_local(pin, presence:, downcast_type:, downcast_not_type:) + def add_downcast_var(pin, presence:, downcast_type:, downcast_not_type:) new_pin = pin.downcast(exclude_return_type: downcast_not_type, intersection_return_type: downcast_type, source: :flow_sensitive_typing, presence: presence) - locals.push(new_pin) + if pin.is_a?(Pin::LocalVariable) + locals.push(new_pin) + elsif pin.is_a?(Pin::InstanceVariable) + ivars.push(new_pin) + else + raise "Tried to add invalid pin type #{pin.class} in FlowSensitiveTyping" + end end - # @param facts_by_pin [Hash{Pin::LocalVariable => Array ComplexType}>}] + # @param facts_by_pin [Hash{Pin::BaseVariable => Array ComplexType}>}] # @param presences [Array] # # @return [void] def process_facts(facts_by_pin, presences) # - # Add specialized locals for the rest of the block + # Add specialized vars for the rest of the block # facts_by_pin.each_pair do |pin, facts| facts.each do |fact| downcast_type = fact.fetch(:type, nil) downcast_not_type = fact.fetch(:not_type, nil) presences.each do |presence| - add_downcast_local(pin, - presence: presence, - downcast_type: downcast_type, - downcast_not_type: downcast_not_type) + add_downcast_var(pin, + presence: presence, + downcast_type: downcast_type, + downcast_not_type: downcast_not_type) end end end @@ -274,7 +282,8 @@ def parse_call(call_node, method_name) end # or like this: # (lvar :repr) - variable_name = call_receiver.children[0].to_s if call_receiver&.type == :lvar + # @sg-ignore Need to look at Tuple#include? handling + variable_name = call_receiver.children[0].to_s if [:lvar, :ivar].include?(call_receiver&.type) return unless variable_name [call_arg, variable_name] @@ -293,11 +302,17 @@ def parse_isa(isa_node) # @param variable_name [String] # @param position [Position] # - # @return [Solargraph::Pin::LocalVariable, nil] - def find_local(variable_name, position) - # @sg-ignore Need to add nil check here - pins = locals.select { |pin| pin.name == variable_name && pin.presence.include?(position) } - pins.first + # @sg-ignore Solargraph::Parser::FlowSensitiveTyping#find_var + # return type could not be inferred + # @return [Solargraph::Pin::LocalVariable, Solargraph::Pin::InstanceVariable, nil] + def find_var(variable_name, position) + if variable_name.start_with?('@') + # @sg-ignore flow sensitive typing needs to handle attrs + ivars.find { |ivar| ivar.name == variable_name && (!ivar.presence || ivar.presence.include?(position)) } + else + # @sg-ignore flow sensitive typing needs to handle attrs + locals.find { |pin| pin.name == variable_name && (!pin.presence || pin.presence.include?(position)) } + end end # @param isa_node [Parser::AST::Node] @@ -311,16 +326,16 @@ def process_isa(isa_node, true_presences, false_presences) # @sg-ignore Need to add nil check here isa_position = Range.from_node(isa_node).start - pin = find_local(variable_name, isa_position) + pin = find_var(variable_name, isa_position) return unless pin - # @type Hash{Pin::LocalVariable => Array ComplexType}>} + # @type Hash{Pin::BaseVariable => Array ComplexType}>} if_true = {} if_true[pin] ||= [] if_true[pin] << { type: ComplexType.parse(isa_type_name) } process_facts(if_true, true_presences) - # @type Hash{Pin::LocalVariable => Array ComplexType}>} + # @type Hash{Pin::BaseVariable => Array ComplexType}>} if_false = {} if_false[pin] ||= [] if_false[pin] << { not_type: ComplexType.parse(isa_type_name) } @@ -348,7 +363,7 @@ def process_nilp(nilp_node, true_presences, false_presences) # @sg-ignore Need to add nil check here nilp_position = Range.from_node(nilp_node).start - pin = find_local(variable_name, nilp_position) + pin = find_var(variable_name, nilp_position) return unless pin # @type Hash{Pin::LocalVariable => Array ComplexType}>} @@ -410,7 +425,7 @@ def process_variable(node, true_presences, false_presences) # @sg-ignore Need to add nil check here var_position = Range.from_node(node).start - pin = find_local(variable_name, var_position) + pin = find_var(variable_name, var_position) return unless pin # @type Hash{Pin::LocalVariable => Array ComplexType}>} @@ -461,6 +476,8 @@ def always_leaves_compound_statement?(clause_node) attr_reader :locals + attr_reader :ivars + attr_reader :enclosing_breakable_pin, :enclosing_compound_statement_pin end end diff --git a/lib/solargraph/parser/node_processor.rb b/lib/solargraph/parser/node_processor.rb index 3d9e9b846..a43fb326c 100644 --- a/lib/solargraph/parser/node_processor.rb +++ b/lib/solargraph/parser/node_processor.rb @@ -36,8 +36,9 @@ def deregister type, cls # @param region [Region] # @param pins [Array] # @param locals [Array] - # @return [Array(Array, Array)] - def self.process node, region = Region.new, pins = [], locals = [] + # @param ivars [Array] + # @return [Array(Array, Array, Array)] + def self.process node, region = Region.new, pins = [], locals = [], ivars = [] if pins.empty? pins.push Pin::Namespace.new( location: region.source.location, @@ -45,17 +46,17 @@ def self.process node, region = Region.new, pins = [], locals = [] source: :parser, ) end - return [pins, locals] unless Parser.is_ast_node?(node) + return [pins, locals, ivars] unless Parser.is_ast_node?(node) node_processor_classes = @@processors[node.type] || [NodeProcessor::Base] node_processor_classes.each do |klass| - processor = klass.new(node, region, pins, locals) + processor = klass.new(node, region, pins, locals, ivars) process_next = processor.process break unless process_next end - [pins, locals] + [pins, locals, ivars] end end end diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index 39f67cbe3..e11078e07 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -16,15 +16,20 @@ class Base # @return [Array] attr_reader :locals + # @return [Array] + attr_reader :ivars + # @param node [Parser::AST::Node] # @param region [Region] # @param pins [Array] # @param locals [Array] - def initialize node, region, pins, locals + # @param ivars [Array] + def initialize node, region, pins, locals, ivars @node = node @region = region @pins = pins @locals = locals + @ivars = ivars @processed_children = false end @@ -69,7 +74,7 @@ def process_children subregion = region @processed_children = true node.children.each do |child| next unless Parser.is_ast_node?(child) - NodeProcessor.process(child, subregion, pins, locals) + NodeProcessor.process(child, subregion, pins, locals, ivars) end end diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index eac4e7f27..987f89cef 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -40,10 +40,12 @@ def parser end # @param source [Source] - # @return [Array(Array, Array)] + # @return [Array(Array, Array)] def map source # @sg-ignore Need to add nil check here - NodeProcessor.process(source.node, Region.new(source: source)) + pins, locals, ivars = NodeProcessor.process(source.node, Region.new(source: source)) + pins.concat(ivars) + [pins, locals] end # @param source [Source] diff --git a/lib/solargraph/parser/parser_gem/node_processors/and_node.rb b/lib/solargraph/parser/parser_gem/node_processors/and_node.rb index 085c0c68a..83f14a415 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/and_node.rb @@ -11,6 +11,7 @@ def process process_children FlowSensitiveTyping.new(locals, + ivars, enclosing_breakable_pin, enclosing_compound_statement_pin).process_and(node) end 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 9b0bbd978..bf8f95635 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb @@ -9,6 +9,7 @@ class IfNode < Parser::NodeProcessor::Base def process FlowSensitiveTyping.new(locals, + ivars, enclosing_breakable_pin, enclosing_compound_statement_pin).process_if(node) condition_node = node.children[0] @@ -19,7 +20,7 @@ def process node: condition_node, source: :parser, ) - NodeProcessor.process(condition_node, region, pins, locals) + NodeProcessor.process(condition_node, region, pins, locals, ivars) end then_node = node.children[1] if then_node @@ -29,7 +30,7 @@ def process node: then_node, source: :parser, ) - NodeProcessor.process(then_node, region, pins, locals) + NodeProcessor.process(then_node, region, pins, locals, ivars) end else_node = node.children[2] @@ -40,7 +41,7 @@ def process node: else_node, source: :parser, ) - NodeProcessor.process(else_node, region, pins, locals) + NodeProcessor.process(else_node, region, pins, locals, ivars) end true 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 d05ecc41c..0da611e22 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb @@ -9,7 +9,7 @@ class IvasgnNode < Parser::NodeProcessor::Base def process loc = get_node_location(node) - pins.push Solargraph::Pin::InstanceVariable.new( + ivars.push Solargraph::Pin::InstanceVariable.new( location: loc, closure: region.closure, name: node.children[0].to_s, @@ -22,7 +22,7 @@ def process # @type [Pin::Closure, nil] named_path = named_path_pin(here) if named_path.is_a?(Pin::Method) - pins.push Solargraph::Pin::InstanceVariable.new( + ivars.push Solargraph::Pin::InstanceVariable.new( location: loc, closure: Pin::Namespace.new(type: :module, closure: region.closure.closure, name: region.closure.name), name: node.children[0].to_s, 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..40c3167f8 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb @@ -37,8 +37,10 @@ def process pin = if lhs.type == :lvasgn # lvasgn is a local variable locals.find { |l| l.location == location } - else + elsif lhs.type == :ivasgn # e.g., ivasgn is an instance variable, etc + ivars.find { |iv| iv.location == location } + else pins.find { |iv| iv.location == location && iv.is_a?(Pin::BaseVariable) } end # @todo in line below, nothing in typechecking alerts 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 a4359af9d..ab23ffa0e 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb @@ -57,7 +57,7 @@ def process_send_target call, operator, argument [callee, asgn_method, node.updated(:send, [call, operator, argument])]) - NodeProcessor.process(new_send, region, pins, locals) + NodeProcessor.process(new_send, region, pins, locals, ivars) end # @param asgn [Parser::AST::Node] the target of the assignment @@ -89,7 +89,7 @@ def process_vasgn_target asgn, operator, argument ] send_node = node.updated(:send, send_children) new_asgn = node.updated(asgn.type, [variable_name, send_node]) - NodeProcessor.process(new_asgn, region, pins, locals) + NodeProcessor.process(new_asgn, region, pins, locals, ivars) end end end diff --git a/lib/solargraph/parser/parser_gem/node_processors/or_node.rb b/lib/solargraph/parser/parser_gem/node_processors/or_node.rb index c77bad1d6..6c54f1c8c 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/or_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/or_node.rb @@ -11,6 +11,7 @@ def process process_children FlowSensitiveTyping.new(locals, + ivars, enclosing_breakable_pin, enclosing_compound_statement_pin).process_or(node) end diff --git a/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb b/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb index 105b78828..17480adfb 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb @@ -8,7 +8,7 @@ class OrasgnNode < Parser::NodeProcessor::Base # @return [void] def process new_node = node.updated(node.children[0].type, node.children[0].children + [node.children[1]]) - NodeProcessor.process(new_node, region, pins, locals) + NodeProcessor.process(new_node, region, pins, locals, ivars) end end end diff --git a/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb b/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb index c59c2ee04..24846748f 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb @@ -30,7 +30,7 @@ def process source: :parser ) end - NodeProcessor.process(node.children[2], region, pins, locals) + NodeProcessor.process(node.children[2], region, pins, locals, ivars) end end end 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 bbb210012..86c762c98 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -228,9 +228,9 @@ def process_module_function node: ref.node, source: :parser) pins.push mm, cm - pins.select{|pin| pin.is_a?(Pin::InstanceVariable) && pin.closure.path == ref.path}.each do |ivar| - pins.delete ivar - pins.push Solargraph::Pin::InstanceVariable.new( + ivars.select{|pin| pin.is_a?(Pin::InstanceVariable) && pin.closure.path == ref.path}.each do |ivar| + ivars.delete ivar + ivars.push Solargraph::Pin::InstanceVariable.new( location: ivar.location, closure: cm, name: ivar.name, @@ -238,7 +238,7 @@ def process_module_function assignment: ivar.assignment, source: :parser ) - pins.push Solargraph::Pin::InstanceVariable.new( + ivars.push Solargraph::Pin::InstanceVariable.new( location: ivar.location, closure: mm, name: ivar.name, @@ -250,7 +250,7 @@ def process_module_function end end elsif node.children[2].type == :def - NodeProcessor.process node.children[2], region.update(visibility: :module_function), pins, locals + NodeProcessor.process node.children[2], region.update(visibility: :module_function), pins, locals, ivars end end diff --git a/lib/solargraph/parser/parser_gem/node_processors/while_node.rb b/lib/solargraph/parser/parser_gem/node_processors/while_node.rb index 4b025121e..38ccc53b5 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/while_node.rb @@ -9,6 +9,7 @@ class WhileNode < Parser::NodeProcessor::Base def process FlowSensitiveTyping.new(locals, + ivars, enclosing_breakable_pin, enclosing_compound_statement_pin).process_while(node) diff --git a/lib/solargraph/parser/region.rb b/lib/solargraph/parser/region.rb index 279ad0e57..ff88bada2 100644 --- a/lib/solargraph/parser/region.rb +++ b/lib/solargraph/parser/region.rb @@ -40,6 +40,14 @@ def filename source.filename end + # @return [Pin::Namespace, nil] + def namespace_pin + ns = closure + # @sg-ignore flow sensitive typing needs to handle while + ns = ns.closure while ns && !ns.is_a?(Pin::Namespace) + ns + end + # Generate a new Region with the provided attribute changes. # # @param closure [Pin::Closure, nil] diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index e3db0c924..bf45116cf 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -528,7 +528,6 @@ def macros # # @return [Boolean] def maybe_directives? - # @sg-ignore flow sensitive typing needs to handle ivars return !@directives.empty? if defined?(@directives) && @directives @maybe_directives ||= comments.include?('@!') end diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index 8df7167e9..44265dcab 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -44,11 +44,6 @@ def context end end - # @return [ComplexType, ComplexType::UniqueType] - def binder - @binder || context - end - # @param api_map [Solargraph::ApiMap] # @return [void] def rebind api_map; end diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index 23e4f791e..6054b5245 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -71,6 +71,9 @@ def resolvable?(api_map) # # @param api_map [ApiMap] # @return [Pin::Method, nil] + # @sg-ignore Declared return type ::Solargraph::Pin::Method, nil + # does not match inferred type nil, false for + # Solargraph::Pin::DelegatedMethod#resolve_method def resolve_method api_map return if @resolved_method diff --git a/lib/solargraph/pin/keyword.rb b/lib/solargraph/pin/keyword.rb index 089d0a417..08ea1c6e0 100644 --- a/lib/solargraph/pin/keyword.rb +++ b/lib/solargraph/pin/keyword.rb @@ -11,10 +11,6 @@ def initialize(name, **kwargs) def closure @closure ||= Pin::ROOT_PIN end - - def name - @name - end end end end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 76dd0b8fb..f992f84ac 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -158,7 +158,6 @@ def return_type @return_type = ComplexType::UNDEFINED found = param_tag @return_type = ComplexType.try_parse(*found.types) unless found.nil? or found.types.nil? - # @sg-ignore Need to add nil check here if @return_type.undefined? if decl == :restarg @return_type = ComplexType.try_parse('::Array') diff --git a/lib/solargraph/source/chain/instance_variable.rb b/lib/solargraph/source/chain/instance_variable.rb index 2329f24b9..7ceca3547 100644 --- a/lib/solargraph/source/chain/instance_variable.rb +++ b/lib/solargraph/source/chain/instance_variable.rb @@ -13,8 +13,14 @@ def initialize word, node, location @location = location end + # @sg-ignore Declared return type + # ::Array<::Solargraph::Pin::Base> does not match inferred + # type ::Array<::Solargraph::Pin::BaseVariable, ::NilClass> + # for Solargraph::Source::Chain::InstanceVariable#resolve def resolve api_map, name_pin, locals - api_map.get_instance_variable_pins(name_pin.context.namespace, name_pin.context.scope).select{|p| p.name == word} + ivars = api_map.get_instance_variable_pins(name_pin.context.namespace, name_pin.context.scope).select{|p| p.name == word} + out = api_map.var_at_location(ivars, word, name_pin, location) + [out].compact end private diff --git a/lib/solargraph/source/chain/literal.rb b/lib/solargraph/source/chain/literal.rb index a6716ee98..e53e5a2a4 100644 --- a/lib/solargraph/source/chain/literal.rb +++ b/lib/solargraph/source/chain/literal.rb @@ -25,7 +25,6 @@ def initialize type, node end end @type = type - # @sg-ignore flow sensitive typing needs to handle ivars @literal_type = ComplexType.try_parse(@value.inspect) @complex_type = ComplexType.try_parse(type) end diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 4202fbb4f..f28a04cec 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -112,7 +112,8 @@ def process_directive source_position, comment_position, directive case directive.tag.tag_name when 'method' namespace = closure_at(source_position) || @pins.first - # @sg-ignore Need to add nil check here + # @todo Missed nil violation + # @todo Need to add nil check here if namespace.location.range.start.line < comment_position.line namespace = closure_at(comment_position) end @@ -177,7 +178,8 @@ def process_directive source_position, comment_position, directive name = directive.tag.name closure = closure_at(source_position) || @pins.first - # @sg-ignore Need to add nil check here + # @todo Missed nil violation + # @todo Need to add nil check here if closure.location.range.start.line < comment_position.line closure = closure_at(comment_position) end @@ -205,7 +207,10 @@ def process_directive source_position, comment_position, directive else comment_position.line end - Parser.process_node(src.node, region, @pins) + locals = [] + ivars = [] + Parser.process_node(src.node, region, @pins, locals, ivars) + @pins.concat ivars # @sg-ignore Need to add nil check here @pins[index..-1].each do |p| # @todo Smelly instance variable access @@ -217,7 +222,8 @@ def process_directive source_position, comment_position, directive end when 'domain' namespace = closure_at(source_position) || Pin::ROOT_PIN - # @sg-ignore Need to add nil check here + # @todo Missed nil violation + # @todo Need to add nil check here namespace.domains.concat directive.tag.types unless directive.tag.types.nil? when 'override' # @sg-ignore Need to add a nil check here diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 28733605d..bfb134540 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -72,8 +72,7 @@ def require_inferred_type_params? # # flow-sensitive typing could handle (96): # - # @todo 33: flow sensitive typing needs to handle attrs - # @todo 14: flow sensitive typing needs to handle ivars + # @todo 35: flow sensitive typing needs to handle attrs # @todo 9: Should handle redefinition of types in simple contexts # @todo 6: need boolish support for ? methods # @todo 5: literal arrays in this module turn into ::Solargraph::Source::Chain::Array diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index f35ee097d..cc8abca4a 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -978,4 +978,24 @@ def foo a clip = api_map.clip_at('test.rb', [5, 17]) expect(clip.infer.to_s).to eq('Integer') end + + + it 'supports !@x.nil && @x.y' do + source = Solargraph::Source.load_string(%( + class Bar + # @param foo [String, nil] + def initialize(foo) + @foo = foo + end + + def foo? + out = !@foo.nil? && @foo.upcase == 'FOO' + out + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [9, 10]) + expect(clip.infer.to_s).to eq('Boolean') + end end diff --git a/spec/source/chain/instance_variable_spec.rb b/spec/source/chain/instance_variable_spec.rb index 186c176a1..ee4604f91 100644 --- a/spec/source/chain/instance_variable_spec.rb +++ b/spec/source/chain/instance_variable_spec.rb @@ -1,20 +1,33 @@ describe Solargraph::Source::Chain::InstanceVariable do it "resolves instance variable pins" do - closure = Solargraph::Pin::Namespace.new(name: 'Foo') - methpin = Solargraph::Pin::Method.new(closure: closure, name: 'imeth', scope: :instance) - foo_pin = Solargraph::Pin::InstanceVariable.new(closure: methpin, name: '@foo') - bar_pin = Solargraph::Pin::InstanceVariable.new(closure: closure, name: '@foo') + closure = Solargraph::Pin::Namespace.new(name: 'Foo', + location: Solargraph::Location.new('test.rb', Solargraph::Range.from_to(1, 1, 9, 0)), + source: :closure) + methpin = Solargraph::Pin::Method.new(closure: closure, name: 'imeth', scope: :instance, + location: Solargraph::Location.new('test.rb', Solargraph::Range.from_to(1, 1, 3, 0)), + source: :methpin) + foo_pin = Solargraph::Pin::InstanceVariable.new(closure: methpin, name: '@foo', + location: Solargraph::Location.new('test.rb', Solargraph::Range.from_to(2, 0, 2, 0)), + presence: Solargraph::Range.from_to(2, 0, 2, 4), + source: :foo_pin) + bar_pin = Solargraph::Pin::InstanceVariable.new(closure: closure, name: '@foo', + location: Solargraph::Location.new('test.rb', Solargraph::Range.from_to(5, 0, 5, 0)), + presence: Solargraph::Range.from_to(5, 1, 5, 4), + source: :bar_pin) api_map = Solargraph::ApiMap.new api_map.index [closure, methpin, foo_pin, bar_pin] - link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil, nil) + + link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil, Solargraph::Location.new('test.rb', Solargraph::Range.from_to(2, 2, 2, 3))) pins = link.resolve(api_map, methpin, []) expect(pins.length).to eq(1) + expect(pins.first.name).to eq('@foo') expect(pins.first.context.scope).to eq(:instance) # Lookup context is Class to find the civar name_pin = Solargraph::Pin::ProxyType.anonymous(closure.binder, # Closure is the class closure: closure) + link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil, Solargraph::Location.new('test.rb', Solargraph::Range.from_to(5, 1, 5, 2))) pins = link.resolve(api_map, name_pin, []) expect(pins.length).to eq(1) expect(pins.first.name).to eq('@foo') diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 52fcf4843..f6f60b221 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -72,59 +72,6 @@ def catalog expect(checker.problems.map(&:message)).to eq([]) end - it 'accepts ivar assignments and references with no intermediate calls as safe' do - pending 'flow-sensitive typing improvements' - - checker = type_checker(%( - class Foo - def initialize - # @type [Integer, nil] - @foo = nil - end - - # @return [void] - def twiddle - @foo = nil if rand if rand > 0.5 - end - - # @return [Integer] - def bar - @foo = 123 - out = @foo.round - twiddle - out - end - end - )) - - expect(checker.problems.map(&:message)).to be_empty - end - - it 'knows that ivar references with intermediate calls are not safe' do - checker = type_checker(%( - class Foo - def initialize - # @type [Integer, nil] - @foo = nil - end - - # @return [void] - def twiddle - @foo = nil if rand if rand > 0.5 - end - - # @return [Integer] - def bar - @foo = 123 - twiddle - @foo.round - end - end - )) - - expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round"]) - end - it 'understands &. in return position' do checker = type_checker(%( class Baz diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 5f50c9ed0..a0651df39 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -717,7 +717,73 @@ def bar end )) - expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round"]) + expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round on Integer, nil"]) + end + + it 'performs simple flow-sensitive typing on lvars' do + checker = type_checker(%( + class Foo + # @param bar [Integer, nil] + # @return [::Boolean, ::Integer] + def foo bar + !bar || bar.abs + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end + + it 'performs simple flow-sensitive typing on ivars' do + checker = type_checker(%( + class Foo + # @param bar [::Integer, nil] + def initialize bar: nil + @bar = bar + end + + # @return [::Boolean, ::Integer] + def foo + !@bar || @bar.abs + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end + + it 'performs complex flow-sensitive typing on ivars' do + checker = type_checker(%( + class Foo + # @param bar [::Array, nil] + def initialize bar: nil + @bar = bar + end + + def maybe_bar? + return !@bar.empty? if defined?(@bar) && @bar + false + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end + + it 'supports !@x.nil && @x.y' do + checker = type_checker(%( + class Bar + # @param foo [String, nil] + def initialize(foo) + @foo = foo + end + + def foo? + !@foo.nil? && @foo.upcase == 'FOO' + end + end + )) + expect(checker.problems.map(&:message)).to eq([]) end end end From 092199ec947877b462fb6d54800b1828161cc302 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:02:06 +0000 Subject: [PATCH 700/930] Initial plan From ee3aba7b1247d1b52cef939569b9a504bc071ce0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:08:47 +0000 Subject: [PATCH 701/930] Fix typo in comment and add instance variable test Co-authored-by: apiology <3681194+apiology@users.noreply.github.com> --- .../parser/flow_sensitive_typing.rb | 2 +- spec/parser/flow_sensitive_typing_spec.rb | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 6ec7f1003..49be7d1c8 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -60,7 +60,7 @@ def process_or(or_node, true_ranges = [], false_ranges = []) get_node_end_position(rhs)) # can assume if an or is false that every single condition is - # false, so't provide false ranges to assert facts on + # false, so provide false ranges to assert facts on # can't assume if an or is true that every single condition is # true, so don't provide true ranges to assert facts on diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index cc8abca4a..258680983 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -998,4 +998,31 @@ def foo? clip = api_map.clip_at('test.rb', [9, 10]) expect(clip.infer.to_s).to eq('Boolean') end + + it 'uses is_a? with instance variables to refine types' do + source = Solargraph::Source.load_string(%( + class ReproBase; end + class Repro < ReproBase; end + class Example + # @param value [ReproBase] + def initialize(value) + @value = value + end + + def check + if @value.is_a?(Repro) + @value + else + @value + end + end + end + ), 'test.rb') + api_map = Solargraph::ApiMap.new.map(source) + clip = api_map.clip_at('test.rb', [11, 12]) + expect(clip.infer.to_s).to eq('Repro') + + clip = api_map.clip_at('test.rb', [13, 12]) + expect(clip.infer.to_s).to eq('ReproBase') + end end From 1bb9e21385c6e58c16301c49eca457ce122aaaff Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 24 Nov 2025 05:47:54 -0500 Subject: [PATCH 702/930] Drop change I can't prove I need --- lib/solargraph/parser/flow_sensitive_typing.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index e21c4dcb4..dfe58b478 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -248,7 +248,6 @@ def process_expression(expression_node, true_ranges, false_ranges) process_and(expression_node, true_ranges, false_ranges) process_or(expression_node, true_ranges, false_ranges) process_variable(expression_node, true_ranges, false_ranges) - process_if(expression_node, true_ranges, false_ranges) end # @param call_node [Parser::AST::Node] From 41adab490ba696f6c6bd1a84845d1558fdd3e4b5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 24 Nov 2025 05:51:28 -0500 Subject: [PATCH 703/930] Fix type issue combining directives I found this in another branch while improving typechecking. I'm not aware of any practical impact from the bug. --- 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 511c7deb7..afc5af31f 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -144,7 +144,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 f9ec72481784cdc852d4e3040bc297a33e61408b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 24 Nov 2025 05:56:36 -0500 Subject: [PATCH 704/930] Pull in additional build fix --- .github/workflows/rspec.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index ecc3d9771..b213c5ed0 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -35,6 +35,10 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} + # see https://github.com/castwide/solargraph/actions/runs/19391419903/job/55485410493?pr=1119 + # + # match version in Gemfile.lock and use same version below + bundler: 2.5.23 bundler-cache: false - name: Set rbs version run: echo "gem 'rbs', '${{ matrix.rbs-version }}'" >> .Gemfile @@ -46,7 +50,7 @@ jobs: run: echo "gem 'tsort'" >> .Gemfile - name: Install gems run: | - bundle install + bundle _2.5.23_ install bundle update rbs # use latest available for this Ruby version - name: Run tests run: bundle exec rake spec From 08626af3bdba3788ef122bcc0d2401dc6f8abe6d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 27 Nov 2025 07:16:16 -0500 Subject: [PATCH 705/930] Push logic up to BaseVariable for future expansion --- lib/solargraph/pin/base_variable.rb | 106 ++++++++++++++++++++++++- lib/solargraph/pin/common.rb | 1 - lib/solargraph/pin/local_variable.rb | 112 +-------------------------- 3 files changed, 105 insertions(+), 114 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 5d7f78620..1b334164c 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -11,18 +11,28 @@ class BaseVariable < Base attr_accessor :mass_assignment + # @return [Range, nil] + attr_reader :presence + # @param return_type [ComplexType, nil] # @param assignment [Parser::AST::Node, nil] First assignment # that was made to this variable # @param assignments [Array] Possible # assignments that may have been made to this variable # @param mass_assignment [Array(Parser::AST::Node, Integer), nil] - def initialize assignment: nil, assignments: [], mass_assignment: nil, return_type: nil, **splat + # @param presence [Range, nil] + # @param presence_certain [Boolean] + # @param mass_assignment [Array(Parser::AST::Node, Integer), nil] + def initialize assignment: nil, assignments: [], mass_assignment: nil, + presence: nil, presence_certain: false, return_type: nil, + **splat super(**splat) @assignments = (assignment.nil? ? [] : [assignment]) + assignments # @type [nil, ::Array(Parser::AST::Node, Integer)] @mass_assignment = mass_assignment @return_type = return_type + @presence = presence + @presence_certain = presence_certain end def reset_generated! @@ -36,6 +46,8 @@ def combine_with(other, attrs={}) assignments: new_assignments, mass_assignment: combine_mass_assignment(other), return_type: combine_return_type(other), + presence: combine_presence(other), + presence_certain: combine_presence_certain(other), }) super(other, new_attrs) end @@ -49,6 +61,16 @@ def combine_mass_assignment(other) mass_assignment || other.mass_assignment end + # If a certain pin is being combined with an uncertain pin, we + # end up with a certain result + # + # @param other [self] + # + # @return [Boolean] + def combine_presence_certain(other) + presence_certain? || other.presence_certain? + end + # @return [Parser::AST::Node, nil] def assignment @assignment ||= assignments.last @@ -61,6 +83,10 @@ def combine_assignments(other) (other.assignments + assignments).uniq end + def inner_desc + super + ", presence=#{presence.inspect}, assignments=#{assignments}" + end + def completion_item_kind Solargraph::LanguageServer::CompletionItemKinds::VARIABLE end @@ -109,7 +135,7 @@ def return_types_from_node(parent_node, api_map) end # @param api_map [ApiMap] - # @return [ComplexType] + # @return [ComplexType, ComplexType::UniqueType] def probe api_map assignment_types = assignments.flat_map { |node| return_types_from_node(node, api_map) } type_from_assignment = ComplexType.new(assignment_types.flat_map(&:items).uniq) unless assignment_types.empty? @@ -144,6 +170,65 @@ def type_desc "#{super} = #{assignment&.type.inspect}" end + # @param other_loc [Location] + def starts_at?(other_loc) + location&.filename == other_loc.filename && + presence && + presence.start == other_loc.range.start + end + + # Narrow the presence range to the intersection of both. + # + # @param other [self] + # + # @return [Range, nil] + def combine_presence(other) + return presence || other.presence if presence.nil? || other.presence.nil? + + Range.new([presence.start, other.presence.start].max, [presence.ending, other.presence.ending].min) + end + + # @param other [self] + # @return [Pin::Closure, nil] + def combine_closure(other) + return closure if self.closure == other.closure + + # choose first defined, as that establishes the scope of the variable + if closure.nil? || other.closure.nil? + Solargraph.assert_or_log(:varible_closure_missing) do + "One of the local variables being combined is missing a closure: " \ + "#{self.inspect} vs #{other.inspect}" + end + return closure || other.closure + end + + if closure.location.nil? || other.closure.location.nil? + return closure.location.nil? ? other.closure : closure + end + + # if filenames are different, this will just pick one + return closure if closure.location <= other.closure.location + + other.closure + end + + # @param other_closure [Pin::Closure] + # @param other_loc [Location] + def visible_at?(other_closure, other_loc) + location.filename == other_loc.filename && + (!presence || presence.include?(other_loc.range.start)) && + visible_in_closure?(other_closure) + end + + def presence_certain? + @presence_certain + end + + protected + + # @return [Range] + attr_writer :presence + private # See if this variable is visible within 'viewing_closure' @@ -151,8 +236,11 @@ def type_desc # @param viewing_closure [Pin::Closure] # @return [Boolean] def visible_in_closure? viewing_closure + return false if closure.nil? + # if we're declared at top level, we can't be seen from within # methods declared tere + return false if viewing_closure.is_a?(Pin::Method) && closure.context.tags == 'Class<>' return true if viewing_closure.binder.namespace == closure.binder.namespace @@ -170,6 +258,20 @@ def visible_in_closure? viewing_closure visible_in_closure?(parent_of_viewing_closure) end + # @param other [self] + # @return [ComplexType, nil] + def combine_return_type(other) + if presence_certain? && return_type&.defined? + # flow sensitive typing has already figured out this type + # has been downcast - use the type it figured out + return return_type + end + if other.presence_certain? && other.return_type&.defined? + return other.return_type + end + combine_types(other, :return_type) + end + # @param other [self] # @param attr [::Symbol] # diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 04a886aa0..b7eeba716 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -22,7 +22,6 @@ def closure=(value) reset_generated! end - # @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/local_variable.rb b/lib/solargraph/pin/local_variable.rb index a4c190261..f0547703a 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -3,126 +3,16 @@ module Solargraph module Pin class LocalVariable < BaseVariable - # @return [Range, nil] - attr_reader :presence - - def presence_certain? - @presence_certain - end - - # @param presence [Range, nil] - # @param presence_certain [Boolean] - # @param splat [Hash] - def initialize presence: nil, presence_certain: false, **splat - super(**splat) - @presence = presence - @presence_certain = presence_certain - end - def combine_with(other, attrs={}) # keep this as a parameter return other.combine_with(self, attrs) if other.is_a?(Parameter) && !self.is_a?(Parameter) - new_assignments = combine_assignments(other) - new_attrs = attrs.merge({ - presence: combine_presence(other), - presence_certain: combine_presence_certain(other), - }) - - super(other, new_attrs) - end - - def inner_desc - super + ", presence=#{presence.inspect}, presence_certain=#{presence_certain?}" - end - - # @param other_loc [Location] - def starts_at?(other_loc) - location&.filename == other_loc.filename && - presence && - presence.start == other_loc.range.start - end - - # @param other [self] - # @return [Pin::Closure, nil] - def combine_closure(other) - return closure if self.closure == other.closure - - # choose first defined, as that establishes the scope of the variable - if closure.nil? || other.closure.nil? - Solargraph.assert_or_log(:varible_closure_missing) do - "One of the local variables being combined is missing a closure: " \ - "#{self.inspect} vs #{other.inspect}" - end - return closure || other.closure - end - - if closure.location.nil? || other.closure.location.nil? - return closure.location.nil? ? other.closure : closure - end - - # if filenames are different, this will just pick one - return closure if closure.location <= other.closure.location - - other.closure - end - - # @param other_closure [Pin::Closure] - # @param other_loc [Location] - def visible_at?(other_closure, other_loc) - location.filename == other_loc.filename && - (!presence || presence.include?(other_loc.range.start)) && - visible_in_closure?(other_closure) + super end def to_rbs (name || '(anon)') + ' ' + (return_type&.to_rbs || 'untyped') end - - # @param other [self] - # @return [ComplexType, nil] - def combine_return_type(other) - if presence_certain? && return_type&.defined? - # flow sensitive typing has already figured out this type - # has been downcast - use the type it figured out - return return_type - end - if other.presence_certain? && other.return_type&.defined? - return other.return_type - end - combine_types(other, :return_type) - end - - def probe api_map - if presence_certain? && return_type&.defined? - # flow sensitive typing has already probed this type - use - # the type it figured out - return return_type.qualify(api_map, *gates) - end - - super - end - - # Narrow the presence range to the intersection of both. - # - # @param other [self] - # - # @return [Range, nil] - def combine_presence(other) - return presence || other.presence if presence.nil? || other.presence.nil? - - Range.new([presence.start, other.presence.start].max, [presence.ending, other.presence.ending].min) - end - - # If a certain pin is being combined with an uncertain pin, we - # end up with a certain result - # - # @param other [self] - # - # @return [Boolean] - def combine_presence_certain(other) - presence_certain? || other.presence_certain? - end end end end From 9ec6433b632107bb6f71925033c72b659e608a66 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 27 Nov 2025 08:14:48 -0500 Subject: [PATCH 706/930] Merge fix --- lib/solargraph/pin/base_variable.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 7c448aad4..61adf1019 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -90,10 +90,6 @@ def combine_with(other, attrs={}) super(other, new_attrs) end - def inner_desc - super + ", intersection_return_type=#{intersection_return_type&.rooted_tags.inspect}, exclude_return_type=#{exclude_return_type&.rooted_tags.inspect}" - end - # @param other [self] # # @return [Array(AST::Node, Integer), nil] @@ -116,7 +112,7 @@ def combine_assignments(other) end def inner_desc - super + ", presence=#{presence.inspect}, assignments=#{assignments}" + super + ", intersection_return_type=#{intersection_return_type&.rooted_tags.inspect}, exclude_return_type=#{exclude_return_type&.rooted_tags.inspect}, presence=#{presence.inspect}, assignments=#{assignments}" end def completion_item_kind From 8e2a094019c753e66757a6d67e73839ff9dd19c8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 27 Nov 2025 08:19:01 -0500 Subject: [PATCH 707/930] Fix merge --- .github/workflows/plugins.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 3cb2b3106..576aa5e0e 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -179,8 +179,6 @@ jobs: # https://github.com/apiology/solargraph/actions/runs/19400815835/job/55508092473?pr=17 rubygems: latest bundler: latest - # https://github.com/castwide/solargraph/actions/runs/19410641794/job/55531497679?pr=1123 - rubygems: latest env: MATRIX_RAILS_VERSION: "7.0" - name: Install gems From 0f3362f43aa0c1381d138597198f4699a628abb7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 27 Nov 2025 08:36:38 -0500 Subject: [PATCH 708/930] Smooth out merge --- lib/solargraph/pin/base_variable.rb | 33 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 337b84e76..447c688f5 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -20,7 +20,6 @@ class BaseVariable < Base # @param assignments [Array] Possible # assignments that may have been made to this variable # @param mass_assignment [Array(Parser::AST::Node, Integer), nil] - # @param presence [Range, nil] # @param exclude_return_type [ComplexType, nil] Ensure any # return type returned will never include any of these unique # types in the unique types of its complex type. @@ -42,10 +41,10 @@ class BaseVariable < Base # @see https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types # @see https://en.wikipedia.org/wiki/Intersection_type#TypeScript_example # @param mass_assignment [Array(Parser::AST::Node, Integer), nil] - def initialize assignment: nil, assignments: [], mass_assignment: nil, - presence: nil, return_type: nil, + # @param presence [Range, nil] + def initialize assignment: nil, assignments: [], mass_assignment: nil, return_type: nil, intersection_return_type: nil, exclude_return_type: nil, - **splat + presence: nil, **splat super(**splat) @assignments = (assignment.nil? ? [] : [assignment]) + assignments # @type [nil, ::Array(Parser::AST::Node, Integer)] @@ -84,9 +83,9 @@ def combine_with(other, attrs={}) assignments: new_assignments, mass_assignment: combine_mass_assignment(other), return_type: combine_return_type(other), - presence: combine_presence(other), intersection_return_type: combine_types(other, :intersection_return_type), - exclude_return_type: combine_types(other, :exclude_return_type) + exclude_return_type: combine_types(other, :exclude_return_type), + presence: combine_presence(other) }) super(other, new_attrs) end @@ -205,6 +204,17 @@ def return_type generate_complex_type || @return_type || intersection_return_type || ComplexType::UNDEFINED end + def typify api_map + raw_return_type = super + + adjust_type(api_map, raw_return_type) + end + + # @sg-ignore need boolish support for ? methods + def presence_certain? + exclude_return_type || intersection_return_type + end + # @param other_loc [Location] # @sg-ignore flow sensitive typing needs to handle attrs def starts_at?(other_loc) @@ -269,17 +279,6 @@ def visible_at?(other_closure, other_loc) visible_in_closure?(other_closure) end - def typify api_map - raw_return_type = super - - adjust_type(api_map, raw_return_type) - end - - # @sg-ignore need boolish support for ? methods - def presence_certain? - exclude_return_type || intersection_return_type - end - protected attr_accessor :exclude_return_type, :intersection_return_type From 38a6d71c0dbf8e3b88b76c82aa83cfcce2ee6093 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 27 Nov 2025 08:44:46 -0500 Subject: [PATCH 709/930] Smooth out merge --- lib/solargraph/pin/base_variable.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 447c688f5..9f79e8b4e 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -50,9 +50,9 @@ def initialize assignment: nil, assignments: [], mass_assignment: nil, return_ty # @type [nil, ::Array(Parser::AST::Node, Integer)] @mass_assignment = mass_assignment @return_type = return_type - @presence = presence @intersection_return_type = intersection_return_type @exclude_return_type = exclude_return_type + @presence = presence end def reset_generated! @@ -85,7 +85,7 @@ def combine_with(other, attrs={}) return_type: combine_return_type(other), intersection_return_type: combine_types(other, :intersection_return_type), exclude_return_type: combine_types(other, :exclude_return_type), - presence: combine_presence(other) + presence: combine_presence(other), }) super(other, new_attrs) end @@ -232,12 +232,6 @@ def starts_at?(other_loc) def combine_presence(other) return presence || other.presence if presence.nil? || other.presence.nil? - if presence_certain? && !other.presence_certain? - return presence - elsif other.presence_certain? && !presence_certain? - return other.presence - end - # @sg-ignore flow sensitive typing needs to handle attrs Range.new([presence.start, other.presence.start].max, [presence.ending, other.presence.ending].min) end From 50dc53d9379b59339fe2b94a6533f35f4621b050 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 27 Nov 2025 09:55:24 -0500 Subject: [PATCH 710/930] RuboCop fix --- lib/solargraph/pin/base_variable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 9f79e8b4e..390e9bbd2 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -85,7 +85,7 @@ def combine_with(other, attrs={}) return_type: combine_return_type(other), intersection_return_type: combine_types(other, :intersection_return_type), exclude_return_type: combine_types(other, :exclude_return_type), - presence: combine_presence(other), + presence: combine_presence(other) }) super(other, new_attrs) end From 9dda14ebf0701fe66693a88c68f88eaaf8c0f287 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 27 Nov 2025 09:57:42 -0500 Subject: [PATCH 711/930] Finalize todo --- lib/solargraph/source/chain/instance_variable.rb | 2 +- lib/solargraph/type_checker/rules.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/source/chain/instance_variable.rb b/lib/solargraph/source/chain/instance_variable.rb index 2329f24b9..a4d4e5aa1 100644 --- a/lib/solargraph/source/chain/instance_variable.rb +++ b/lib/solargraph/source/chain/instance_variable.rb @@ -19,7 +19,7 @@ def resolve api_map, name_pin, locals private - # TODO: This should fail typechecking - ivar is nullable + # @todo: Missed nil violation # @return [Location] attr_reader :location end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 28733605d..f5be6bb3b 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -61,7 +61,7 @@ def require_inferred_type_params? # # False negatives: # - # @todo 3: Missed nil violation + # @todo 4: Missed nil violation # # pending code fixes (277): # From 559cd7ce482c226359aa229b7c738b5b111c5384 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 27 Nov 2025 10:27:51 -0500 Subject: [PATCH 712/930] Update lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 40c3167f8..b5a7805d9 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb @@ -38,7 +38,7 @@ def process # lvasgn is a local variable locals.find { |l| l.location == location } elsif lhs.type == :ivasgn - # e.g., ivasgn is an instance variable, etc + # ivasgn is an instance variable assignment ivars.find { |iv| iv.location == location } else pins.find { |iv| iv.location == location && iv.is_a?(Pin::BaseVariable) } From 6e558b724b75401969fd656fc35f8ffa4c4d297d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 28 Nov 2025 13:12:03 -0500 Subject: [PATCH 713/930] Avoid ignoring valid pins in typechecking An overly-conservative check in type_checker.rb was suppressing later warnings on method parameters with no default values --- lib/solargraph/type_checker.rb | 7 ++++--- spec/type_checker/levels/strong_spec.rb | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 4600767b5..262d65137 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -195,7 +195,7 @@ def variable_type_tag_problems if pin.return_type.defined? declared = pin.typify(api_map) next if declared.duck_type? - if declared.defined? + if declared.defined? && pin.assignment if rules.validate_tags? inferred = pin.probe(api_map) if inferred.undefined? @@ -216,7 +216,7 @@ def variable_type_tag_problems elsif !pin.is_a?(Pin::Parameter) && !resolved_constant?(pin) result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", pin: pin) end - else + elsif pin.assignment inferred = pin.probe(api_map) if inferred.undefined? && declared_externally?(pin) ignored_pins.push pin @@ -604,7 +604,8 @@ def external? pin # @param pin [Pin::BaseVariable] def declared_externally? pin - return true if pin.assignment.nil? + raise "No assignment found" if pin.assignment.nil? + chain = Solargraph::Parser.chain(pin.assignment, filename) rng = Solargraph::Range.from_node(pin.assignment) closure_pin = source_map.locate_closure_pin(rng.start.line, rng.start.column) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 970435dc3..5c7544f17 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -4,6 +4,21 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end + it 'provides nil checking on calls from parameters without assignments' do + pending('https://github.com/castwide/solargraph/pull/1127') + + checker = type_checker(%( + # @param baz [String, nil] + # + # @return [String] + def quux(baz) + baz.upcase # ERROR: Unresolved call to upcase on String, nil + end + )) + expect(checker.problems.map(&:message)).to eq(['#quux return type could not be inferred', + 'Unresolved call to upcase on String, nil']) + end + it 'does not complain on array dereference' do checker = type_checker(%( # @param idx [Integer, nil] an index From 890d638cb18b9f09c2f34893eb121b1d52062a33 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 28 Nov 2025 13:21:17 -0500 Subject: [PATCH 714/930] Fix newly found type issue --- lib/solargraph/shell.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 046a74296..688ec90ff 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -273,6 +273,7 @@ def pin path else pins = api_map.get_path_pins path end + # @type [Hash{Symbol => Pin::Base}] references = {} pin = pins.first case pin From 40f547f0fb0fbceadb567eb447a9f06cfb801d7b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 28 Nov 2025 15:55:08 -0500 Subject: [PATCH 715/930] Annotations needed after https://github.com/castwide/solargraph/pull/1130 --- lib/solargraph/api_map.rb | 1 + lib/solargraph/api_map/constants.rb | 12 +++---- lib/solargraph/api_map/index.rb | 34 ++++++++++++++++--- lib/solargraph/api_map/source_to_yard.rb | 3 ++ lib/solargraph/api_map/store.rb | 11 ++++-- lib/solargraph/complex_type.rb | 1 + lib/solargraph/complex_type/type_methods.rb | 1 + lib/solargraph/complex_type/unique_type.rb | 6 ++++ lib/solargraph/diagnostics/rubocop_helpers.rb | 5 ++- lib/solargraph/doc_map.rb | 3 ++ lib/solargraph/equality.rb | 1 + lib/solargraph/gem_pins.rb | 3 ++ lib/solargraph/language_server/host.rb | 1 + .../message/text_document/formatting.rb | 2 ++ lib/solargraph/location.rb | 1 + .../parser_gem/node_processors/and_node.rb | 1 + .../parser_gem/node_processors/send_node.rb | 2 ++ lib/solargraph/pin/base.rb | 2 ++ lib/solargraph/pin/base_variable.rb | 1 + lib/solargraph/pin/block.rb | 1 + lib/solargraph/pin/callable.rb | 2 ++ lib/solargraph/pin/method.rb | 18 +++++++++- lib/solargraph/pin/parameter.rb | 4 +++ lib/solargraph/pin/search.rb | 3 ++ lib/solargraph/position.rb | 1 + lib/solargraph/range.rb | 4 +++ lib/solargraph/rbs_map/conversions.rb | 13 +++++++ lib/solargraph/shell.rb | 2 ++ lib/solargraph/source/chain.rb | 2 ++ lib/solargraph/source/chain/call.rb | 1 + lib/solargraph/source/chain/if.rb | 2 +- lib/solargraph/source/chain/link.rb | 2 +- lib/solargraph/source/chain/or.rb | 2 +- lib/solargraph/type_checker.rb | 2 ++ lib/solargraph/yard_map/mapper/to_method.rb | 3 +- lib/solargraph/yard_map/to_method.rb | 3 +- solargraph.gemspec | 1 + 37 files changed, 136 insertions(+), 21 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index c4fd49134..936f7406d 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -39,6 +39,7 @@ def initialize pins: [] # @param other [Object] def eql?(other) self.class == other.class && + # @sg-ignore Flow sensitive typing needs to handle self.class == other.class equality_fields == other.equality_fields end diff --git a/lib/solargraph/api_map/constants.rb b/lib/solargraph/api_map/constants.rb index 0df8d83ce..f643e2ad2 100644 --- a/lib/solargraph/api_map/constants.rb +++ b/lib/solargraph/api_map/constants.rb @@ -45,7 +45,7 @@ def dereference pin # Collect a list of all constants defined in the specified gates. # # @param gates [Array, String>] - # @return [Array] + # @return [Array] def collect(*gates) flat = gates.flatten cached_collect[flat] || collect_and_cache(flat) @@ -155,7 +155,7 @@ def simple_resolve name, gate, internal end # @param gates [Array] - # @return [Array] + # @return [Array] def collect_and_cache gates skip = Set.new cached_collect[gates] = gates.flat_map do |gate| @@ -168,7 +168,7 @@ def cached_resolve @cached_resolve ||= {} end - # @return [Hash{Array => Array}] + # @return [Hash{Array => Array}] def cached_collect @cached_collect ||= {} end @@ -213,7 +213,7 @@ def inner_qualify name, root, skip return fqns if store.namespace_exists?(fqns) incs = store.get_includes(roots.join('::')) incs.each do |inc| - foundinc = inner_qualify(name, inc.parametrized_tag.to_s, skip) + foundinc = inner_qualify(name, inc.type.to_s, skip) possibles.push foundinc unless foundinc.nil? end roots.pop @@ -221,7 +221,7 @@ def inner_qualify name, root, skip if possibles.empty? incs = store.get_includes('') incs.each do |inc| - foundinc = inner_qualify(name, inc.parametrized_tag.to_s, skip) + foundinc = inner_qualify(name, inc.type.to_s, skip) possibles.push foundinc unless foundinc.nil? end end @@ -233,7 +233,7 @@ def inner_qualify name, root, skip # @param fqns [String] # @param visibility [Array] # @param skip [Set] - # @return [Array] + # @return [Array] def inner_get_constants fqns, visibility, skip return [] if fqns.nil? || skip.include?(fqns) skip.add fqns diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index cb61a28eb..944f02c79 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -17,16 +17,22 @@ def pins # @return [Hash{String => Array}] def namespace_hash + # @param h [String] + # @param k [Array] @namespace_hash ||= Hash.new { |h, k| h[k] = [] } end # @return [Hash{String => Array}] def pin_class_hash + # @param h [String] + # @param k [Array] @pin_class_hash ||= Hash.new { |h, k| h[k] = [] } end # @return [Hash{String => Array}] def path_pin_hash + # @param h [String] + # @param k [Array] @path_pin_hash ||= Hash.new { |h, k| h[k] = [] } end @@ -42,26 +48,36 @@ def pins_by_class klass # @return [Hash{String => Array}] def include_references + # @param h [String] + # @param k [Array] @include_references ||= Hash.new { |h, k| h[k] = [] } end # @return [Hash{String => Array}] def include_reference_pins + # @param h [String] + # @param k [Array] @include_reference_pins ||= Hash.new { |h, k| h[k] = [] } end # @return [Hash{String => Array}] def extend_references + # @param h [String] + # @param k [Array] @extend_references ||= Hash.new { |h, k| h[k] = [] } end # @return [Hash{String => Array}] def prepend_references + # @param h [String] + # @param k [Array] @prepend_references ||= Hash.new { |h, k| h[k] = [] } end # @return [Hash{String => Array}] def superclass_references + # @param h [String] + # @param k [Array] @superclass_references ||= Hash.new { |h, k| h[k] = [] } end @@ -99,12 +115,18 @@ def catalog new_pins @pin_select_cache = {} pins.concat new_pins set = new_pins.to_set + # @param k [String] + # @param v [Set] set.classify(&:class) - .map { |k, v| pin_class_hash[k].concat v.to_a } + .map { |k, v| pin_class_hash[k].concat v.to_a } + # @param k [String] + # @param v [Set] set.classify(&:namespace) - .map { |k, v| namespace_hash[k].concat v.to_a } + .map { |k, v| namespace_hash[k].concat v.to_a } + # @param k [String] + # @param v [Set] set.classify(&:path) - .map { |k, v| path_pin_hash[k].concat v.to_a } + .map { |k, v| path_pin_hash[k].concat v.to_a } @namespaces = path_pin_hash.keys.compact.to_set map_references Pin::Reference::Include, include_references map_references Pin::Reference::Prepend, prepend_references @@ -115,11 +137,12 @@ def catalog new_pins end # @generic T - # @param klass [Class] - # @param hash [Hash{String => T}] + # @param klass [Class>] + # @param hash [Hash{String => generic}] # # @return [void] def map_references klass, hash + # @param pin [generic] pins_by_class(klass).each do |pin| hash[pin.namespace].push pin end @@ -127,6 +150,7 @@ def map_references klass, hash # @return [void] def map_overrides + # @param ovr [Pin::Reference::Override] pins_by_class(Pin::Reference::Override).each do |ovr| logger.debug { "ApiMap::Index#map_overrides: Looking at override #{ovr} for #{ovr.name}" } pins = path_pin_hash[ovr.name] diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index ccbed3eb6..b45754d80 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -32,11 +32,13 @@ def rake_yard store next end if pin.type == :class + # @param obj [YARD::CodeObjects::RootObject] code_object_map[pin.path] ||= YARD::CodeObjects::ClassObject.new(root_code_object, pin.path) { |obj| next if pin.location.nil? || pin.location.filename.nil? obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) } else + # @param obj [YARD::CodeObjects::RootObject] code_object_map[pin.path] ||= YARD::CodeObjects::ModuleObject.new(root_code_object, pin.path) { |obj| next if pin.location.nil? || pin.location.filename.nil? obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) @@ -65,6 +67,7 @@ def rake_yard store next end + # @param obj [YARD::CodeObjects::RootObject] code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope) { |obj| next if pin.location.nil? || pin.location.filename.nil? obj.add_file pin.location.filename, pin.location.range.start.line diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index c41e19c09..32fa8bfd4 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -17,7 +17,7 @@ def pins index.pins end - # @param pinsets [Array>] + # @param pinsets [Array>] # - pinsets[0] = core Ruby pins # - pinsets[1] = documentation/gem pins # - pinsets[2] = convention pins @@ -60,6 +60,7 @@ def inspect # @return [Enumerable] def get_constants fqns, visibility = [:public] namespace_children(fqns).select { |pin| + # @sg-ignore flow-sensitive typing not smart enough to handle this case !pin.name.empty? && (pin.is_a?(Pin::Namespace) || pin.is_a?(Pin::Constant)) && visibility.include?(pin.visibility) } end @@ -70,6 +71,7 @@ def get_constants fqns, visibility = [:public] # @return [Enumerable] def get_methods fqns, scope: :instance, visibility: [:public] all_pins = namespace_children(fqns).select do |pin| + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 pin.is_a?(Pin::Method) && pin.scope == scope && visibility.include?(pin.visibility) end GemPins.combine_method_pins_by_path(all_pins) @@ -258,7 +260,7 @@ def get_ancestors(fqns) # @param fqns [String] # - # @return [Array] + # @return [Array] def get_ancestor_references(fqns) (get_prepends(fqns) + get_includes(fqns) + [get_superclass(fqns)]).compact end @@ -275,7 +277,7 @@ def index @indexes.last end - # @param pinsets [Array>] + # @param pinsets [Array>] # # @return [void] def catalog pinsets @@ -296,6 +298,9 @@ def catalog pinsets # @return [Hash{::Array(String, String) => ::Array}] def fqns_pins_map + # @param h [Hash{::Array(String, String) => ::Array}] + # @param base [String] + # @param name [String] @fqns_pins_map ||= Hash.new do |h, (base, name)| value = namespace_children(base).select { |pin| pin.name == name && pin.is_a?(Pin::Namespace) } h[[base, name]] = value diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 669a66900..555d60947 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -324,6 +324,7 @@ def parse *strings, partial: false paren_stack = 0 base = String.new subtype_string = String.new + # @param char [String] type_string&.each_char do |char| if char == '=' #raise ComplexTypeError, "Invalid = in type #{type_string}" unless curly_stack > 0 diff --git a/lib/solargraph/complex_type/type_methods.rb b/lib/solargraph/complex_type/type_methods.rb index d8d4fc7d7..ead1559af 100644 --- a/lib/solargraph/complex_type/type_methods.rb +++ b/lib/solargraph/complex_type/type_methods.rb @@ -190,6 +190,7 @@ def scope # @param other [Object] def == other return false unless self.class == other.class + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 tag == other.tag end diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 05a585dcf..e490bff35 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -138,11 +138,17 @@ def determine_non_literal_name def eql?(other) self.class == other.class && + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 @name == other.name && + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 @key_types == other.key_types && + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 @subtypes == other.subtypes && + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 @rooted == other.rooted? && + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 @all_params == other.all_params && + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 @parameters_type == other.parameters_type end diff --git a/lib/solargraph/diagnostics/rubocop_helpers.rb b/lib/solargraph/diagnostics/rubocop_helpers.rb index f6f4c82c8..fc458956e 100644 --- a/lib/solargraph/diagnostics/rubocop_helpers.rb +++ b/lib/solargraph/diagnostics/rubocop_helpers.rb @@ -20,9 +20,11 @@ def require_rubocop(version = nil) gem_lib_path = File.join(gem_path, 'lib') $LOAD_PATH.unshift(gem_lib_path) unless $LOAD_PATH.include?(gem_lib_path) rescue Gem::MissingSpecVersionError => e + # @type [Array] + specs = e.specs raise InvalidRubocopVersionError, "could not find '#{e.name}' (#{e.requirement}) - "\ - "did find: [#{e.specs.map { |s| s.version.version }.join(', ')}]" + "did find: [#{specs.map { |s| s.version.version }.join(', ')}]" end require 'rubocop' end @@ -36,6 +38,7 @@ def generate_options filename, code args = ['-f', 'j', '--force-exclusion', filename] base_options = RuboCop::Options.new options, paths = base_options.parse(args) + # @sg-ignore options[:stdin] = code [options, paths] end diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 05f2f1647..96fa13acd 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -358,13 +358,16 @@ def change_gemspec_version gemspec, version # @return [Array] def fetch_dependencies gemspec # @param spec [Gem::Dependency] + # @param deps [Set] 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? + # @sg-ignore Unresolved call to requirement on Gem::Dependency dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement) deps.merge fetch_dependencies(dep) if deps.add?(dep) rescue Gem::MissingSpecError + # @sg-ignore Unresolved call to requirement on Gem::Dependency Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found in RubyGems." end.to_a end diff --git a/lib/solargraph/equality.rb b/lib/solargraph/equality.rb index 0667efacd..f8c50ff31 100644 --- a/lib/solargraph/equality.rb +++ b/lib/solargraph/equality.rb @@ -12,6 +12,7 @@ module Equality # @return [Boolean] def eql?(other) self.class.eql?(other.class) && + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 equality_fields.eql?(other.equality_fields) end diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index a193a8a39..1c4330389 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -27,6 +27,8 @@ def self.combine_method_pins_by_path(pins) def self.combine_method_pins(*pins) # @type [Pin::Method, nil] combined_pin = nil + # @param memo [Pin::Method, nil] + # @param pin [Pin::Method] out = pins.reduce(combined_pin) do |memo, pin| next pin if memo.nil? if memo == pin && memo.source != :combined @@ -63,6 +65,7 @@ def self.combine(yard_pins, rbs_pins) next yard_pin unless rbs_pin && yard_pin.class == Pin::Method unless rbs_pin + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 logger.debug { "GemPins.combine: No rbs pin for #{yard_pin.path} - using YARD's '#{yard_pin.inspect} (return_type=#{yard_pin.return_type}; signatures=#{yard_pin.signatures})" } next yard_pin end diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index 53da20175..b228bdba6 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -504,6 +504,7 @@ def locate_pins params name: 'new', scope: :class, location: pin.location, + # @sg-ignore Unresolved call to parameters on Solargraph::Pin::Base parameters: pin.parameters, return_type: ComplexType.try_parse(params['data']['path']), comments: pin.comments, diff --git a/lib/solargraph/language_server/message/text_document/formatting.rb b/lib/solargraph/language_server/message/text_document/formatting.rb index 821de7ffc..d67a0b414 100644 --- a/lib/solargraph/language_server/message/text_document/formatting.rb +++ b/lib/solargraph/language_server/message/text_document/formatting.rb @@ -18,6 +18,7 @@ def process require_rubocop(config['version']) options, paths = ::RuboCop::Options.new.parse(args) + # @sg-ignore Unresolved call to []= options[:stdin] = original # Ensure only one instance of RuboCop::Runner is running at @@ -28,6 +29,7 @@ def process ::RuboCop::Runner.new(options, ::RuboCop::ConfigStore.new).run(paths) end end + # @sg-ignore Unresolved call to []= result = options[:stdin] log_corrections(corrections) diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index 713b4fef1..df92668bf 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -71,6 +71,7 @@ def self.from_node(node) # @param other [BasicObject] def == other return false unless other.is_a?(Location) + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 filename == other.filename and range == other.range end diff --git a/lib/solargraph/parser/parser_gem/node_processors/and_node.rb b/lib/solargraph/parser/parser_gem/node_processors/and_node.rb index d3485af7c..a761ae38c 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/and_node.rb @@ -11,6 +11,7 @@ def process process_children position = get_node_start_position(node) + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 enclosing_breakable_pin = pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location.range.contain?(position)}.last FlowSensitiveTyping.new(locals, enclosing_breakable_pin).process_and(node) end 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 645baf00f..e63a23abc 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -231,6 +231,7 @@ def process_module_function closure: cm, name: ivar.name, comments: ivar.comments, + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 assignment: ivar.assignment, source: :parser ) @@ -239,6 +240,7 @@ def process_module_function closure: mm, name: ivar.name, comments: ivar.comments, + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 assignment: ivar.assignment, source: :parser ) diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 511c7deb7..57d083453 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -259,6 +259,7 @@ def rbs_location? def assert_same_macros(other) return unless self.source == :yardoc && other.source == :yardoc assert_same_count(other, :macros) + # @param [YARD::Tags::MacroDirective] assert_same_array_content(other, :macros) { |macro| macro.tag.name } end @@ -466,6 +467,7 @@ def nearly? other # @param other [Object] def == other return false unless nearly? other + # @sg-ignore Should add more explicit type check on other comments == other.comments && location == other.location end diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index da1f15cb2..93b568647 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -106,6 +106,7 @@ def probe api_map # @param other [Object] def == other return false unless super + # @sg-ignore Should add type check on other assignment == other.assignment end diff --git a/lib/solargraph/pin/block.rb b/lib/solargraph/pin/block.rb index 227bc0873..0c6ecd258 100644 --- a/lib/solargraph/pin/block.rb +++ b/lib/solargraph/pin/block.rb @@ -54,6 +54,7 @@ def typify_parameters(api_map) locals = clip.locals - [self] meths = chain.define(api_map, closure, locals) # @todo Convert logic to use signatures + # @param meth [Pin::Method] meths.each do |meth| next if meth.block.nil? diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 207c2619b..edbc3f941 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -67,6 +67,8 @@ def generics # @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 + # @param param [Pin::Parameter] + # @param other_param [Pin::Parameter] parameters.zip(other.parameters).map do |param, other_param| if param.nil? && other_param.block? other_param diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 011f096f6..86bf1cd09 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -34,14 +34,18 @@ def initialize visibility: :public, explicit: true, block: :undefined, node: nil @anon_splat = anon_splat end + # @param signature_pins [Array] # @return [Array] def combine_all_signature_pins(*signature_pins) + # @type [Hash{Array => Array}] by_arity = {} signature_pins.each do |signature_pin| by_arity[signature_pin.arity] ||= [] by_arity[signature_pin.arity] << signature_pin end by_arity.transform_values! do |same_arity_pins| + # @param memo [Pin::Signature, nil] + # @param signature [Pin::Signature] same_arity_pins.reduce(nil) do |memo, signature| next signature if memo.nil? memo.combine_with(signature) @@ -88,6 +92,7 @@ def combine_with(other, attrs = {}) end new_attrs = { visibility: combine_visibility(other), + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 explicit: explicit? || other.explicit?, block: combine_blocks(other), node: choose_node(other, :node), @@ -376,11 +381,14 @@ def attribute? @attribute end - # @parm other [Method] + # @parm other [self] def nearly? other super && + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 parameters == other.parameters && + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 scope == other.scope && + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 visibility == other.visibility end @@ -392,9 +400,12 @@ def probe api_map def overloads # Ignore overload tags with nil parameters. If it's not an array, the # tag's source is likely malformed. + + # @param tag [YARD::Tags::OverloadTag] @overloads ||= docstring.tags(:overload).select(&:parameters).map do |tag| Pin::Signature.new( generics: generics, + # @param src [Array(String, String)] parameters: tag.parameters.map do |src| name, decl = parse_overload_param(src.first) Pin::Parameter.new( @@ -507,6 +518,7 @@ def clean_param name # # @return [ComplexType] def param_type_from_name(tag, name) + # @param t [YARD::Tags::Tag] param = tag.tags(:param).select { |t| t.name == name }.first return ComplexType::UNDEFINED unless param ComplexType.try_parse(*param.types) @@ -522,8 +534,12 @@ def generate_complex_type # @param api_map [ApiMap] # @return [ComplexType, nil] def see_reference api_map + # This should actually be an intersection type + # @param ref [YARD::Tags::Tag, Solargraph::Yard::Tags::RefTag] docstring.ref_tags.each do |ref| + # @sg-ignore ref should actually be an intersection type next unless ref.tag_name == 'return' && ref.owner + # @sg-ignore ref should actually be an intersection type result = resolve_reference(ref.owner.to_s, api_map) return result unless result.nil? end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 947513689..91c205921 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -232,8 +232,12 @@ def typify_method_param api_map # @param skip [::Array] # @return [::Array] def see_reference heredoc, api_map, skip = [] + # This should actually be an intersection type + # @param ref [YARD::Tags::Tag, Solargraph::Yard::Tags::RefTag] heredoc.ref_tags.each do |ref| + # @sg-ignore ref should actually be an intersection type next unless ref.tag_name == 'param' && ref.owner + # @sg-ignore ref should actually be an intersection type result = resolve_reference(ref.owner.to_s, api_map, skip) return result unless result.nil? end diff --git a/lib/solargraph/pin/search.rb b/lib/solargraph/pin/search.rb index f92978a35..0f9883b65 100644 --- a/lib/solargraph/pin/search.rb +++ b/lib/solargraph/pin/search.rb @@ -42,6 +42,9 @@ def do_query Result.new(match, pin) if match > 0.7 end .compact + # @param a [self] + # @param b [self] + # @sg-ignore https://github.com/castwide/solargraph/pull/1050 .sort { |a, b| b.match <=> a.match } .map(&:pin) end diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 1197038ef..eed62561b 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -111,6 +111,7 @@ def self.normalize object def == other return false unless other.is_a?(Position) + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 line == other.line and character == other.character end end diff --git a/lib/solargraph/range.rb b/lib/solargraph/range.rb index 7a9bc0e30..86452d646 100644 --- a/lib/solargraph/range.rb +++ b/lib/solargraph/range.rb @@ -27,9 +27,12 @@ def initialize start, ending # @param other [BasicObject] def <=>(other) return nil unless other.is_a?(Range) + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 if start == other.start + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 ending <=> other.ending else + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 start <=> other.start end end @@ -98,6 +101,7 @@ def self.from_expr expr def == other return false unless other.is_a?(Range) + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 start == other.start && ending == other.ending end diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 3e777f726..54bca0f73 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -65,6 +65,7 @@ def convert_decl_to_pin decl, closure # STDERR.puts "Skipping interface #{decl.name.relative!}" interface_decl_to_pin decl, closure when RBS::AST::Declarations::TypeAlias + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 type_aliases[decl.name.to_s] = decl when RBS::AST::Declarations::Module module_decl_to_pin decl @@ -426,6 +427,7 @@ def method_def_to_pin decl, closure, context # @param pin [Pin::Method] # @return [void] def method_def_to_sigs decl, pin + # @param overload [RBS::AST::Members::MethodDefinition::Overload] decl.overloads.map do |overload| type_location = location_decl_to_pin_location(overload.method_type.location) generics = overload.method_type.type_params.map(&:name).map(&:to_s) @@ -466,12 +468,16 @@ def parts_of_function type, pin parameters = [] arg_num = -1 type.type.required_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + # @sg-ignore RBS generic type understanding issue parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, source: :rbs, type_location: type_location) end type.type.optional_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, type_location: type_location, source: :rbs) @@ -489,18 +495,23 @@ def parts_of_function type, pin return_type: rest_positional_type,) end type.type.trailing_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, type_location: type_location) end type.type.required_keywords.each do |orig, param| + # @sg-ignore RBS generic type understanding issue name = orig ? orig.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, source: :rbs, type_location: type_location) end type.type.optional_keywords.each do |orig, param| + # @sg-ignore RBS generic type understanding issue name = orig ? orig.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, type_location: type_location, source: :rbs) @@ -792,7 +803,9 @@ def other_type_to_tag type # @param namespace [Pin::Namespace] # @return [void] def add_mixins decl, namespace + # @param mixin [RBS::AST::Members::Include, RBS::AST::Members::Members::Extend, RBS::AST::Members::Members::Prepend] decl.each_mixin do |mixin| + # @todo are we handling prepend correctly? klass = mixin.is_a?(RBS::AST::Members::Include) ? Pin::Reference::Include : Pin::Reference::Extend type = build_type(mixin.name, mixin.args) generic_values = type.all_params.map(&:to_s) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 046a74296..bd0e5026d 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -79,6 +79,7 @@ def config(directory = '.') conf['extensions'].push m end end + # @param file [File] File.open(File.join(directory, '.solargraph.yml'), 'w') do |file| file.puts conf.to_yaml end @@ -273,6 +274,7 @@ def pin path else pins = api_map.get_path_pins path end + # @type [Hash{Symbol => Pin::Base}] references = {} pin = pins.first case pin diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index c08d04878..f7a03b552 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -264,6 +264,8 @@ def infer_from_definitions pins, context, api_map, locals ComplexType::UNDEFINED elsif types.length > 1 # Move nil to the end by convention + + # @param a [ComplexType::UniqueType] sorted = types.flat_map(&:items).sort { |a, _| a.tag == 'nil' ? 1 : 0 } ComplexType.new(sorted.uniq) else diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 24d10656d..194771c1f 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -266,6 +266,7 @@ def yield_pins api_map, name_pin method_pin = find_method_pin(name_pin) return [] unless method_pin + # @param signature_pin [Pin::Signature] method_pin.signatures.map(&:block).compact.map do |signature_pin| return_type = signature_pin.return_type.qualify(api_map, *name_pin.gates) signature_pin.proxy(return_type) 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/link.rb b/lib/solargraph/source/chain/link.rb index bcd9eb196..344f7affd 100644 --- a/lib/solargraph/source/chain/link.rb +++ b/lib/solargraph/source/chain/link.rb @@ -38,7 +38,7 @@ def constant? # @param api_map [ApiMap] # @param name_pin [Pin::Base] - # @param locals [::Enumerable] + # @param locals [::Array] # @return [::Array] def resolve api_map, name_pin, locals [] 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 4600767b5..996830c4d 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -460,7 +460,9 @@ def kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pi else ptype = data[:qualified] unless ptype.undefined? + # @sg-ignore https://github.com/castwide/solargraph/pull/1127 argtype = argchain.infer(api_map, closure_pin, locals) + # @sg-ignore Unresolved call to defined? 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 diff --git a/lib/solargraph/yard_map/mapper/to_method.rb b/lib/solargraph/yard_map/mapper/to_method.rb index d8e3b8b43..0838b9f4f 100644 --- a/lib/solargraph/yard_map/mapper/to_method.rb +++ b/lib/solargraph/yard_map/mapper/to_method.rb @@ -11,7 +11,7 @@ module ToMethod ["Rails::Engine", :class, "find_root_with_flag"] => :public } - # @param code_object [YARD::CodeObjects::Base] + # @param code_object [YARD::CodeObjects::MethodObject] # @param name [String, nil] # @param scope [Symbol, nil] # @param visibility [Symbol, nil] @@ -85,6 +85,7 @@ def get_parameters code_object, location, comments, pin # HACK: Skip `nil` and `self` parameters that are sometimes emitted # for methods defined in C # See https://github.com/castwide/solargraph/issues/345 + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 code_object.parameters.select { |a| a[0] && a[0] != 'self' }.map do |a| Solargraph::Pin::Parameter.new( location: location, diff --git a/lib/solargraph/yard_map/to_method.rb b/lib/solargraph/yard_map/to_method.rb index 3ecb7ac26..010db89a5 100644 --- a/lib/solargraph/yard_map/to_method.rb +++ b/lib/solargraph/yard_map/to_method.rb @@ -15,6 +15,7 @@ def get_parameters code_object, location, comments # HACK: Skip `nil` and `self` parameters that are sometimes emitted # for methods defined in C # See https://github.com/castwide/solargraph/issues/345 + # @sg-ignore https://github.com/castwide/solargraph/pull/1114 code_object.parameters.select { |a| a[0] && a[0] != 'self' }.map do |a| Solargraph::Pin::Parameter.new( location: location, @@ -57,7 +58,7 @@ def arg_type a include Helpers - # @param code_object [YARD::CodeObjects::Base] + # @param code_object [YARD::CodeObjects::MethodObject] # @param name [String, nil] # @param scope [Symbol, nil] # @param visibility [Symbol, nil] diff --git a/solargraph.gemspec b/solargraph.gemspec index 49265f9c6..0414f27d8 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -2,6 +2,7 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + '/lib' require 'solargraph/version' require 'date' +# @param s [Gem::Specification] Gem::Specification.new do |s| s.name = 'solargraph' s.version = Solargraph::VERSION From 1594b958e2362d2a03c0e31d6bb178327cc7d6f2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 28 Nov 2025 16:15:19 -0500 Subject: [PATCH 716/930] Annotations needed after https://github.com/castwide/solargraph/pull/1130 --- lib/solargraph/api_map/store.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index 32fa8bfd4..f86080d12 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -247,6 +247,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? + # @param ref [String] refs.map(&:parametrized_tag).map(&:to_s).each do |ref| next if ref.nil? || ref.empty? || visited.include?(ref) ancestors << ref From 405d9bef1d3c1071cfaf4cadb5ce8f054352a5f9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 28 Nov 2025 17:59:43 -0500 Subject: [PATCH 717/930] Handle a self resolution case --- lib/solargraph/pin/parameter.rb | 4 +++- spec/type_checker/levels/strong_spec.rb | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 797f7b5da..cd0de8c57 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -172,7 +172,9 @@ def typify api_map # sniff based on param tags new_type = closure.is_a?(Pin::Block) ? typify_block_param(api_map) : typify_method_param(api_map) - adjust_type api_map, new_type + return adjust_type api_map, new_type.self_to_type(full_context) if new_type.defined? + + adjust_type api_map, super.self_to_type(full_context) end # @param atype [ComplexType] diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 970435dc3..c7e695517 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -4,6 +4,23 @@ def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end + it 'understands self type when passed as parameter' do + checker = type_checker(%( + class Location + # @return [String] + attr_reader :filename + + # @param other [self] + def <=>(other) + return nil unless other.is_a?(Location) + + filename <=> other.filename + end + end + )) + expect(checker.problems.map(&:message)).to be_empty + end + it 'does not complain on array dereference' do checker = type_checker(%( # @param idx [Integer, nil] an index From 5a4aae2dc35ac3834b80dcf1449ad8ed3278511c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 28 Nov 2025 19:07:09 -0500 Subject: [PATCH 718/930] Drop @sg-ignores --- lib/solargraph/pin/base_variable.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 1a3488346..dc4ecce2b 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -52,12 +52,9 @@ def combine_with(other, attrs={}) assignment: assert_same(other, :assignment), mass_assignment: assert_same(other, :mass_assignment), return_type: combine_return_type(other), - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 intersection_return_type: combine_types(other, :intersection_return_type), - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 exclude_return_type: combine_types(other, :exclude_return_type), }) - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 super(other, new_attrs) end From f74a49dd7b3553679596977aa5665af9bc375fa1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 29 Nov 2025 14:09:10 -0500 Subject: [PATCH 719/930] Add missing spec after experiencing subtle merge error --- spec/type_checker/levels/strong_spec.rb | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 3b96683cf..133a540f7 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -722,5 +722,33 @@ def bar expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round"]) end + + it 'uses cast type instead of defined type' do + checker = type_checker(%( + # frozen_string_literal: true + + class Base; end + + class Subclass < Base + # @return [String] + attr_reader :bar + end + + class Foo + # @param bases [::Array] + # @return [void] + def baz(bases) + # @param sub [Subclass] + bases.each do |sub| + puts sub.bar + end + end + end + )) + + # expect 'sub' to be treated as 'Subclass' inside the block, and + # an error when trying to declare sub as Subclass + expect(checker.problems.map(&:message)).not_to include('Unresolved call to bar on Base') + end end end From 140137336004925f5fc597dbd7af4397dfb7d857 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 29 Nov 2025 14:15:29 -0500 Subject: [PATCH 720/930] Fix type --- 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 944f02c79..ad96fd759 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -138,7 +138,7 @@ def catalog new_pins # @generic T # @param klass [Class>] - # @param hash [Hash{String => generic}] + # @param hash [Hash{String => Array>}] # # @return [void] def map_references klass, hash From d27a522d7757f35eaeec0a24fb552beb0a9a086a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 29 Nov 2025 14:36:25 -0500 Subject: [PATCH 721/930] Fix annotations --- lib/solargraph/library.rb | 2 -- lib/solargraph/pin/delegated_method.rb | 1 - lib/solargraph/pin/method.rb | 6 +++--- lib/solargraph/pin/parameter.rb | 2 +- lib/solargraph/rbs_map/conversions.rb | 5 ++++- lib/solargraph/type_checker.rb | 3 --- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index b0afaad4e..5c7851201 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -303,9 +303,7 @@ def locate_ref location return nil if pin.nil? # @param full [String] return_if_match = proc do |full| - # @sg-ignore Need to handle restarg parameters in block parameters if source_map_hash.key?(full) - # @sg-ignore Need to handle restarg parameters in block parameters return Location.new(full, Solargraph::Range.from_to(0, 0, 0, 0)) end end diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index 530804747..bcf5b5912 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 Need to handle restarg parameters in block parameters 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/pin/method.rb b/lib/solargraph/pin/method.rb index b942193a9..260377a48 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -536,11 +536,11 @@ def generate_complex_type # @return [ComplexType, nil] def see_reference api_map # This should actually be an intersection type - # @param ref [YARD::Tags::Tag, Solargraph::Yard::Tags::RefTag] + # @param ref [YARD::Tags::Tag, YARD::Tags::RefTag] docstring.ref_tags.each do |ref| - # @sg-ignore ref should actually be an intersection type + # @todo ref should actually be an intersection type next unless ref.tag_name == 'return' && ref.owner - # @sg-ignore ref should actually be an intersection type + # @todo ref should actually be an intersection type result = resolve_reference(ref.owner.to_s, api_map) return result unless result.nil? end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index dc1f15440..6051f77af 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -269,7 +269,7 @@ def see_reference heredoc, api_map, skip = [] heredoc.ref_tags.each do |ref| # @sg-ignore ref should actually be an intersection type next unless ref.tag_name == 'param' && ref.owner - # @sg-ignore ref should actually be an intersection type + # @todo ref should actually be an intersection type result = resolve_reference(ref.owner.to_s, api_map, skip) return result unless result.nil? end diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 9bd966bda..a8ba388ac 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -738,7 +738,9 @@ def type_tag(type_name, type_args = []) build_type(type_name, type_args).tags end - # @param type [RBS::Types::Bases::Base] + # @param type [RBS::Types::Bases::Base,Object] RBS type object. + # Note: Generally these extend from RBS::Types::Bases::Base, + # but not all. # @return [String] def other_type_to_tag type if type.is_a?(RBS::Types::Optional) @@ -795,6 +797,7 @@ def other_type_to_tag type # e.g., singleton(String) type_tag(type.name) else + # all types should include location Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" 'undefined' end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 1d20f2547..b17e7bf85 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -789,13 +789,10 @@ def fake_args_for(pin) with_block = false # @param pin [Pin::Parameter] pin.parameters.each do |pin| - # @sg-ignore Should handle redefinition of types in simple contexts if [:kwarg, :kwoptarg, :kwrestarg].include?(pin.decl) with_opts = true - # @sg-ignore Should handle redefinition of types in simple contexts elsif pin.decl == :block with_block = true - # @sg-ignore Should handle redefinition of types in simple contexts elsif pin.decl == :restarg args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)], nil, true) else From 8701af7039361113b6f48ab0b9ffb1a86b1fa63c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 29 Nov 2025 15:11:58 -0500 Subject: [PATCH 722/930] Handle an issue brought out by future merge --- lib/solargraph/pin/parameter.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index cd0de8c57..ae86e262e 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -38,6 +38,16 @@ def combine_with(other, attrs={}) super(other, new_attrs) end + def combine_return_type(other) + out = super + if out.undefined? + # allow our return_type method to provide a better type + # using :param tag + out = nil + end + out + end + def keyword? [:kwarg, :kwoptarg].include?(decl) end From 08024bde06ab1a48ac6a98ff33745d933459daab Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 29 Nov 2025 15:58:47 -0500 Subject: [PATCH 723/930] Fixes based on future branch type checking --- lib/solargraph/complex_type/unique_type.rb | 7 +++++++ lib/solargraph/pin/parameter.rb | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index dc386cd24..41138207c 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -385,6 +385,13 @@ def map &block [block.yield(self)] end + # @yieldparam t [self] + # @yieldreturn [self] + # @return [Enumerable] + def each &block + [self].each &block + end + # @return [Array] def to_a [self] diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index ae86e262e..51286f855 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -40,7 +40,7 @@ def combine_with(other, attrs={}) def combine_return_type(other) out = super - if out.undefined? + if out&.undefined? # allow our return_type method to provide a better type # using :param tag out = nil From bf4a799b69a6666e13fb347457e5c52611a72916 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 30 Nov 2025 16:08:25 -0500 Subject: [PATCH 724/930] Adjust accounting --- lib/solargraph/type_checker/rules.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 44c9da709..1166eba2c 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -74,8 +74,7 @@ def require_inferred_type_params? # # @todo 35: flow sensitive typing needs to handle attrs # @todo 19: flow sensitive typing needs to narrow down type with an if is_a? check - # @todo 14: flow sensitive typing needs to handle ivars - # Should handle redefinition of types in simple contexts + # @todo 12: Should handle redefinition of types in simple contexts # @todo 6: need boolish support for ? methods # @todo 5: literal arrays in this module turn into ::Solargraph::Source::Chain::Array # @todo 4: flow sensitive typing needs better handling of ||= on lvars From 6b9bce8e58ce3d17e9e1f23f90aa81914ec85d3f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Dec 2025 12:22:31 -0500 Subject: [PATCH 725/930] Annotation fixes --- lib/solargraph/api_map.rb | 8 ++-- lib/solargraph/doc_map.rb | 6 ++- lib/solargraph/pin_cache.rb | 68 +++++++++++++++--------------- lib/solargraph/rbs_map.rb | 2 +- lib/solargraph/rbs_map/core_map.rb | 2 +- lib/solargraph/shell.rb | 1 + lib/solargraph/workspace.rb | 4 +- lib/solargraph/yardoc.rb | 4 +- 8 files changed, 49 insertions(+), 46 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 695de0230..f99de7ffa 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -30,7 +30,7 @@ def initialize pins: [] index pins end - # @param out [IO, nil] output stream for logging + # @param out [StringIO, IO, nil] output stream for logging # @return [void] def self.reset_core out: nil @@core_map = RbsMap::CoreMap.new @@ -136,7 +136,7 @@ def core_pins @@core_map.pins end - # @param name [String] + # @param name [String, nil] # @return [YARD::Tags::MacroDirective, nil] def named_macro name store.named_macros[name] @@ -186,7 +186,7 @@ def self.load directory api_map end - # @param out [IO, nil] + # @param out [StringIO, IO, nil] # @return [void] def cache_all_for_doc_map! out @doc_map.cache_doc_map_gems!(out) @@ -209,7 +209,7 @@ class << self # # # @param directory [String] - # @param out [IO] The output stream for messages + # @param out [IO, StringIO, nil] The output stream for messages # # @return [ApiMap] def self.load_with_cache directory, out = $stderr diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index cc3a4aed6..a82d1eebd 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -36,6 +36,7 @@ def requires end alias required requires + # @sg-ignore Translate to something flow sensitive typing understands # @return [Array] def uncached_gemspecs if @uncached_gemspecs.nil? @@ -66,7 +67,7 @@ def any_uncached? end # Cache all pins needed for the sources in this doc_map - # @param out [IO, nil] output stream for logging + # @param out [StringIO, IO, nil] output stream for logging # @return [void] def cache_doc_map_gems! out unless uncached_gemspecs.empty? @@ -109,7 +110,7 @@ def dependencies out: $stderr # # @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 + # @param out [StringIO, IO, nil] output stream for logging # # @return [void] def cache gemspec, rebuild: false, out: nil @@ -235,6 +236,7 @@ def change_gemspec_version gemspec, version # @return [Array] def fetch_dependencies gemspec, out: nil # @param spec [Gem::Dependency] + # @param deps [Set] 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] diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index fb3b8d7a4..97f5f5d6a 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -29,7 +29,7 @@ def cached? gemspec # @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 + # @param out [StringIO, 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) @@ -50,7 +50,7 @@ def cache_gem gemspec:, rebuild: false, out: nil end # @param gemspec [Gem::Specification, Bundler::LazySpecification] - # @param rbs_version_cache_key [String] + # @param rbs_version_cache_key [String, nil] 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 @@ -59,7 +59,7 @@ def suppress_yard_cache? gemspec, rbs_version_cache_key false end - # @param out [IO, nil] output stream for logging + # @param out [StringIO, IO, nil] output stream for logging # # @return [void] def cache_all_stdlibs out: $stderr @@ -93,8 +93,8 @@ def lookup_rbs_version_cache_key gemspec rbs_map.cache_key end - # @param gemspec [Gem::Specification] - # @param rbs_version_cache_key [String] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param rbs_version_cache_key [String, nil] # @param yard_pins [Array] # @param rbs_collection_pins [Array] # @return [void] @@ -103,16 +103,16 @@ def cache_combined_pins gemspec, rbs_version_cache_key, yard_pins, rbs_collectio serialize_combined_gem(gemspec, rbs_version_cache_key, combined_pins) end - # @param gemspec [Gem::Specification] - # @return [Array] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @return [Array, nil] 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] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param out [StringIO, IO, nil] # @return [void] def uncache_gem gemspec, out: nil PinCache.uncache(yardoc_path(gemspec), out: out) @@ -172,7 +172,7 @@ def calculate_build_needs gemspec, rebuild:, rbs_version_cache_key: # @param build_yard [Boolean] # @param build_rbs_collection [Boolean] # @param build_combined [Boolean] - # @param out [IO, nil] + # @param out [StringIO, IO, nil] # # @return [void] def build_combine_and_cache gemspec, @@ -199,7 +199,7 @@ def build_combine_and_cache gemspec, # @param build_yard [Boolean] # @param build_rbs_collection [Boolean] # @param build_combined [Boolean] - # @param out [IO, nil] + # @param out [StringIO, IO, nil] # # @return [void] def log_cache_info gemspec, @@ -223,7 +223,7 @@ def log_cache_info gemspec, end # @param gemspec [Gem::Specification, Bundler::LazySpecification] - # @param out [IO, nil] + # @param out [StringIO, IO, nil] # @return [Array] def cache_yard_pins gemspec, out gem_yardoc_path = yardoc_path(gemspec) @@ -239,8 +239,8 @@ def combined_pins_in_memory PinCache.all_combined_pins_in_memory[yard_plugins] ||= {} end - # @param gemspec [Gem::Specification] - # @param _out [IO, nil] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param _out [StringIO, IO, nil] # @return [Array] def cache_rbs_collection_pins gemspec, _out rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path) @@ -258,8 +258,8 @@ def cache_rbs_collection_pins gemspec, _out pins end - # @param gemspec [Gem::Specification] - # @return [Array] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @return [Array, nil] def deserialize_yard_pin_cache gemspec cached = load_yard_gem(gemspec) if cached @@ -271,7 +271,7 @@ def deserialize_yard_pin_cache gemspec end # @param gemspec [Gem::Specification, Bundler::LazySpecification] - # @param rbs_version_cache_key [String] + # @param rbs_version_cache_key [String, nil] # @return [Array] def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key cached = load_rbs_collection_pins(gemspec, rbs_version_cache_key) @@ -288,7 +288,7 @@ def yard_path_components yard_plugins.sort.uniq.join('-')] end - # @param gemspec [Gem::Specification] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @return [String] def yardoc_path gemspec File.join(PinCache.base_dir, @@ -296,39 +296,39 @@ def yardoc_path gemspec "#{gemspec.name}-#{gemspec.version}.yardoc") end - # @param gemspec [Gem::Specification] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @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] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @return [Array, nil] def load_yard_gem gemspec PinCache.load(yard_gem_path(gemspec)) end - # @param gemspec [Gem::Specification] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @param pins [Array] # @return [void] def serialize_yard_gem gemspec, pins PinCache.save(yard_gem_path(gemspec), pins) end - # @param gemspec [Gem::Specification] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @return [Boolean] def yard_gem? gemspec exist?(yard_gem_path(gemspec)) end - # @param gemspec [Gem::Specification] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @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] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @return [String] def rbs_collection_pins_path_prefix gemspec File.join(PinCache.work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-") @@ -342,7 +342,7 @@ def load_rbs_collection_pins gemspec, hash PinCache.load(rbs_collection_pins_path(gemspec, hash)) end - # @param gemspec [Gem::Specification] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @param hash [String, nil] # @param pins [Array] # @return [void] @@ -350,20 +350,20 @@ def serialize_rbs_collection_pins gemspec, hash, pins PinCache.save(rbs_collection_pins_path(gemspec, hash), pins) end - # @param gemspec [Gem::Specification] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @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] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @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 gemspec [Gem::Specification, Bundler::LazySpecification] # @param hash [String, nil] # @param pins [Array] # @return [void] @@ -377,7 +377,7 @@ def combined_gem? gemspec, hash exist?(combined_path(gemspec, hash)) end - # @param gemspec [Gem::Specification] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] # @param hash [String, nil] # @return [Array, nil] def load_combined_gem gemspec, hash @@ -388,8 +388,8 @@ def load_combined_gem gemspec, hash loaded end - # @param gemspec [Gem::Specification] - # @param hash [String] + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param hash [String, nil] def rbs_collection_pins? gemspec, hash exist?(rbs_collection_pins_path(gemspec, hash)) end @@ -447,7 +447,7 @@ def uncache *path_segments, out: nil end end - # @param out [IO, nil] + # @param out [StringIO, IO, nil] # @return [void] def uncache_core out: nil uncache(core_path, out: out) @@ -455,7 +455,7 @@ def uncache_core out: nil ApiMap.reset_core(out: out) end - # @param out [IO, nil] + # @param out [StringIO, IO, nil] # @return [void] def uncache_stdlib out: nil uncache(stdlib_path, out: out) @@ -629,7 +629,7 @@ def core? File.file?(core_path) end - # @param out [IO, nil] + # @param out [StringIO, IO, nil] # @return [Array] def cache_core out: $stderr RbsMap::CoreMap.new.cache_core(out: out) diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index f6309bb55..a5b998ff0 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -43,7 +43,7 @@ def initialize library, version = nil, rbs_collection_config_path: nil, rbs_coll CACHE_KEY_STDLIB = 'stdlib' CACHE_KEY_LOCAL = 'local' - # @param cache_key [String] + # @param cache_key [String, nil] # @return [String, nil] a description of the source of the RBS info def self.rbs_source_desc cache_key case cache_key diff --git a/lib/solargraph/rbs_map/core_map.rb b/lib/solargraph/rbs_map/core_map.rb index 275dbbcf4..8fe2afc83 100644 --- a/lib/solargraph/rbs_map/core_map.rb +++ b/lib/solargraph/rbs_map/core_map.rb @@ -22,7 +22,7 @@ def pins out: $stderr @pins = cache_core(out: out) end - # @param out [IO, nil] output stream for logging + # @param out [StringIO, IO, nil] output stream for logging # @return [Array] def cache_core out: $stderr new_pins = [] diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 735e6c290..2a45a67ec 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -80,6 +80,7 @@ def config(directory = '.') conf['extensions'].push m end end + # @param file [File] File.open(File.join(directory, '.solargraph.yml'), 'w') do |file| file.puts conf.to_yaml end diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 2697e90e0..72a802623 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -70,7 +70,7 @@ def global_environ end # @param gemspec [Gem::Specification, Bundler::LazySpecification] - # @param out [IO, nil] output stream for logging + # @param out [StringIO, IO, nil] output stream for logging # @param rebuild [Boolean] whether to rebuild the pins even if they are cached # # @return [void] @@ -79,7 +79,7 @@ def cache_gem gemspec, out: nil, rebuild: false end # @param gemspec [Gem::Specification, Bundler::LazySpecification] - # @param out [IO, nil] output stream for logging + # @param out [StringIO, IO, nil] output stream for logging # # @return [void] def uncache_gem gemspec, out: nil diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 50f212f13..a7f5d2ab8 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -35,8 +35,8 @@ def build_docs gem_yardoc_path, yard_plugins, gemspec 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 + # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param out [StringIO, IO, nil] where to log messages # @return [Array] def build_pins gem_yardoc_path, gemspec, out: $stderr yardoc = load!(gem_yardoc_path) From fca10824817356a24245ae8112716b9c2e9ececb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Dec 2025 12:27:33 -0500 Subject: [PATCH 726/930] Remove @sg-ignore --- lib/solargraph/doc_map.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index a82d1eebd..3e9e64212 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -36,7 +36,6 @@ def requires end alias required requires - # @sg-ignore Translate to something flow sensitive typing understands # @return [Array] def uncached_gemspecs if @uncached_gemspecs.nil? From 76c6d7f6d9a1b6e585b8437e996c03d9380d45ba Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Dec 2025 15:45:23 -0500 Subject: [PATCH 727/930] Add fix that this PR revealed on future branch --- lib/solargraph/pin/callable.rb | 5 +++++ lib/solargraph/pin/parameter.rb | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 207c2619b..93a69687b 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -21,6 +21,11 @@ def initialize block: nil, return_type: nil, parameters: [], **splat @parameters = parameters end + def reset_generated! + parameters.each(&:reset_generated!) + super + end + # @return [String] def method_namespace closure.namespace diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 947513689..ae7cc5416 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -135,6 +135,11 @@ def full end end + def reset_generated! + @return_type = nil + super + end + # @return [ComplexType] def return_type if @return_type.nil? From 892630cf68a19058297f2d2e8ea1d9d66e7cb464 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Dec 2025 16:03:58 -0500 Subject: [PATCH 728/930] Add fix that this PR revealed on future branch --- lib/solargraph/pin/parameter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index ae7cc5416..9d7ab1c89 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -136,7 +136,7 @@ def full end def reset_generated! - @return_type = nil + @return_type = nil if param_tag super end From f879eb2c8c15e4e29083fb21995bcb1cc9d0fbc2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Dec 2025 16:19:16 -0500 Subject: [PATCH 729/930] Add @sg-ignore --- lib/solargraph/yardoc.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 09bcd4586..0afdf1482 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -23,6 +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 + # @sg-ignore Unrecognized keyword argument chdir to Open3.capture2e 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}" } From 5600ee1ff7275cd0f624a904392d53bc40076248 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Dec 2025 16:30:42 -0500 Subject: [PATCH 730/930] Remove @sg-ignores --- lib/solargraph/shell.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 046a74296..3b1c4952d 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -241,24 +241,17 @@ def list puts "#{workspace.filenames.length} files total." end - # @sg-ignore Unresolved call to desc desc 'pin [PATH]', 'Describe a pin', hide: true - # @sg-ignore Unresolved call to option option :rbs, type: :boolean, desc: 'Output the pin as RBS', default: false - # @sg-ignore Unresolved call to option option :typify, type: :boolean, desc: 'Output the calculated return type of the pin from annotations', default: false - # @sg-ignore Unresolved call to option option :references, type: :boolean, desc: 'Show references', default: false - # @sg-ignore Unresolved call to option option :probe, type: :boolean, desc: 'Output the calculated return type of the pin from annotations and inference', default: false - # @sg-ignore Unresolved call to option option :stack, type: :boolean, desc: 'Show entire stack of a method pin by including definitions in superclasses', default: false # @param path [String] The path to the method pin, e.g. 'Class#method' or 'Class.method' # @return [void] def pin path api_map = Solargraph::ApiMap.load_with_cache('.', $stderr) is_method = path.include?('#') || path.include?('.') - # @sg-ignore Unresolved call to options if is_method && options[:stack] scope, ns, meth = if path.include? '#' [:instance, *path.split('#', 2)] @@ -280,7 +273,6 @@ def pin path $stderr.puts "Pin not found for path '#{path}'" exit 1 when Pin::Namespace - # @sg-ignore Unresolved call to options if options[:references] superclass_tag = api_map.qualify_superclass(pin.return_type.tag) superclass_pin = api_map.get_path_pins(superclass_tag).first if superclass_tag @@ -289,12 +281,9 @@ def pin path 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 @@ -338,7 +327,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 @@ -349,7 +337,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 939786ed30bce6718895f5cc1b5cfcb395fb4ac7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Dec 2025 16:38:44 -0500 Subject: [PATCH 731/930] Remove @sg-ignores --- lib/solargraph/yardoc.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 0afdf1482..09bcd4586 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -23,7 +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 Unrecognized keyword argument chdir to Open3.capture2e 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}" } From d87f222640e484df7c67092d804b3a7b96210697 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Dec 2025 16:47:40 -0500 Subject: [PATCH 732/930] Use consistent Ruby versions for typechecking --- .github/workflows/plugins.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index b5984f3cb..d1d6b9be6 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -23,7 +23,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: 3.4 bundler-cache: true - uses: awalsh128/cache-apt-pkgs-action@latest with: @@ -54,7 +54,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: 3.4 bundler-cache: false - uses: awalsh128/cache-apt-pkgs-action@latest with: @@ -83,7 +83,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: 3.4 bundler-cache: false - uses: awalsh128/cache-apt-pkgs-action@latest with: From aa4000614189c0c61c641de22e413632517431fe Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 1 Dec 2025 20:09:38 -0500 Subject: [PATCH 733/930] Bring in fixes for strong typechecking issues --- lib/solargraph/pin/parameter.rb | 2 +- lib/solargraph/yard_map/mapper.rb | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 9d7ab1c89..7daa34d32 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -205,7 +205,7 @@ def param_tag # @return [ComplexType] def typify_block_param api_map block_pin = closure - if block_pin.is_a?(Pin::Block) && block_pin.receiver + if block_pin.is_a?(Pin::Block) && block_pin.receiver && index return block_pin.typify_parameters(api_map)[index] end ComplexType::UNDEFINED diff --git a/lib/solargraph/yard_map/mapper.rb b/lib/solargraph/yard_map/mapper.rb index 592b3805e..d9cf90a5f 100644 --- a/lib/solargraph/yard_map/mapper.rb +++ b/lib/solargraph/yard_map/mapper.rb @@ -39,10 +39,11 @@ def generate_pins code_object @namespace_pins[code_object.path] = nspin result.push nspin if code_object.is_a?(YARD::CodeObjects::ClassObject) and !code_object.superclass.nil? - # This method of superclass detection is a bit of a hack. If - # the superclass is a Proxy, it is assumed to be undefined in its - # yardoc and converted to a fully qualified namespace. - superclass = if code_object.superclass.is_a?(YARD::CodeObjects::Proxy) + # This method of superclass detection is a bit of a + # hack. If the superclass is a Proxy that can't be + # resolved', it is assumed to be undefined in its yardoc + # and converted to a fully qualified namespace. + superclass = if code_object.superclass.is_a?(YARD::CodeObjects::Proxy) && code_object.type == :proxy "::#{code_object.superclass}" else code_object.superclass.to_s From 6c08617106f235ae4bc957b105c9c358caeaac50 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Dec 2025 21:49:08 -0500 Subject: [PATCH 734/930] Fix merge --- lib/solargraph/diagnostics/rubocop_helpers.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/diagnostics/rubocop_helpers.rb b/lib/solargraph/diagnostics/rubocop_helpers.rb index 5f738bad9..fc458956e 100644 --- a/lib/solargraph/diagnostics/rubocop_helpers.rb +++ b/lib/solargraph/diagnostics/rubocop_helpers.rb @@ -24,7 +24,6 @@ def require_rubocop(version = nil) specs = e.specs raise InvalidRubocopVersionError, "could not find '#{e.name}' (#{e.requirement}) - "\ - \ # @sg-ignore Unresolved call to version on String "did find: [#{specs.map { |s| s.version.version }.join(', ')}]" end require 'rubocop' From bef2fb77e79e2a587effaf4e69a22bb83b359c86 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Dec 2025 22:10:19 -0500 Subject: [PATCH 735/930] Typechecking fix --- lib/solargraph/range.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/range.rb b/lib/solargraph/range.rb index 25bd43387..25fc8b661 100644 --- a/lib/solargraph/range.rb +++ b/lib/solargraph/range.rb @@ -81,7 +81,7 @@ def self.from_to l1, c1, l2, c2 # Get a range from a node. # - # @param node [AST::Node] + # @param node [::Parser::AST::Node] # @return [Range, nil] def self.from_node node if node&.loc && node.loc.expression From 2cb58999c5d045996d86d379e700205801a074ce Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Dec 2025 22:19:07 -0500 Subject: [PATCH 736/930] Fix RuboCop issue --- .rubocop_todo.yml | 1 - spec/convention/activesupport_concern_spec.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 535712315..02a74780c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1156,7 +1156,6 @@ Style/StringLiterals: # This cop supports safe autocorrection (--autocorrect). Style/SuperArguments: Exclude: - - 'lib/solargraph/pin/base_variable.rb' - 'lib/solargraph/pin/callable.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/signature.rb' diff --git a/spec/convention/activesupport_concern_spec.rb b/spec/convention/activesupport_concern_spec.rb index b58cd6584..a0eae979a 100644 --- a/spec/convention/activesupport_concern_spec.rb +++ b/spec/convention/activesupport_concern_spec.rb @@ -149,7 +149,7 @@ class Base RBS end - it { should_not be_empty } + it { is_expected.not_to be_empty } it "has one item" do expect(method_pins.size).to eq(1) From adc11c6c01c106820825ddbfb4fdc1620891ceef Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Dec 2025 22:41:13 -0500 Subject: [PATCH 737/930] Fix typechecking error --- lib/solargraph/shell.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 69a246175..471f59f6f 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -173,7 +173,6 @@ def reporters # @return [void] def typecheck *files directory = File.realpath(options[:directory]) - # @sg-ignore Unresolved call to options level = options[:level].to_sym rules = Solargraph::TypeChecker::Rules.new(level) api_map = @@ -189,7 +188,6 @@ def typecheck *files filecount = 0 time = Benchmark.measure { files.each do |file| - # @sg-ignore Unresolved call to options checker = TypeChecker.new(file, api_map: api_map, rules: rules, level: options[:level].to_sym) problems = checker.problems next if problems.empty? From e7a3595039157111d7f332ae0346fa34011cf869 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Dec 2025 23:13:30 -0500 Subject: [PATCH 738/930] Merge remote-tracking branch 'origin/master' into pin_cache_refactor --- spec/shell_spec.rb | 99 ---------------------------------------------- 1 file changed, 99 deletions(-) diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index 4c91b879e..607a369f8 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -186,105 +186,6 @@ def bundle_exec(*cmd) 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 - # @type cmd [Array] # @return [String] def bundle_exec(*cmd) From 95b76c3d87eb09961445be4a16f21e170bff4884 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Dec 2025 23:24:32 -0500 Subject: [PATCH 739/930] Fix merge --- spec/doc_map_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index abb7f1c26..4113529f5 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -75,7 +75,6 @@ def global(doc_map) Solargraph::Environ.new( requires: ['convention_gem1', 'convention_gem2'] ) ->>>>>>> origin/master end end From e935c972a316bde1c7ec71e66500d00228789535 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Dec 2025 23:26:47 -0500 Subject: [PATCH 740/930] Fix RuboCop --- spec/doc_map_spec.rb | 15 --------------- spec/yardoc_spec.rb | 3 --- 2 files changed, 18 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 4113529f5..6ab97ef97 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -33,21 +33,6 @@ end 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 - - expect(doc_map_with_bundler_require.pins.length - plain_doc_map.pins.length).to be_positive - 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. diff --git a/spec/yardoc_spec.rb b/spec/yardoc_spec.rb index 02444254a..6cd575de0 100644 --- a/spec/yardoc_spec.rb +++ b/spec/yardoc_spec.rb @@ -40,9 +40,6 @@ describe '#build_docs' do let(:workspace) { Solargraph::Workspace.new(Dir.pwd) } let(:gemspec) { workspace.find_gem('rubocop') } - 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 From f5c63aa01ac15b16dbd3107887e304a0b148dbe9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Dec 2025 23:36:14 -0500 Subject: [PATCH 741/930] Fix strong typechecking issues --- lib/solargraph/library.rb | 2 +- lib/solargraph/parser/parser_gem/class_methods.rb | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 95b4337f7..7de06bacd 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 => Array}] def source_map_external_require_hash @source_map_external_require_hash ||= {} end diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index a512cdb05..2daf22fc7 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -23,9 +23,6 @@ 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 From 95d0280c1480db02d558681c019f165650a1ef0f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 30 Dec 2025 23:52:34 -0500 Subject: [PATCH 742/930] Fix merge --- 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 38b46da30..f832cf57f 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -67,13 +67,16 @@ def resolve_require require # @return [Array] def fetch_dependencies gemspec, out: $stderr # @param spec [Gem::Dependency] + # @param deps [Set] 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? + # @sg-ignore Unresolved call to requirement on Gem::Dependency dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement) deps.merge fetch_dependencies(dep) if deps.add?(dep) rescue Gem::MissingSpecError + # @sg-ignore Unresolved call to requirement on Gem::Dependency Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for " \ "#{gemspec.name} not found in RubyGems." end.to_a From 5c42eb74977e63474025c18573960c528a8423a3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 08:37:40 -0500 Subject: [PATCH 743/930] Drop some sg-ignores --- lib/solargraph/parser/parser_gem/node_methods.rb | 1 - lib/solargraph/pin/method.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index b77c4cd47..02f790c00 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 Wrong argument type for AST::Node.new: type expected AST::_ToSym, received :nil NIL_NODE = ::Parser::AST::Node.new(:nil) # @param node [Parser::AST::Node] diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 86bf1cd09..e6e0f6503 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -92,7 +92,6 @@ def combine_with(other, attrs = {}) end new_attrs = { visibility: combine_visibility(other), - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 explicit: explicit? || other.explicit?, block: combine_blocks(other), node: choose_node(other, :node), From 0090a80f13098015ea8158ec3ec83e43bec565cf Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 08:42:44 -0500 Subject: [PATCH 744/930] 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 355b9ee18..065a96582 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 0bfd89fea5ebd5eb29196fdee527ecbe4cf3ca35 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 08:47:08 -0500 Subject: [PATCH 745/930] Drop sg-ignores --- lib/solargraph/pin/base_variable.rb | 2 -- lib/solargraph/pin/local_variable.rb | 2 -- 2 files changed, 4 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 9514aee8d..1d5f85c0e 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -28,8 +28,6 @@ def combine_with(other, attrs={}) # tells you if the arg is optional or not. Prefer a # provided value if we have one here since we can't rely on # it from RBS so we can infer from it and typecheck on it. - # - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 assignment: choose(other, :assignment), mass_assignment: assert_same(other, :mass_assignment), return_type: combine_return_type(other), diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index d78329521..9673d425d 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -21,12 +21,10 @@ def initialize presence: nil, presence_certain: false, **splat def combine_with(other, attrs={}) new_attrs = { - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 presence_certain: assert_same(other, :presence_certain?), }.merge(attrs) new_attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence) - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 super(other, new_attrs) end From c59316ed38a58d4bdd4abb04dee663a4a2427f12 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 08:51:40 -0500 Subject: [PATCH 746/930] Add @sg-ignore --- lib/solargraph/yardoc.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 907afb2de..9d857a0a6 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -32,6 +32,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 + # @sg-ignore Unrecognized keyword argument chdir to Open3.capture2e 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}" } From fb4f6a9855b38aa7b656a61f1f9e1aa04ccd0e43 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 09:43:52 -0500 Subject: [PATCH 747/930] Fix merge --- .github/workflows/rspec.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 20e064585..4a3388ce4 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -29,16 +29,10 @@ jobs: rbs-version: '3.9.5' - ruby-version: '3.0' rbs-version: '4.0.0.dev.4' - # Missing require in 'rbs collection update' - hopefully - # fixed in next RBS release - - ruby-version: 'head' - rbs-version: '4.0.0.dev.4' - - ruby-version: 'head' - rbs-version: '3.9.4' - - ruby-version: 'head' - rbs-version: '3.6.1' - ruby-version: '4.0' rbs-version: '3.6.1' + # Missing require in 'rbs collection update' - hopefully + # fixed in next RBS release - ruby-version: '4.0' rbs-version: '4.0.0.dev.4' steps: From dadf38fed46e0cb4a3d516feb2d9250af2247eb0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 09:50:17 -0500 Subject: [PATCH 748/930] Adjust annotations --- lib/solargraph/pin/local_variable.rb | 6 +++--- lib/solargraph/pin/method.rb | 2 +- lib/solargraph/pin/parameter.rb | 2 +- lib/solargraph/rbs_map/conversions.rb | 3 +++ 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 51e16a094..8d28be2f6 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -39,12 +39,12 @@ def combine_with(other, attrs={}) # @param other_closure [Pin::Closure] # @param other_loc [Location] - # @sg-ignore Need to add nil check here + # @todo Need to add nil check here def visible_at?(other_closure, other_loc) - # @sg-ignore Need to add nil check here + # @todo Need to add nil check here location.filename == other_loc.filename && presence&.include?(other_loc.range.start) && - # @sg-ignore Need to add nil check here + # @todo Need to add nil check here match_named_closure(other_closure, closure) end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index e6e0f6503..cb408411b 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -538,7 +538,7 @@ def see_reference api_map docstring.ref_tags.each do |ref| # @sg-ignore ref should actually be an intersection type next unless ref.tag_name == 'return' && ref.owner - # @sg-ignore ref should actually be an intersection type + # @todo ref should actually be an intersection type result = resolve_reference(ref.owner.to_s, api_map) return result unless result.nil? end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 0e0b999cd..23ce865ad 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -258,7 +258,7 @@ def see_reference heredoc, api_map, skip = [] heredoc.ref_tags.each do |ref| # @sg-ignore ref should actually be an intersection type next unless ref.tag_name == 'param' && ref.owner - # @sg-ignore ref should actually be an intersection type + # @todo ref should actually be an intersection type result = resolve_reference(ref.owner.to_s, api_map, skip) return result unless result.nil? end diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index c07159ef8..10263a953 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -794,6 +794,9 @@ def other_type_to_tag type # e.g., singleton(String) type_tag(type.name) else + # RBS doesn't provide a common base class for its type AST nodes' + # + # @sg-ignore Unresolved call to location on Object Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" 'undefined' end From 086044739b32529657317726ff93601820b82222 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 10:25:01 -0500 Subject: [PATCH 749/930] Fix typecheck errors --- lib/solargraph/api_map/store.rb | 3 +++ lib/solargraph/complex_type.rb | 1 + lib/solargraph/library.rb | 2 ++ lib/solargraph/parser/parser_gem/node_methods.rb | 1 - lib/solargraph/pin/method.rb | 1 - lib/solargraph/rbs_map/conversions.rb | 1 - lib/solargraph/shell.rb | 1 - lib/solargraph/source/chain/call.rb | 7 +++++++ lib/solargraph/type_checker.rb | 5 ++++- lib/solargraph/workspace/require_paths.rb | 1 + 10 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index bc829ba5a..619a2fc32 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -244,8 +244,11 @@ def get_ancestors(fqns) next if refs.nil? # @param ref [String] refs.map(&:type).map(&:to_s).each do |ref| + # @sg-ignore Flow-sensitive typing should be able to handle redefinition next if ref.nil? || ref.empty? || visited.include?(ref) + # @sg-ignore Flow-sensitive typing should be able to handle redefinition ancestors << ref + # @sg-ignore Flow-sensitive typing should be able to handle redefinition queue << ref end end diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 1b9f96dfa..a20652baf 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -21,6 +21,7 @@ def initialize types = [UniqueType::UNDEFINED] items.delete_if { |i| i.name == 'false' || i.name == 'true' } items.unshift(ComplexType::BOOLEAN) end + # @type [Array] items = [UniqueType::UNDEFINED] if items.any?(&:undefined?) @items = items end diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 5c7851201..b51e3cc2b 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -259,11 +259,13 @@ def references_from filename, line, column, strip: false, only: false referenced&.path == pin.path end if pin.path == 'Class#new' + # @todo Flow-sensitive typing should allow shadowing of Kernel#caller caller = cursor.chain.base.infer(api_map, clip.send(:closure), clip.locals).first if caller.defined? found.select! do |loc| clip = api_map.clip_at(loc.filename, loc.range.start) other = clip.send(:cursor).chain.base.infer(api_map, clip.send(:closure), clip.locals).first + # @todo Flow-sensitive typing should allow shadowing of Kernel#caller caller == other end else diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index b77c4cd47..02f790c00 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 Wrong argument type for AST::Node.new: type expected AST::_ToSym, received :nil NIL_NODE = ::Parser::AST::Node.new(:nil) # @param node [Parser::AST::Node] diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 13e9cb77a..302702ad8 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -94,7 +94,6 @@ def combine_with(other, attrs = {}) end new_attrs = { visibility: combine_visibility(other), - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 explicit: explicit? || other.explicit?, block: combine_blocks(other), node: choose_node(other, :node), diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 54bca0f73..c26b9e264 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -352,7 +352,6 @@ def global_decl_to_pin decl # @param context [Context] # @param scope [Symbol] :instance or :class # @param name [String] The name of the method - # @sg-ignore # @return [Symbol] def calculate_method_visibility(decl, context, closure, scope, name) override_key = [closure.path, scope, name] diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 4e3300336..8c4b3a02c 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -279,7 +279,6 @@ def pin path $stderr.puts "Pin not found for path '#{path}'" exit 1 when Pin::Namespace - # @sg-ignore Unresolved call to options if options[:references] superclass_tag = api_map.qualify_superclass(pin.return_type.tag) superclass_pin = api_map.get_path_pins(superclass_tag).first if superclass_tag diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index d48a93549..88bb3c1b0 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -71,6 +71,7 @@ def resolve api_map, name_pin, locals def inferred_pins pins, api_map, name_pin, locals result = pins.map do |p| next p unless p.is_a?(Pin::Method) + # @sg-ignore Flow-sensitive typing should handle is_a? and next overloads = p.signatures # next p if overloads.empty? type = ComplexType::UNDEFINED @@ -79,10 +80,15 @@ def inferred_pins pins, api_map, name_pin, locals # use it. If we didn't pass a block, the logic below will # reject it regardless + # @sg-ignore Flow-sensitive typing should handle is_a? and next with_block, without_block = overloads.partition(&:block?) + # @sg-ignore Flow-sensitive typing should handle is_a? and next + # @type Array sorted_overloads = with_block + without_block # @type [Pin::Signature, nil] new_signature_pin = nil + # @sg-ignore Flow-sensitive typing should handle is_a? and next + # @param ol [Pin::Signature] sorted_overloads.each do |ol| next unless ol.arity_matches?(arguments, with_block?) match = true @@ -141,6 +147,7 @@ def inferred_pins pins, api_map, name_pin, locals end break if type.defined? end + # @sg-ignore Flow-sensitive typing should handle is_a? and next p = p.with_single_signature(new_signature_pin) unless new_signature_pin.nil? next p.proxy(type) if type.defined? if !p.macros.empty? diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index d49de7384..cb645a831 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -463,7 +463,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? + # @todo Unresolved call to defined? 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 @@ -741,10 +741,13 @@ def fake_args_for(pin) with_opts = false with_block = false pin.parameters.each do |pin| + # @sg-ignore Flow-sensitive typing should be able to handle redefinition if [:kwarg, :kwoptarg, :kwrestarg].include?(pin.decl) with_opts = true + # @sg-ignore Flow-sensitive typing should be able to handle redefinition elsif pin.decl == :block with_block = true + # @sg-ignore Flow-sensitive typing should be able to handle redefinition elsif pin.decl == :restarg args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)], nil, true) else diff --git a/lib/solargraph/workspace/require_paths.rb b/lib/solargraph/workspace/require_paths.rb index c8eea161b..9c0424a88 100644 --- a/lib/solargraph/workspace/require_paths.rb +++ b/lib/solargraph/workspace/require_paths.rb @@ -83,6 +83,7 @@ def require_path_from_gemspec_file gemspec_file_path return [] if hash.empty? hash['paths'].map { |path| File.join(base, path) } rescue StandardError => e + # @sg-ignore Flow-sensitive typing should allow redefinition of 'e' Solargraph.logger.warn "Error reading #{gemspec_file_path}: [#{e.class}] #{e.message}" [] end From 8d1a96ad1cd8f76f1f001a11864522054bbae565 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 10:29:00 -0500 Subject: [PATCH 750/930] Fix merge --- spec/type_checker/levels/strong_spec.rb | 37 ------------------------- 1 file changed, 37 deletions(-) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index c6c335294..95648e190 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -661,43 +661,6 @@ def run_command expect(checker.problems.map(&:message)).to be_empty end - 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 - - 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 - - it 'resolves class name correctly in generic resolution' do - expect(checker.problems.map(&:message)).to be_empty - end - end - it 'handles "while foo" flow sensitive typing correctly' do checker = type_checker(%( # @param a [String, nil] From 88591aa2e5824bf67b49041ad95bc7425dc6cc69 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 10:41:53 -0500 Subject: [PATCH 751/930] 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 cc3031ea5..67e953d27 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -512,7 +512,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 4663e7d796724f99bac82f9b18b6ba22d37e9455 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 11:00:44 -0500 Subject: [PATCH 752/930] Fix some @sg-ignores --- lib/solargraph/pin/parameter.rb | 1 + lib/solargraph/workspace/gemspecs.rb | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 018926563..08b371129 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -206,6 +206,7 @@ def param_tag def typify_block_param api_map block_pin = closure if block_pin.is_a?(Pin::Block) && block_pin.receiver && index + # @sg-ignore flow-sensivie typing should handle is_a? with && return block_pin.typify_parameters(api_map)[index] end ComplexType::UNDEFINED diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index c34120451..20fe350ae 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -116,6 +116,7 @@ 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| + # @sg-ignore Unresolved call to requirement on Gem::Dependency dep = find_gem(runtime_dep.name, runtime_dep.requirement) next unless dep @@ -170,7 +171,7 @@ def to_gem_specification specish end nil end - # yay! + # yay! when Bundler::LazySpecification # materializing didn't work. Let's look in the local @@ -180,8 +181,11 @@ def to_gem_specification specish when Bundler::StubSpecification # turns a Bundler::StubSpecification into a # Gem::StubSpecification into a Gem::Specification + # @sg-ignore Flow-sensitive typing ought to be able to handle 'when ClassName' specish = specish.stub + # @sg-ignore Flow-sensitive typing ought to be able to handle 'when ClassName' if specish.respond_to?(:spec) + # @sg-ignore Flow-sensitive typing ought to be able to handle 'when ClassName' specish.spec else # turn the crank again @@ -190,8 +194,8 @@ def to_gem_specification specish else @@warned_on_gem_type ||= false unless @@warned_on_gem_type - logger.warn 'Unexpected type while resolving gem: ' \ - "#{specish.class}" + # @sg-ignore Unresolved call to class on Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification + logger.warn "Unexpected type while resolving gem: #{specish.class}" @@warned_on_gem_type = true end nil From a48c40a5b879c95548c8e7a59d91cdd51973e0bc Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 11:05:15 -0500 Subject: [PATCH 753/930] Fix merge --- spec/shell_spec.rb | 110 --------------------------------------------- 1 file changed, 110 deletions(-) diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index c28403c7b..12daabe99 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -297,114 +297,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 23e970e1918327b87515e310d96e46968fc0fb9d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 11:39:06 -0500 Subject: [PATCH 754/930] Merge branch 'intersection_types' into flow_sensitive_typing_2_0 --- .github/workflows/plugins.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index bb8c113b7..0a83f8aba 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -140,10 +140,10 @@ jobs: # this. (Gem::LoadError) bundle exec appraisal install bundle exec appraisal update date - # For some reason on ruby 3.1 it defaults to an old version: 1.3.2 - # https://github.com/lekemula/solargraph-rspec/actions/runs/17814581205/job/50645370316?pr=22 - # We update manually to the latest - bundle exec appraisal update rspec-rails + # For some reason on ruby 3.1 it defaults to an old version: 1.3.2 + # https://github.com/lekemula/solargraph-rspec/actions/runs/17814581205/job/50645370316?pr=22 + # We update manually to the latest + bundle exec appraisal update rspec-rails - name: Configure .solargraph.yml run: | cd ../solargraph-rspec From a2ce95f0970d61ef778ab4c550c2415b2a90cd20 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 11:41:14 -0500 Subject: [PATCH 755/930] Fix RuboCop issue --- spec/type_checker/levels/strong_spec.rb | 37 ------------------------- 1 file changed, 37 deletions(-) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index d475d6fc5..cd702d677 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -648,43 +648,6 @@ def baz expect(checker.problems.map(&:message)).to be_empty end - 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 - - 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 - - it 'resolves class name correctly in generic resolution' do - expect(checker.problems.map(&:message)).to be_empty - end - end - it 'handles "while foo" flow sensitive typing correctly' do checker = type_checker(%( # @param a [String, nil] From 8b0cbf87ded35062a54893b3c978245d80be0e15 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 11:53:41 -0500 Subject: [PATCH 756/930] Fix type issues --- lib/solargraph/api_map.rb | 1 - lib/solargraph/api_map/store.rb | 1 - lib/solargraph/language_server/host.rb | 1 + lib/solargraph/language_server/host/sources.rb | 1 + lib/solargraph/location.rb | 1 - lib/solargraph/position.rb | 1 - lib/solargraph/range.rb | 4 ---- lib/solargraph/type_checker.rb | 5 ++--- lib/solargraph/yard_map/mapper/to_method.rb | 1 - lib/solargraph/yard_map/to_method.rb | 1 - 10 files changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index cc3031ea5..d5eaa2746 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -870,7 +870,6 @@ def resolve_method_alias(alias_pin) break if original end - # @sg-ignore ignore `received nil` for original create_resolved_alias_pin(alias_pin, original) if original end diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index bc829ba5a..34079fad7 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -71,7 +71,6 @@ def get_constants fqns, visibility = [:public] # @return [Enumerable] def get_methods fqns, scope: :instance, visibility: [:public] all_pins = namespace_children(fqns).select do |pin| - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 pin.is_a?(Pin::Method) && pin.scope == scope && visibility.include?(pin.visibility) end GemPins.combine_method_pins_by_path(all_pins) diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index b228bdba6..d43f58a80 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -301,6 +301,7 @@ def prepare directory, name = nil end # @return [String] + # @sg-ignore Need detailed hash types def command_path options['commandPath'] || 'solargraph' end diff --git a/lib/solargraph/language_server/host/sources.rb b/lib/solargraph/language_server/host/sources.rb index da0c63b93..cdbaa8feb 100644 --- a/lib/solargraph/language_server/host/sources.rb +++ b/lib/solargraph/language_server/host/sources.rb @@ -55,6 +55,7 @@ def update uri, updater # @raise [FileNotFoundError] if the URI does not match an open source. # # @param uri [String] + # @sg-ignore Flow-sensitive typing should understand raise # @return [Solargraph::Source] def find uri open_source_hash[uri] || raise(Solargraph::FileNotFoundError, "Host could not find #{uri}") diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index df92668bf..713b4fef1 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -71,7 +71,6 @@ def self.from_node(node) # @param other [BasicObject] def == other return false unless other.is_a?(Location) - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 filename == other.filename and range == other.range end diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 74606f142..2faa0a99b 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -112,7 +112,6 @@ def self.normalize object def == other return false unless other.is_a?(Position) - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 line == other.line and character == other.character end end diff --git a/lib/solargraph/range.rb b/lib/solargraph/range.rb index 86452d646..7a9bc0e30 100644 --- a/lib/solargraph/range.rb +++ b/lib/solargraph/range.rb @@ -27,12 +27,9 @@ def initialize start, ending # @param other [BasicObject] def <=>(other) return nil unless other.is_a?(Range) - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 if start == other.start - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 ending <=> other.ending else - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 start <=> other.start end end @@ -101,7 +98,6 @@ def self.from_expr expr def == other return false unless other.is_a?(Range) - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 start == other.start && ending == other.ending end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 9c1a4bb0f..66fdf9c1f 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -192,7 +192,7 @@ def ignored_pins def variable_type_tag_problems result = [] all_variables.each do |pin| - # @sg-ignore Need to add nil check here + # @todo Need to add nil check here if pin.return_type.defined? declared = pin.typify(api_map) next if declared.duck_type? @@ -462,9 +462,8 @@ 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? - # @sg-ignore https://github.com/castwide/solargraph/pull/1127 argtype = argchain.infer(api_map, closure_pin, locals).self_to_type(closure_pin.context) - # @sg-ignore Unresolved call to defined? + # @todo Unresolved call to defined? 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 diff --git a/lib/solargraph/yard_map/mapper/to_method.rb b/lib/solargraph/yard_map/mapper/to_method.rb index 0838b9f4f..138c9ed44 100644 --- a/lib/solargraph/yard_map/mapper/to_method.rb +++ b/lib/solargraph/yard_map/mapper/to_method.rb @@ -85,7 +85,6 @@ def get_parameters code_object, location, comments, pin # HACK: Skip `nil` and `self` parameters that are sometimes emitted # for methods defined in C # See https://github.com/castwide/solargraph/issues/345 - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 code_object.parameters.select { |a| a[0] && a[0] != 'self' }.map do |a| Solargraph::Pin::Parameter.new( location: location, diff --git a/lib/solargraph/yard_map/to_method.rb b/lib/solargraph/yard_map/to_method.rb index 010db89a5..7380ff305 100644 --- a/lib/solargraph/yard_map/to_method.rb +++ b/lib/solargraph/yard_map/to_method.rb @@ -15,7 +15,6 @@ def get_parameters code_object, location, comments # HACK: Skip `nil` and `self` parameters that are sometimes emitted # for methods defined in C # See https://github.com/castwide/solargraph/issues/345 - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 code_object.parameters.select { |a| a[0] && a[0] != 'self' }.map do |a| Solargraph::Pin::Parameter.new( location: location, From 7cb629b46d697c223034df0ad001b0faf52fee31 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 11:55:07 -0500 Subject: [PATCH 757/930] Fix type issues --- .github/workflows/rspec.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 61b8a9cdf..6f9438c9a 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -40,13 +40,13 @@ jobs: - ruby-version: '3.4' # only include the 4.0 variants we include later - ruby-version: '4.0' - # Missing require in 'rbs collection update' - hopefully - # fixed in next RBS release - ruby-version: '4.0' rbs-version: '4.0.0.dev.4' - ruby-version: 'head' rbs-version: '3.9.4' - - ruby-version: 'head' + # Missing require in 'rbs collection update' - hopefully + # fixed in next RBS release + - ruby-version: '4.0' rbs-version: '3.6.1' include: - ruby-version: '3.1' From 63832df6eb3c7fab8531ffe49b5d30ccd1c8e3e8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 12:09:56 -0500 Subject: [PATCH 758/930] Fix rspec.yml --- .github/workflows/rspec.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 6f9438c9a..00d45b96d 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -42,12 +42,12 @@ jobs: - ruby-version: '4.0' - ruby-version: '4.0' rbs-version: '4.0.0.dev.4' - - ruby-version: 'head' + - ruby-version: '4.0' rbs-version: '3.9.4' # Missing require in 'rbs collection update' - hopefully # fixed in next RBS release - ruby-version: '4.0' - rbs-version: '3.6.1' + include: - ruby-version: '3.1' rbs-version: '3.6.1' @@ -58,7 +58,7 @@ jobs: - ruby-version: '3.4' rbs-version: '4.0.0.dev.4' - ruby-version: '4.0' - rbs-version: '3.6.1' + rbs-version: '4.0.0.dev.4' steps: - uses: actions/checkout@v3 - name: Set up Ruby From 704e97c027c8583aa5f6f0d7d5f0870ace8e5138 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 12:46:33 -0500 Subject: [PATCH 759/930] Fix rspec.yml --- .github/workflows/rspec.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 00d45b96d..acad1048c 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -47,7 +47,7 @@ jobs: # Missing require in 'rbs collection update' - hopefully # fixed in next RBS release - ruby-version: '4.0' - + rbs-version: '4.0.0.dev.4' include: - ruby-version: '3.1' rbs-version: '3.6.1' @@ -57,8 +57,6 @@ jobs: rbs-version: '4.0.0.dev.4' - ruby-version: '3.4' rbs-version: '4.0.0.dev.4' - - ruby-version: '4.0' - rbs-version: '4.0.0.dev.4' steps: - uses: actions/checkout@v3 - name: Set up Ruby From 21f05ec1d1347f35af4283a1e95500ffa2d5d6a3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 12:55:49 -0500 Subject: [PATCH 760/930] Add @sg-ignores --- lib/solargraph/pin/closure.rb | 1 + lib/solargraph/pin/common.rb | 1 + lib/solargraph/source_map.rb | 1 + lib/solargraph/source_map/data.rb | 2 ++ lib/solargraph/type_checker.rb | 4 ++++ lib/solargraph/workspace.rb | 1 + lib/solargraph/workspace/config.rb | 1 + 7 files changed, 11 insertions(+) diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index 347e0229e..911e42d24 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -44,6 +44,7 @@ def context end end + # @sg-ignore https://github.com/castwide/solargraph/pull/1100 def binder @binder || context end diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 062099ee4..a87c46b72 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -41,6 +41,7 @@ def namespace end # @return [ComplexType] + # @sg-ignore https://github.com/castwide/solargraph/pull/1100 def binder @binder || context end diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 15b747760..16628d917 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -187,6 +187,7 @@ def data end # @return [Array] + # @sg-ignore https://github.com/castwide/solargraph/pull/1100 def convention_pins @convention_pins || [] end diff --git a/lib/solargraph/source_map/data.rb b/lib/solargraph/source_map/data.rb index 453520414..b42f9184b 100644 --- a/lib/solargraph/source_map/data.rb +++ b/lib/solargraph/source_map/data.rb @@ -9,12 +9,14 @@ def initialize source end # @return [Array] + # @sg-ignore https://github.com/castwide/solargraph/pull/1100 def pins generate @pins || [] end # @return [Array] + # @sg-ignore https://github.com/castwide/solargraph/pull/1100 def locals generate @locals || [] diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 66fdf9c1f..835997334 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -284,8 +284,11 @@ def call_problems end closest = found.typify(api_map) if found # @todo remove the internal_or_core? check at a higher-than-strict level + # @sg-ignore Change to something flow-sensitive typing understands if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) + # @sg-ignore Change to something flow-sensitive typing understands unless closest.generic? || ignored_pins.include?(found) + # @sg-ignore Change to something flow-sensitive typing understands if closest.defined? result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest}") else @@ -629,6 +632,7 @@ def declared_externally? pin base = base.base end closest = found.typify(api_map) if found + # @sg-ignore Change to something flow-sensitive typing understands if !found || closest.defined? || internal?(found) return false end diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 655104307..e77861182 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -144,6 +144,7 @@ def synchronize! updater end # @return [String] + # @sg-ignore Need to validate config def command_path server['commandPath'] || 'solargraph' end diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 3e30e5d74..181564c97 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -79,6 +79,7 @@ def required # An array of load paths for required paths. # # @return [Array] + # @sg-ignore Need to validate config def require_paths raw_data['require_paths'] || [] end From 103ed36ac13b084e176ed08fc8dc0a84d00aff26 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 13:41:39 -0500 Subject: [PATCH 761/930] Remove @sg-ignores --- lib/solargraph/pin/closure.rb | 1 - lib/solargraph/pin/local_variable.rb | 2 -- lib/solargraph/source/chain/call.rb | 3 --- lib/solargraph/type_checker.rb | 4 ---- 4 files changed, 10 deletions(-) diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index 911e42d24..347e0229e 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -44,7 +44,6 @@ def context end end - # @sg-ignore https://github.com/castwide/solargraph/pull/1100 def binder @binder || context end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 7b59db1eb..efd94c5ff 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -55,9 +55,7 @@ def combine_closure(other) # @param other_closure [Pin::Closure] # @param other_loc [Location] - # @sg-ignore Need to add nil check here def visible_at?(other_closure, other_loc) - # @sg-ignore Need to add nil check here location.filename == other_loc.filename && (!presence || presence.include?(other_loc.range.start)) && visible_in_closure?(other_closure) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 42743d9f3..da72a557a 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -75,7 +75,6 @@ def resolve api_map, name_pin, locals def inferred_pins pins, api_map, name_pin, locals result = pins.map do |p| next p unless p.is_a?(Pin::Method) - # @sg-ignore Flow-sensitive typing should handle is_a? and next overloads = p.signatures # next p if overloads.empty? type = ComplexType::UNDEFINED @@ -84,7 +83,6 @@ def inferred_pins pins, api_map, name_pin, locals # use it. If we didn't pass a block, the logic below will # reject it regardless - # @sg-ignore Flow-sensitive typing should handle is_a? and next with_block, without_block = overloads.partition(&:block?) # @sg-ignore Flow-sensitive typing should handle is_a? and next # @type Array @@ -151,7 +149,6 @@ def inferred_pins pins, api_map, name_pin, locals end break if type.defined? end - # @sg-ignore Flow-sensitive typing should handle is_a? and next p = p.with_single_signature(new_signature_pin) unless new_signature_pin.nil? next p.proxy(type) if type.defined? if !p.macros.empty? diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 7ebae1f1a..a4c73ab8a 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -327,11 +327,8 @@ def call_problems 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 - # @sg-ignore Change to something flow-sensitive typing understands if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) - # @sg-ignore Change to something flow-sensitive typing understands unless closest.generic? || ignored_pins.include?(found) - # @sg-ignore Change to something flow-sensitive typing understands if closest.defined? result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest}") else @@ -680,7 +677,6 @@ def declared_externally? pin end all_closest = all_found.map { |pin| pin.typify(api_map) } closest = ComplexType.new(all_closest.flat_map(&:items).uniq) - # @sg-ignore Change to something flow-sensitive typing understands if !found || closest.defined? || internal?(found) return false end From a93dd3f7b7d509f5cb7be2cf6cd1deffb809208e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 15:01:46 -0500 Subject: [PATCH 762/930] Merge branch 'or_support_in_flow_sensitive_typing' into union_type_enforcement From 6f3c45952020879c082420c0d3e15d7d85524a29 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 15:28:42 -0500 Subject: [PATCH 763/930] Fix spec --- spec/type_checker/levels/strong_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index f56a46825..0432ca059 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -43,6 +43,7 @@ class Location attr_reader :filename # @param other [self] + # @return [-1, 0, 1, nil] def <=>(other) return nil unless other.is_a?(Location) From dc3cb9c0c139fede52d6974c22c46b24476420cc Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 15:53:20 -0500 Subject: [PATCH 764/930] Fix annotations --- lib/solargraph/api_map.rb | 1 + .../parser/parser_gem/node_processors/send_node.rb | 2 -- lib/solargraph/pin/local_variable.rb | 12 ++++++++++-- lib/solargraph/pin/method.rb | 12 +++++++++++- lib/solargraph/pin/proxy_type.rb | 2 +- lib/solargraph/source/chain/call.rb | 5 ++--- lib/solargraph/source_map.rb | 1 - lib/solargraph/source_map/mapper.rb | 2 ++ lib/solargraph/type_checker.rb | 5 ++--- 9 files changed, 29 insertions(+), 13 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 751df2a6a..bc9278d05 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -805,6 +805,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false if scope == :instance store.get_includes(fqns).reverse.each do |ref| in_tag = dereference(ref) + # @sg-ignore Need to add nil check here 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) 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 5b2747bd1..bbb210012 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -235,7 +235,6 @@ def process_module_function closure: cm, name: ivar.name, comments: ivar.comments, - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 assignment: ivar.assignment, source: :parser ) @@ -244,7 +243,6 @@ def process_module_function closure: mm, name: ivar.name, comments: ivar.comments, - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 assignment: ivar.assignment, source: :parser ) diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index efd94c5ff..82ea3eaa8 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -4,11 +4,12 @@ module Solargraph module Pin class LocalVariable < BaseVariable # @param api_map [ApiMap] - # @return [ComplexType] + # @return [ComplexType, ComplexType::UniqueType] def probe api_map - if presence_certain? && return_type&.defined? + if presence_certain? && return_type && return_type&.defined? # flow sensitive typing has already figured out this type # has been downcast - use the type it figured out + # @sg-ignore Flow-sensitive typing should support ivars return adjust_type api_map, return_type.qualify(api_map, *gates) end @@ -22,10 +23,12 @@ def combine_with(other, attrs={}) super end + # @sg-ignore Flow-sensitive typing should support ivars # @param other_loc [Location] def starts_at?(other_loc) location&.filename == other_loc.filename && presence && + # @sg-ignore Flow-sensitive typing should support ivars presence.start == other_loc.range.start end @@ -43,11 +46,14 @@ def combine_closure(other) return closure || other.closure end + # @sg-ignore Flow-sensitive typing should support ivars if closure.location.nil? || other.closure.location.nil? + # @sg-ignore Flow-sensitive typing should support ivars return closure.location.nil? ? other.closure : closure end # if filenames are different, this will just pick one + # @sg-ignore Flow-sensitive typing should support ivars return closure if closure.location <= other.closure.location other.closure @@ -56,7 +62,9 @@ def combine_closure(other) # @param other_closure [Pin::Closure] # @param other_loc [Location] def visible_at?(other_closure, other_loc) + # @sg-ignore Need to add nil check here location.filename == other_loc.filename && + # @sg-ignore Flow-sensitive typing should support || (!presence || presence.include?(other_loc.range.start)) && visible_in_closure?(other_closure) end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 73ce9cb06..a5e338ad1 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -418,6 +418,11 @@ def overloads generics: generics, # @param src [Array(String, String)] parameters: tag.parameters.map do |src| + # @sg-ignore Wrong argument type for + # Solargraph::Pin::Method#parse_overload_param: name + # expected String, received String, generic, + # generic, generic, generic, generic, + # generic, generic, generic name, decl = parse_overload_param(src.first) Pin::Parameter.new( location: location, @@ -427,6 +432,11 @@ def overloads decl: decl, # @sg-ignore flow sensitive typing needs to handle attrs presence: location ? location.range : nil, + # @sg-ignore Wrong argument type for + # Solargraph::Pin::Method#param_type_from_name: name + # expected String, received String, generic, + # generic, generic, generic, generic, + # generic, generic, generic return_type: param_type_from_name(tag, src.first), source: :overloads ) @@ -553,7 +563,7 @@ def see_reference api_map docstring.ref_tags.each do |ref| # @sg-ignore ref should actually be an intersection type next unless ref.tag_name == 'return' && ref.owner - # @todo ref should actually be an intersection type + # @sg-ignore should actually be an intersection type result = resolve_reference(ref.owner.to_s, api_map) return result unless result.nil? end diff --git a/lib/solargraph/pin/proxy_type.rb b/lib/solargraph/pin/proxy_type.rb index 2f40b353f..e856c8833 100644 --- a/lib/solargraph/pin/proxy_type.rb +++ b/lib/solargraph/pin/proxy_type.rb @@ -3,7 +3,7 @@ module Solargraph module Pin class ProxyType < Base - # @param return_type [ComplexType] + # @param return_type [ComplexType, ComplexType::UniqueType] # @param gates [Array, nil] Namespaces to try while resolving non-rooted types # @param binder [ComplexType, ComplexType::UniqueType, nil] # @param gates [Array, nil] diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index dbcf75f69..7ecd4181c 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -127,7 +127,7 @@ def inferred_pins pins, api_map, name_pin, locals if match if ol.block && with_block? block_atypes = ol.block.parameters.map(&:return_type) - # @sg-ignore Need to add nil check here + # @todo Need to add nil check here if block.links.map(&:class) == [BlockSymbol] # like the bar in foo(&:bar) blocktype = block_symbol_call_type(api_map, name_pin.context, block_atypes, locals) @@ -137,7 +137,6 @@ def inferred_pins pins, api_map, name_pin, locals end # @type new_signature_pin [Pin::Signature] new_signature_pin = ol.resolve_generics_from_context_until_complete(ol.generics, atypes, nil, nil, blocktype) - # @sg-ignore Should handle redefinition of types in simple contexts new_return_type = new_signature_pin.return_type if head? # If we're at the head of the chain, we called a @@ -159,7 +158,7 @@ def inferred_pins pins, api_map, name_pin, locals # # qualify(), however, happens in the namespace where # the docs were written - from the method pin. - # @sg-ignore Need to add nil check here + # @todo Need to add nil check here type = with_params(new_return_type.self_to_type(self_type), self_type).qualify(api_map, *p.gates) if new_return_type.defined? type ||= ComplexType::UNDEFINED end diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 14e8a153e..241cc680f 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -191,7 +191,6 @@ def data end # @return [Array] - # @sg-ignore https://github.com/castwide/solargraph/pull/1100 def convention_pins @convention_pins || [] end diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 3e4f9408e..98d48739f 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -119,6 +119,8 @@ def process_directive source_position, comment_position, directive begin src = Solargraph::Source.load_string("def #{directive.tag.name};end", @source.filename) region = Parser::Region.new(source: src, closure: namespace) + # @sg-ignore Unresolved call to is_a? + # @type [Array] method_gen_pins = Parser.process_node(src.node, region).first.select { |pin| pin.is_a?(Pin::Method) } gen_pin = method_gen_pins.last return if gen_pin.nil? diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 31760a44f..d8c53dace 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -112,8 +112,8 @@ def load_string code, filename = nil, level = :normal, api_map: nil source = Solargraph::Source.load_string(code, filename) rules = Rules.new(level) api_map ||= Solargraph::ApiMap.new(loose_unions: - # @sg-ignore flow sensitive typing needs better handling of ||= on lvars !rules.require_all_unique_types_match_expected_on_lhs?) + # @sg-ignore flow sensitive typing needs better handling of ||= on lvars api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end @@ -236,7 +236,7 @@ def ignored_pins def variable_type_tag_problems result = [] all_variables.each do |pin| - # @todo Need to add nil check here + # @sg-ignore Need to add nil check here if pin.return_type.defined? declared = pin.typify(api_map) next if declared.duck_type? @@ -527,7 +527,6 @@ 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? - # @sg-ignore https://github.com/castwide/solargraph/pull/1127 # @type [ComplexType] argtype = argchain.infer(api_map, closure_pin, locals).self_to_type(closure_pin.context) # @todo Unresolved call to defined? From a612241b740331480e94378d04582535cfb4c00e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 15:59:00 -0500 Subject: [PATCH 765/930] Fix RuboCop issues --- lib/solargraph/complex_type/unique_type.rb | 1 - lib/solargraph/pin/base_variable.rb | 2 +- lib/solargraph/pin/local_variable.rb | 2 +- spec/type_checker/levels/strong_spec.rb | 18 ------------------ 4 files changed, 2 insertions(+), 21 deletions(-) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index c2d42270e..86f6c28bf 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -366,7 +366,6 @@ def generic? name == GENERIC_TAG_NAME || all_params.any?(&:generic?) end - def nullable? nil_type? end diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index b6f9e42e3..3ec34c775 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -90,7 +90,7 @@ def combine_with(other, attrs={}) intersection_return_type: combine_types(other, :intersection_return_type), exclude_return_type: combine_types(other, :exclude_return_type), presence: combine_presence(other), - presence_certain: combine_presence_certain(other), + presence_certain: combine_presence_certain(other) }) super(other, new_attrs) end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 82ea3eaa8..a159e609f 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -53,7 +53,7 @@ def combine_closure(other) end # if filenames are different, this will just pick one - # @sg-ignore Flow-sensitive typing should support ivars + # @sg-ignore Flow-sensitive typing should support ivars return closure if closure.location <= other.closure.location other.closure diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 0432ca059..a2286c331 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -36,24 +36,6 @@ def a b expect(checker.problems.map(&:message)).to be_empty end - it 'understands self type when passed as parameter' do - checker = type_checker(%( - class Location - # @return [String] - attr_reader :filename - - # @param other [self] - # @return [-1, 0, 1, nil] - def <=>(other) - return nil unless other.is_a?(Location) - - filename <=> other.filename - end - end - )) - expect(checker.problems.map(&:message)).to be_empty - end - it 'respects pin visibility in if/nil? pattern' do checker = type_checker(%( class Foo From 2b753e87d0ded080af7fa5c4afa7b22f6ff84e87 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 16:47:28 -0500 Subject: [PATCH 766/930] Bump RBS versions in rspec test --- .github/workflows/rspec.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 73edd47a3..db84786dd 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -21,8 +21,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby-version: ['3.0', '3.1', '3.2', '3.3', '3.4', '4.0'] - rbs-version: ['3.6.1', '3.9.5', '4.0.0.dev.4'] + ruby-version: ['3.0', '3.1', '3.2', '3.3', '3.4', '4.0', 'head'] + rbs-version: ['3.6.1', '3.8.1', '3.9.5', '3.10.0', '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 @@ -42,16 +42,19 @@ jobs: # fixed in next RBS release - ruby-version: '4.0' rbs-version: '3.6.1' - - ruby-version: '4.0' + - ruby-version: 'head' + rbs-version: '3.6.1' include: - ruby-version: '3.1' rbs-version: '3.6.1' - ruby-version: '3.2' - rbs-version: '3.9.4' + rbs-version: '3.8.1' - ruby-version: '3.3' - rbs-version: '4.0.0.dev.4' + rbs-version: '3.8.5' - ruby-version: '3.4' - rbs-version: '4.0.0.dev.4' + rbs-version: '3.10.0' + - ruby-version: '3.4' + rbs-version: '4.0.0.dev' steps: - uses: actions/checkout@v3 - name: Set up Ruby From e182a53fe173d866e6d7ca4c256a5cdf7d774c55 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 16:48:26 -0500 Subject: [PATCH 767/930] Fix version --- .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 db84786dd..95d721900 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -54,7 +54,7 @@ jobs: - ruby-version: '3.4' rbs-version: '3.10.0' - ruby-version: '3.4' - rbs-version: '4.0.0.dev' + rbs-version: '4.0.0.dev.4' steps: - uses: actions/checkout@v3 - name: Set up Ruby From 34cdf784ad06ae9688c04a65af12310859ebca5a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 16:50:37 -0500 Subject: [PATCH 768/930] Fix version matrix --- .github/workflows/rspec.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 95d721900..406268714 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -25,9 +25,11 @@ jobs: rbs-version: ['3.6.1', '3.8.1', '3.9.5', '3.10.0', '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 3.0 doesn't work with RBS >=3.9 - ruby-version: '3.0' rbs-version: '3.9.5' + - ruby-version: '3.0' + rbs-version: '3.8.1' - ruby-version: '3.0' rbs-version: '4.0.0.dev.4' # only include the 3.1 variants we include later @@ -38,12 +40,10 @@ jobs: - ruby-version: '3.3' # only include the 3.4 variants we include later - ruby-version: '3.4' - # Missing require in 'rbs collection update' - hopefully - # fixed in next RBS release - - ruby-version: '4.0' - rbs-version: '3.6.1' - - ruby-version: 'head' - rbs-version: '3.6.1' + # - ruby-version: '4.0' + # rbs-version: '3.6.1' + # - ruby-version: 'head' + # rbs-version: '3.6.1' include: - ruby-version: '3.1' rbs-version: '3.6.1' From 851d142473fd362a04c0c3bca6aad97e3c06224e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 16:58:47 -0500 Subject: [PATCH 769/930] Fix version matrix --- .github/workflows/rspec.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 406268714..7913bdabf 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -25,13 +25,8 @@ jobs: rbs-version: ['3.6.1', '3.8.1', '3.9.5', '3.10.0', '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 - - ruby-version: '3.0' - rbs-version: '3.9.5' - - ruby-version: '3.0' - rbs-version: '3.8.1' - - ruby-version: '3.0' - rbs-version: '4.0.0.dev.4' + # only include the 3.0 variants we include later + - ruby-version: '3.1' # only include the 3.1 variants we include later - ruby-version: '3.1' # only include the 3.2 variants we include later @@ -40,11 +35,18 @@ jobs: - ruby-version: '3.3' # only include the 3.4 variants we include later - ruby-version: '3.4' + # only include the 4.0 variants we include later + - ruby-version: '4.0' + # Don't exclude 'head' - let's test all RBS versions we + # can there + # - ruby-version: '4.0' # rbs-version: '3.6.1' # - ruby-version: 'head' # rbs-version: '3.6.1' include: + - ruby-version: '3.0' + rbs-version: '3.6.1' - ruby-version: '3.1' rbs-version: '3.6.1' - ruby-version: '3.2' From e2d27c9dcaec14b90cd8f3e262dc8ca36d3a28c3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 16:59:50 -0500 Subject: [PATCH 770/930] Fix version matrix --- .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 7913bdabf..94a714508 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -26,7 +26,7 @@ jobs: # Ruby 3.0 doesn't work with RBS 3.9.4 or 4.0.0.dev.4 exclude: # only include the 3.0 variants we include later - - ruby-version: '3.1' + - ruby-version: '3.0' # only include the 3.1 variants we include later - ruby-version: '3.1' # only include the 3.2 variants we include later From 8df808a8bf5cf3b13c3a46d41e4c1ff88286e122 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 17:01:05 -0500 Subject: [PATCH 771/930] Fix version matrix --- .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 94a714508..dcb726445 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -52,7 +52,7 @@ jobs: - ruby-version: '3.2' rbs-version: '3.8.1' - ruby-version: '3.3' - rbs-version: '3.8.5' + rbs-version: '3.9.5' - ruby-version: '3.4' rbs-version: '3.10.0' - ruby-version: '3.4' From 9974481a534d2e7c386121c01142c933f90c120b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 17:10:50 -0500 Subject: [PATCH 772/930] Fix version matrix --- .github/workflows/rspec.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index dcb726445..ced8df10d 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -40,10 +40,12 @@ jobs: # Don't exclude 'head' - let's test all RBS versions we # can there - # - ruby-version: '4.0' - # rbs-version: '3.6.1' - # - ruby-version: 'head' - # rbs-version: '3.6.1' + # For some reason 'rbs collection install' is broken in + # 4.0.0.dev.4 on newer Rubies: + # + # https://github.com/castwide/solargraph/actions/runs/20627923548/job/59241444380?pr=1102 + - ruby-version: 'head' + rbs-version: '4.0.0.dev.4' include: - ruby-version: '3.0' rbs-version: '3.6.1' From 56e25350a0bffe071172d3f563bf4935f6290e56 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 17:22:43 -0500 Subject: [PATCH 773/930] Exclude another --- .github/workflows/rspec.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index ced8df10d..65eb8cac8 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -40,10 +40,9 @@ jobs: # Don't exclude 'head' - let's test all RBS versions we # can there - # For some reason 'rbs collection install' is broken in - # 4.0.0.dev.4 on newer Rubies: - # # https://github.com/castwide/solargraph/actions/runs/20627923548/job/59241444380?pr=1102 + - ruby-version: 'head' + rbs-version: '3.8.1' - ruby-version: 'head' rbs-version: '4.0.0.dev.4' include: From d9e49366f2de9f8093c3bd48eecb566ddc751af2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 17:29:22 -0500 Subject: [PATCH 774/930] Exclude another --- .github/workflows/rspec.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 65eb8cac8..39475cc1d 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -41,6 +41,8 @@ jobs: # can there # https://github.com/castwide/solargraph/actions/runs/20627923548/job/59241444380?pr=1102 + - ruby-version: 'head' + rbs-version: '3.6.1' - ruby-version: 'head' rbs-version: '3.8.1' - ruby-version: 'head' From 751430271fbb5ac9b899923508200605171320b0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 17:52:29 -0500 Subject: [PATCH 775/930] Add version, fix doc --- .github/workflows/rspec.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 39475cc1d..f761b61aa 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -38,8 +38,12 @@ jobs: # only include the 4.0 variants we include later - ruby-version: '4.0' # Don't exclude 'head' - let's test all RBS versions we - # can there - + # can there. + # + # + # Just exclude some odd-ball compatibility issues we can't + # work around: + # # https://github.com/castwide/solargraph/actions/runs/20627923548/job/59241444380?pr=1102 - ruby-version: 'head' rbs-version: '3.6.1' @@ -56,6 +60,8 @@ jobs: rbs-version: '3.8.1' - ruby-version: '3.3' rbs-version: '3.9.5' + - ruby-version: '3.3' + rbs-version: '3.10.0' - ruby-version: '3.4' rbs-version: '3.10.0' - ruby-version: '3.4' From 935bc9c4d67ef1567dbc85306dec2e7fc5d217fa Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 31 Dec 2025 18:42:24 -0500 Subject: [PATCH 776/930] init -> config --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index febc972ee..044018cc6 100755 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ 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. +Use `bundle exec solargraph config` to create a configuration file. ### Plugins From 05265b9c0d801ac2af4929b61700cd7edf045e2d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 1 Jan 2026 10:40:09 -0500 Subject: [PATCH 777/930] Fix rbs-version for Ruby 4.0 in CI workflow --- .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 4a3388ce4..c82ade49b 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -29,10 +29,10 @@ jobs: rbs-version: '3.9.5' - ruby-version: '3.0' rbs-version: '4.0.0.dev.4' - - ruby-version: '4.0' - rbs-version: '3.6.1' # Missing require in 'rbs collection update' - hopefully # fixed in next RBS release + - ruby-version: '4.0' + rbs-version: '3.6.1' - ruby-version: '4.0' rbs-version: '4.0.0.dev.4' steps: From 58737d715dd06e2931062b693fe0b270c066a774 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 1 Jan 2026 10:40:47 -0500 Subject: [PATCH 778/930] Clean up ruby-version entries in rspec.yml Removed deprecated ruby-version entries for RBS. --- .github/workflows/rspec.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 20e064585..c82ade49b 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -31,12 +31,6 @@ jobs: rbs-version: '4.0.0.dev.4' # Missing require in 'rbs collection update' - hopefully # fixed in next RBS release - - ruby-version: 'head' - rbs-version: '4.0.0.dev.4' - - ruby-version: 'head' - rbs-version: '3.9.4' - - ruby-version: 'head' - rbs-version: '3.6.1' - ruby-version: '4.0' rbs-version: '3.6.1' - ruby-version: '4.0' From b5d11573355cb5ee3193d13b1bfa52039599b011 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 1 Jan 2026 10:41:22 -0500 Subject: [PATCH 779/930] Fix RBS version for Ruby 4.0 in workflow --- .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 4a3388ce4..c82ade49b 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -29,10 +29,10 @@ jobs: rbs-version: '3.9.5' - ruby-version: '3.0' rbs-version: '4.0.0.dev.4' - - ruby-version: '4.0' - rbs-version: '3.6.1' # Missing require in 'rbs collection update' - hopefully # fixed in next RBS release + - ruby-version: '4.0' + rbs-version: '3.6.1' - ruby-version: '4.0' rbs-version: '4.0.0.dev.4' steps: From db36785e89de1d70715347b6a35b0ac39dfc479a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 1 Jan 2026 14:26:48 -0500 Subject: [PATCH 780/930] Fix merge --- lib/solargraph/type_checker.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 75f72df05..63a0796fc 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -101,8 +101,7 @@ class << self # @return [self] def load filename, level = :normal source = Solargraph::Source.load(filename) - workspace = Workspace.new(File.dirname(filename)) - rules = workspace.rules(level) + rules = Rules.new(level, {}) api_map = Solargraph::ApiMap.new(loose_unions: !rules.require_all_unique_types_match_expected_on_lhs?) api_map.map(source) @@ -116,8 +115,7 @@ def load filename, level = :normal # @return [self] def load_string code, filename = nil, level = :normal, api_map: nil source = Solargraph::Source.load_string(code, filename) - workspace = Workspace.new(File.dirname(filename)) - rules = workspace.rules(level) + rules = Rules.new(level, {}) api_map ||= Solargraph::ApiMap.new(loose_unions: !rules.require_all_unique_types_match_expected_on_lhs?) api_map.map(source) From 977b18355a293deb5677e73303cdb02e11530534 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 1 Jan 2026 20:03:51 -0500 Subject: [PATCH 781/930] Improve signature combination Use our generated RBS signature from parameters as a key to combine method signatures from RBS/YARD pins. This is closer to what RBS does than the current technique of using the arity alone, and fixes a key degenerate case in Integer#+ revealed by updated definitions used by recently released RBS gems --- lib/solargraph/doc_map.rb | 2 +- lib/solargraph/parser/parser_gem/class_methods.rb | 1 + lib/solargraph/parser/parser_gem/node_methods.rb | 1 - lib/solargraph/pin/callable.rb | 7 ++++++- lib/solargraph/pin/method.rb | 14 +++++++------- spec/pin/method_spec.rb | 10 ++++++++++ 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index e45ff0b65..136c02078 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -346,7 +346,7 @@ def gemspec_or_preference gemspec end # @param gemspec [Gem::Specification] - # @param version [Gem::Version] + # @param version [Gem::Version, String] # @return [Gem::Specification] def change_gemspec_version gemspec, version Gem::Specification.find_by_name(gemspec.name, "= #{version}") diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index 2daf22fc7..c8fc186c8 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -19,6 +19,7 @@ def parse_with_comments code, filename = nil # @param filename [String, nil] # @param line [Integer] # @return [Parser::AST::Node] + # @sg-ignore Need to handle type of 'raise' def parse code, filename = nil, line = 0 buffer = ::Parser::Source::Buffer.new(filename, line) buffer.source = code diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index b77c4cd47..02f790c00 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 Wrong argument type for AST::Node.new: type expected AST::_ToSym, received :nil NIL_NODE = ::Parser::AST::Node.new(:nil) # @param node [Parser::AST::Node] diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index edbc3f941..bfce92c24 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -215,8 +215,13 @@ def mandatory_positional_param_count parameters.count(&:arg?) end + # @return [String] + def parameters_to_rbs + rbs_generics + '(' + parameters.map { |param| param.to_rbs }.join(', ') + ') ' + (block.nil? ? '' : '{ ' + block.to_rbs + ' } ') + end + def to_rbs - rbs_generics + '(' + parameters.map { |param| param.to_rbs }.join(', ') + ') ' + (block.nil? ? '' : '{ ' + block.to_rbs + ' } ') + '-> ' + return_type.to_rbs + parameters_to_rbs + '-> ' + return_type.to_rbs end def block? diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 86bf1cd09..30afb8f48 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -37,21 +37,21 @@ def initialize visibility: :public, explicit: true, block: :undefined, node: nil # @param signature_pins [Array] # @return [Array] def combine_all_signature_pins(*signature_pins) - # @type [Hash{Array => Array}] - by_arity = {} + # @type [Hash{String => Array}] + by_rbs = {} signature_pins.each do |signature_pin| - by_arity[signature_pin.arity] ||= [] - by_arity[signature_pin.arity] << signature_pin + by_rbs[signature_pin.parameters_to_rbs] ||= [] + by_rbs[signature_pin.parameters_to_rbs] << signature_pin end - by_arity.transform_values! do |same_arity_pins| + by_rbs.transform_values! do |same_rbs_pins| # @param memo [Pin::Signature, nil] # @param signature [Pin::Signature] - same_arity_pins.reduce(nil) do |memo, signature| + same_rbs_pins.reduce(nil) do |memo, signature| next signature if memo.nil? memo.combine_with(signature) end end - by_arity.values.flatten + by_rbs.values.flatten end # @param other [Pin::Method] diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index 283ef6d51..c2ef40448 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -549,6 +549,16 @@ class Foo expect(pin.return_type).to be_undefined end + it 'combines signatures by type' do + # Integer+ in RBS is a number of signatures that dispatch based + # on type. Let's make sure we combine those with anything else + # found (e.g., additions from the BigDecimal RBS collection) + # without collapsing signatures + api_map = Solargraph::ApiMap.load_with_cache(Dir.pwd, nil) + method = api_map.get_method_stack('Integer', '+', scope: :instance).first + expect(method.signatures.count).to be > 3 + end + it 'infers untagged types from instance variables' do source = Solargraph::Source.load_string(%( class Foo From 1587a8119d8a5a3c48daa5bd4a8d1e20ffa14b5d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 1 Jan 2026 20:21:05 -0500 Subject: [PATCH 782/930] Update annotations --- lib/solargraph/api_map.rb | 1 + lib/solargraph/diagnostics/require_not_found.rb | 1 + lib/solargraph/diagnostics/rubocop.rb | 1 + lib/solargraph/language_server/host/dispatch.rb | 1 + lib/solargraph/library.rb | 10 ++++++++++ lib/solargraph/logging.rb | 3 +++ lib/solargraph/parser/region.rb | 2 +- lib/solargraph/source/cursor.rb | 2 +- lib/solargraph/source_map.rb | 2 +- lib/solargraph/source_map/clip.rb | 1 + lib/solargraph/source_map/mapper.rb | 2 +- lib/solargraph/workspace.rb | 3 +++ 12 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index bc9278d05..72effa759 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -620,6 +620,7 @@ def locate_pins location # @param cursor [Source::Cursor] # @return [SourceMap::Clip] def clip cursor + # @sg-ignore Need to add nil check here raise FileNotFoundError, "ApiMap did not catalog #{cursor.filename}" unless source_map_hash.key?(cursor.filename) SourceMap::Clip.new(self, cursor) diff --git a/lib/solargraph/diagnostics/require_not_found.rb b/lib/solargraph/diagnostics/require_not_found.rb index 757849e08..df42da2e5 100644 --- a/lib/solargraph/diagnostics/require_not_found.rb +++ b/lib/solargraph/diagnostics/require_not_found.rb @@ -10,6 +10,7 @@ def diagnose source, api_map return [] unless source.parsed? && source.synchronized? result = [] refs = {} + # @sg-ignore Need to add nil check here map = api_map.source_map(source.filename) map.requires.each { |ref| refs[ref.name] = ref } api_map.missing_docs.each do |r| diff --git a/lib/solargraph/diagnostics/rubocop.rb b/lib/solargraph/diagnostics/rubocop.rb index 0a55d0367..5eade7592 100644 --- a/lib/solargraph/diagnostics/rubocop.rb +++ b/lib/solargraph/diagnostics/rubocop.rb @@ -25,6 +25,7 @@ class Rubocop < Base def diagnose source, _api_map @source = source require_rubocop(rubocop_version) + # @sg-ignore Need to add nil check here options, paths = generate_options(source.filename, source.code) store = RuboCop::ConfigStore.new runner = RuboCop::Runner.new(options, store) diff --git a/lib/solargraph/language_server/host/dispatch.rb b/lib/solargraph/language_server/host/dispatch.rb index 1ff1227b8..f64b4ac96 100644 --- a/lib/solargraph/language_server/host/dispatch.rb +++ b/lib/solargraph/language_server/host/dispatch.rb @@ -33,6 +33,7 @@ def libraries # @return [void] def update_libraries uri src = sources.find(uri) + # @sg-ignore Need to add nil check here using = libraries.select { |lib| lib.contain?(src.filename) } using.push library_for(uri) if using.empty? using.each { |lib| lib.merge src } diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 9ba154a85..129d23f0d 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -490,10 +490,13 @@ def mapped? # @return [SourceMap, Boolean] def next_map return false if mapped? + # @sg-ignore Need to add nil check here src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) } if src Logging.logger.debug "Mapping #{src.filename}" + # @sg-ignore Need to add nil check here source_map_hash[src.filename] = Solargraph::SourceMap.map(src) + # @sg-ignore Need to add nil check here source_map_hash[src.filename] else false @@ -503,7 +506,9 @@ def next_map # @return [self] def map! workspace.sources.each do |src| + # @sg-ignore Need to add nil check here source_map_hash[src.filename] = Solargraph::SourceMap.map(src) + # @sg-ignore Need to add nil check here find_external_requires source_map_hash[src.filename] end self @@ -534,6 +539,7 @@ def find_external_requires source_map # return if new_set == source_map_external_require_hash[source_map.filename] _filenames = nil filenames = ->{ _filenames ||= workspace.filenames.to_set } + # @sg-ignore Need to add nil check here source_map_external_require_hash[source_map.filename] = new_set.reject do |path| workspace.require_paths.any? do |base| full = File.join(base, path) @@ -585,11 +591,15 @@ def handle_file_not_found filename, error # @return [void] def maybe_map source return unless source + # @sg-ignore Need to add nil check here return unless @current == source || workspace.has_file?(source.filename) + # @sg-ignore Need to add nil check here if source_map_hash.key?(source.filename) new_map = Solargraph::SourceMap.map(source) + # @sg-ignore Need to add nil check here source_map_hash[source.filename] = new_map else + # @sg-ignore Need to add nil check here source_map_hash[source.filename] = Solargraph::SourceMap.map(source) end end diff --git a/lib/solargraph/logging.rb b/lib/solargraph/logging.rb index 707a13936..30bdc06c2 100644 --- a/lib/solargraph/logging.rb +++ b/lib/solargraph/logging.rb @@ -46,6 +46,9 @@ def logger else new_log_level = LOG_LEVELS[log_level.to_s] logger = Logger.new(STDERR, level: new_log_level) + + # @sg-ignore Wrong argument type for Logger#formatter=: arg_0 + # expected nil, received Logger::_Formatter, nil logger.formatter = @@logger.formatter logger end diff --git a/lib/solargraph/parser/region.rb b/lib/solargraph/parser/region.rb index 279ad0e57..c1906e646 100644 --- a/lib/solargraph/parser/region.rb +++ b/lib/solargraph/parser/region.rb @@ -35,7 +35,7 @@ def initialize source: Solargraph::Source.load_string(''), closure: nil, @lvars = lvars end - # @return [String] + # @return [String, nil] def filename source.filename end diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index db6772595..5ee9ac4b8 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -19,7 +19,7 @@ def initialize source, position @position = Position.normalize(position) end - # @return [String] + # @return [String, nil] def filename source.filename end diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 241cc680f..714970f21 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -65,7 +65,7 @@ def api_hash @api_hash ||= (pins_by_class(Pin::Constant) + pins_by_class(Pin::Namespace).select { |pin| pin.namespace.to_s > '' } + pins_by_class(Pin::Reference) + pins_by_class(Pin::Method).map(&:node) + locals).hash end - # @return [String] + # @return [String, nil] def filename source.filename end diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index c3caeafda..b2b4d2b5d 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -94,6 +94,7 @@ def translate phrase # @return [SourceMap] def source_map + # @sg-ignore Need to add nil check here @source_map ||= api_map.source_map(cursor.filename) end diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 98d48739f..5440c65df 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -48,6 +48,7 @@ class << self # @param source [Source] # @return [Array] def map source + # @sg-ignore Need to add nil check here return new.unmap(source.filename, source.code) unless source.parsed? new.map source end @@ -119,7 +120,6 @@ def process_directive source_position, comment_position, directive begin src = Solargraph::Source.load_string("def #{directive.tag.name};end", @source.filename) region = Parser::Region.new(source: src, closure: namespace) - # @sg-ignore Unresolved call to is_a? # @type [Array] method_gen_pins = Parser.process_node(src.node, region).first.select { |pin| pin.is_a?(Pin::Method) } gen_pin = method_gen_pins.last diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 694aacd52..a0fc52993 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -63,6 +63,7 @@ def rules(level) # @param sources [Array] # @return [Boolean] True if the source was added to the workspace def merge *sources + # @sg-ignore Need to add nil check here unless directory == '*' || sources.all? { |source| source_hash.key?(source.filename) } # Reload the config to determine if a new source should be included @config = Solargraph::Workspace::Config.new(directory) @@ -70,7 +71,9 @@ def merge *sources includes_any = false sources.each do |source| + # @sg-ignore Need to add nil check here if directory == "*" || config.calculated.include?(source.filename) + # @sg-ignore Need to add nil check here source_hash[source.filename] = source includes_any = true end From 81927c837dd9e5fbc367647b7afca61c22abe812 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 1 Jan 2026 20:22:14 -0500 Subject: [PATCH 783/930] Drop annotation --- lib/solargraph/parser/parser_gem/class_methods.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index c8fc186c8..2daf22fc7 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -19,7 +19,6 @@ def parse_with_comments code, filename = nil # @param filename [String, nil] # @param line [Integer] # @return [Parser::AST::Node] - # @sg-ignore Need to handle type of 'raise' def parse code, filename = nil, line = 0 buffer = ::Parser::Source::Buffer.new(filename, line) buffer.source = code From 1b931447c424f8597491f7d3ccb76aa3e2944333 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 1 Jan 2026 20:24:01 -0500 Subject: [PATCH 784/930] Fix RuboCop issue --- lib/solargraph/workspace.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 06980e6d0..156855a1b 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -70,10 +70,10 @@ def merge *sources includes_any = false sources.each do |source| - if directory == "*" || config.calculated.include?(source.filename) - source_hash[source.filename] = source - includes_any = true - end + next unless directory == "*" || config.calculated.include?(source.filename) + + source_hash[source.filename] = source + includes_any = true end includes_any From 1f0c11d7f40a0e00f66727bcd1d9887f9c5929d5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 1 Jan 2026 21:34:25 -0500 Subject: [PATCH 785/930] Fix merge --- spec/doc_map_spec.rb | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 6ab97ef97..376e7d9bd 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -37,39 +37,23 @@ # 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'], []) + 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 - 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 - context 'with an uncached but valid gemspec' do let(:requires) { ['uncached_gem'] } let(:pre_cache) { false } From 25b114852089ce87515ef34a294790a284d5ef39 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 1 Jan 2026 21:42:06 -0500 Subject: [PATCH 786/930] Don't use solargraph-rspec branch --- .github/workflows/plugins.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index c7ad72cb4..3f34cbae1 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -116,12 +116,9 @@ jobs: - name: clone https://github.com/lekemula/solargraph-rspec/ run: | cd .. - # git clone https://github.com/lekemula/solargraph-rspec.git + git clone https://github.com/lekemula/solargraph-rspec.git - # pending https://github.com/lekemula/solargraph-rspec/pull/30 - git clone https://github.com/apiology/solargraph-rspec.git cd solargraph-rspec - git checkout reset_closures - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From f44141cc5788e5fcd802fef526214704792d0f69 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 1 Jan 2026 21:58:08 -0500 Subject: [PATCH 787/930] Fix merge --- spec/gem_pins_spec.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/gem_pins_spec.rb b/spec/gem_pins_spec.rb index cafed4cf6..4d2bb4ff5 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 From 979850540f21ca22abdd078d83dacf0b987271dd Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 1 Jan 2026 23:35:16 -0500 Subject: [PATCH 788/930] Debug --- .github/workflows/rspec.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index f761b61aa..ae507e2a5 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -51,6 +51,7 @@ jobs: rbs-version: '3.8.1' - ruby-version: 'head' rbs-version: '4.0.0.dev.4' + - ruby-version: 'head' # TEMP include: - ruby-version: '3.0' rbs-version: '3.6.1' @@ -89,8 +90,6 @@ jobs: run: | bundle _2.5.23_ install bundle update rbs # use latest available for this Ruby version - bundle list - bundle exec solargraph pin 'Bundler::Dsl#source' - name: Update types run: | bundle exec rbs collection update From 5edcd2d3226ef84f718d8f68990fd488a44fe366 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 2 Jan 2026 11:44:49 -0500 Subject: [PATCH 789/930] Add another use of stdlib dependencies in RBS --- .github/workflows/rspec.yml | 1 - lib/solargraph/doc_map.rb | 14 +- rbs/fills/rubygems/0/dependency.rbs | 193 ++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 rbs/fills/rubygems/0/dependency.rbs diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index ae507e2a5..72b4a08cc 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -51,7 +51,6 @@ jobs: rbs-version: '3.8.1' - ruby-version: 'head' rbs-version: '4.0.0.dev.4' - - ruby-version: 'head' # TEMP include: - ruby-version: '3.0' rbs-version: '3.6.1' diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 7dd7b9a3c..891bbabb3 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -240,11 +240,9 @@ def fetch_dependencies gemspec, out: nil Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}" dep = Gem.loaded_specs[spec.name] # @todo is next line necessary? - # @sg-ignore Unresolved call to requirement on Gem::Dependency dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement) deps.merge fetch_dependencies(dep) if deps.add?(dep) rescue Gem::MissingSpecError - # @sg-ignore Unresolved call to requirement on Gem::Dependency Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found in RubyGems." end.to_a end @@ -252,7 +250,17 @@ def fetch_dependencies gemspec, out: nil # @param gemspec [Gem::Specification] # @return [Array] def only_runtime_dependencies gemspec - gemspec.dependencies - gemspec.development_dependencies + gemspec_deps = gemspec.dependencies - gemspec.development_dependencies + stdlib_dep_names = workspace.stdlib_dependencies(gemspec.name) + stdlib_deps = workspace.stdlib_dependencies(gemspec.name).flat_map do |dep_name| + # already know about this dependency + next [] if gemspec_deps.any? { |dep| dep.name == dep_name } + + stdlib_specs = resolve_path_to_gemspecs(dep_name) || [] + + stdlib_specs.map { |spec| Gem::Dependency.new spec.name, "= #{spec.version}" } + end + gemspec_deps + stdlib_deps end diff --git a/rbs/fills/rubygems/0/dependency.rbs b/rbs/fills/rubygems/0/dependency.rbs new file mode 100644 index 000000000..db572b80d --- /dev/null +++ b/rbs/fills/rubygems/0/dependency.rbs @@ -0,0 +1,193 @@ +# +# The Dependency class holds a Gem name and a Gem::Requirement. +# +class Gem::Dependency + @name: untyped + + @requirement: untyped + + @type: untyped + + @prerelease: untyped + + @version_requirements: untyped + + @version_requirement: untyped + + # + # Valid dependency types. + # + TYPES: ::Array[:development | :runtime] + + # + # Dependency name or regular expression. + # + attr_accessor name: untyped + + # + # Allows you to force this dependency to be a prerelease. + # + attr_writer prerelease: untyped + + # + # Constructs a dependency with `name` and `requirements`. The last argument can + # optionally be the dependency type, which defaults to `:runtime`. + # + def initialize: (untyped name, *untyped requirements) -> void + + def hash: () -> untyped + + def inspect: () -> untyped + + # + # Does this dependency require a prerelease? + # + def prerelease?: () -> untyped + + # + # Is this dependency simply asking for the latest version of a gem? + # + def latest_version?: () -> untyped + + def pretty_print: (untyped q) -> untyped + + # + # What does this dependency require? + # + def requirement: () -> untyped + + # + # + def requirements_list: () -> untyped + + def to_s: () -> ::String + + # + # Dependency type. + # + def type: () -> untyped + + # + # + def runtime?: () -> untyped + + def ==: (untyped other) -> untyped + + # + # Dependencies are ordered by name. + # + def <=>: (untyped other) -> untyped + + # + # Uses this dependency as a pattern to compare to `other`. This dependency will + # match if the name matches the other's name, and other has only an equal + # version requirement that satisfies this dependency. + # + def =~: (untyped other) -> (nil | false | untyped) + + # + # + alias === =~ + + # + # Does this dependency match the specification described by `name` and `version` + # or match `spec`? + # + # NOTE: Unlike #matches_spec? this method does not return true when the version + # is a prerelease version unless this is a prerelease dependency. + # + def match?: (untyped obj, ?untyped? version, ?bool allow_prerelease) -> (false | true | untyped) + + # + # Does this dependency match `spec`? + # + # NOTE: This is not a convenience method. Unlike #match? this method returns + # true when `spec` is a prerelease version even if this dependency is not a + # prerelease dependency. + # + def matches_spec?: (untyped spec) -> (false | true | untyped) + + # + # Merges the requirements of `other` into this dependency + # + def merge: (untyped other) -> untyped + + # + # + def matching_specs: (?bool platform_only) -> untyped + + # + # True if the dependency will not always match the latest version. + # + def specific?: () -> untyped + + # + # + def to_specs: () -> untyped + + # + # + def to_spec: () -> untyped + + # + # + def identity: () -> (:complete | :abs_latest | :latest | :released) + + def encode_with: (untyped coder) -> untyped +end From 8320f5b5cfc28d6d60e472e90db56edf0ee4ff02 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 2 Jan 2026 11:54:39 -0500 Subject: [PATCH 790/930] Mock additional call --- spec/doc_map_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 376e7d9bd..1e50301f4 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -64,7 +64,7 @@ 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(workspace).to receive_messages(stdlib_dependencies: [], global_environ: 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 From 6730e8f50792809893f29cbd5c321764db7c9a3a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 2 Jan 2026 12:12:44 -0500 Subject: [PATCH 791/930] Fix annotations --- .../language_server/message/extended/check_gem_version.rb | 1 - lib/solargraph/parser/parser_gem/class_methods.rb | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) 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 ead1eeaf2..0447a7feb 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,6 @@ def available @fetched = true begin @available ||= begin - # @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/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index 2daf22fc7..b3caa900a 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -30,7 +30,9 @@ def parse code, filename = nil, line = 0 # @return [::Parser::Base] def parser @parser ||= Prism::Translation::Parser.new(FlawedBuilder.new).tap do |parser| + # @sg-ignore Unresolved call to diagnostics on Prism::Translation::Parser parser.diagnostics.all_errors_are_fatal = true + # @sg-ignore Unresolved call to diagnostics on Prism::Translation::Parser parser.diagnostics.ignore_warnings = true end end From 9a88f53a33107d63da42145db4ef227ad5a9c3de Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 2 Jan 2026 12:16:49 -0500 Subject: [PATCH 792/930] Update types in rspec undercover --- .github/workflows/rspec.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 72b4a08cc..d9473bd3e 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -90,8 +90,7 @@ jobs: bundle _2.5.23_ install bundle update rbs # use latest available for this Ruby version - name: Update types - run: | - bundle exec rbs collection update + run: bundle exec rbs collection update - name: Run tests run: bundle exec rake spec undercover: @@ -106,9 +105,15 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: '3.4' + # see https://github.com/castwide/solargraph/actions/runs/19391419903/job/55485410493?pr=1119 + # + # match version in Gemfile.lock and use same version below + bundler: 2.5.23 bundler-cache: false - name: Install gems run: bundle install + - name: Update types + run: bundle exec rbs collection update - name: Run tests run: bundle exec rake spec - name: Check PR coverage From 00c2947c9b137026dfb3c97e4b86866d1fc45106 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 2 Jan 2026 14:25:55 -0500 Subject: [PATCH 793/930] Debug --- .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 3602da38d..5555617c5 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -122,7 +122,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.4 + ruby-version: '3.1' bundler-cache: false - name: Install gems run: | From 5df17d5645e689a044458428f70ba9389c287a85 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 2 Jan 2026 14:31:23 -0500 Subject: [PATCH 794/930] Debug --- .github/workflows/plugins.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 5555617c5..3f34cbae1 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -123,6 +123,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: '3.1' + rubygems: latest bundler-cache: false - name: Install gems run: | From c22e3105184644b6066afe536502e0c6adca3509 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 2 Jan 2026 16:43:17 -0500 Subject: [PATCH 795/930] Drop incorrect rbs collection use in spec --- .github/workflows/plugins.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 3f34cbae1..edaebe3eb 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -150,10 +150,12 @@ jobs: run: | cd ../solargraph-rspec cp .solargraph.yml.example .solargraph.yml - - name: Solargraph generate RSpec gems YARD and RBS pins + - name: Solargraph generate RSpec gems YARD pins run: | cd ../solargraph-rspec - bundle exec appraisal rbs collection update + # solargraph-rspec's specs don't pass a workspace, so it + # doesn't know where to look for the RBS collection - let's + # not load one so that the solargraph gems command below works rspec_gems=$(bundle exec appraisal ruby -r './lib/solargraph-rspec' -e 'puts Solargraph::Rspec::Gems.gem_names.join(" ")' 2>/dev/null | tail -n1) bundle exec appraisal solargraph gems $rspec_gems - name: Run specs From 0d6b68b01c70fd72582c2bfdf4dfef44998fa501 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 2 Jan 2026 17:23:43 -0500 Subject: [PATCH 796/930] Update rubocop todo --- .rubocop_todo.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3e8845df2..6406cc522 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -106,7 +106,6 @@ Layout/EndOfLine: Exclude: - 'Gemfile' - 'Rakefile' - - 'lib/solargraph/source/encoding_fixes.rb' - 'solargraph.gemspec' # This cop supports safe autocorrection (--autocorrect). @@ -575,7 +574,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' @@ -1174,7 +1172,6 @@ Style/StringLiterals: # This cop supports safe autocorrection (--autocorrect). Style/SuperArguments: Exclude: - - 'lib/solargraph/pin/base_variable.rb' - 'lib/solargraph/pin/callable.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/signature.rb' From d5d619e3445a166462de76a4f564877bec1556c4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 2 Jan 2026 19:42:53 -0500 Subject: [PATCH 797/930] Revert change --- lib/solargraph/yard_map/mapper.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/yard_map/mapper.rb b/lib/solargraph/yard_map/mapper.rb index d9cf90a5f..74584d603 100644 --- a/lib/solargraph/yard_map/mapper.rb +++ b/lib/solargraph/yard_map/mapper.rb @@ -43,7 +43,8 @@ def generate_pins code_object # hack. If the superclass is a Proxy that can't be # resolved', it is assumed to be undefined in its yardoc # and converted to a fully qualified namespace. - superclass = if code_object.superclass.is_a?(YARD::CodeObjects::Proxy) && code_object.type == :proxy + # + superclass = if code_object.superclass.is_a?(YARD::CodeObjects::Proxy) "::#{code_object.superclass}" else code_object.superclass.to_s From 8c42ebe941a62e359133323805eedbdb301993dc Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 2 Jan 2026 21:05:21 -0500 Subject: [PATCH 798/930] Fix RuboCop issue --- lib/solargraph/workspace.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index a0fc52993..4454600f1 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -72,11 +72,11 @@ def merge *sources includes_any = false sources.each do |source| # @sg-ignore Need to add nil check here - if directory == "*" || config.calculated.include?(source.filename) - # @sg-ignore Need to add nil check here - source_hash[source.filename] = source - includes_any = true - end + next unless directory == "*" || config.calculated.include?(source.filename) + + # @sg-ignore Need to add nil check here + source_hash[source.filename] = source + includes_any = true end includes_any From 552e0240a023ac5392dda0edd2556a417b6d4a45 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 2 Jan 2026 21:10:56 -0500 Subject: [PATCH 799/930] Fix annotations --- lib/solargraph/library.rb | 4 ++++ lib/solargraph/rbs_map.rb | 2 ++ 2 files changed, 6 insertions(+) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 49c3262d7..08a09603a 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -58,8 +58,11 @@ def synchronized? # @param source [Source, nil] # @return [void] def attach source + # @sg-ignore Need to add nil check here if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename) + # @sg-ignore Need to add nil check here source_map_hash.delete @current.filename + # @sg-ignore Need to add nil check here source_map_external_require_hash.delete @current.filename @external_requires = nil end @@ -448,6 +451,7 @@ def bench source_maps: source_map_hash.values, workspace: workspace, external_requires: external_requires, + # @sg-ignore Need to add nil check here live_map: @current ? source_map_hash[@current.filename] : nil ) end diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index 94bf65b50..ff330c0e8 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -160,7 +160,9 @@ def add_library loader, library, version end # @return [String] + # @sg-ignore Need to add nil check here def short_name + # @sg-ignore Need to add nil check here self.class.name.split('::').last end end From 0f6862b432692354b7c10ddc489d1a2900b5ecf4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 2 Jan 2026 21:27:06 -0500 Subject: [PATCH 800/930] Fix annotations --- lib/solargraph/rbs_map.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index ff330c0e8..94bf65b50 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -160,9 +160,7 @@ def add_library loader, library, version end # @return [String] - # @sg-ignore Need to add nil check here def short_name - # @sg-ignore Need to add nil check here self.class.name.split('::').last end end From 49b63382378005fe06cc95465a9bb3c087c7a9a6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 3 Jan 2026 09:08:59 -0500 Subject: [PATCH 801/930] RuboCop fix --- lib/solargraph/workspace.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index a0fc52993..4454600f1 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -72,11 +72,11 @@ def merge *sources includes_any = false sources.each do |source| # @sg-ignore Need to add nil check here - if directory == "*" || config.calculated.include?(source.filename) - # @sg-ignore Need to add nil check here - source_hash[source.filename] = source - includes_any = true - end + next unless directory == "*" || config.calculated.include?(source.filename) + + # @sg-ignore Need to add nil check here + source_hash[source.filename] = source + includes_any = true end includes_any From 40a915f62c0d2a0c885ace0981ab02003bdb789e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 3 Jan 2026 10:36:09 -0500 Subject: [PATCH 802/930] Use "type arity" to guide signature combination --- lib/solargraph/pin/callable.rb | 18 +++++- lib/solargraph/pin/method.rb | 99 ++++++++++++++++++++++----------- lib/solargraph/pin/parameter.rb | 5 ++ 3 files changed, 87 insertions(+), 35 deletions(-) diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index bfce92c24..ed1c2d7c9 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -89,11 +89,25 @@ def blockless_parameters end end - # @return [Array] + # e.g., [["T"], "", "?", "foo:"] - parameter arity declarations, + # ignoring positional names. Used to match signatures. + # + # @return [Array, String>] def arity [generics, blockless_parameters.map(&:arity_decl), block&.arity] end + # e.g., [["T"], "1", "?3", "foo:5"] - parameter arity + # declarations, including the number of unique types in each + # parameter. Used to determine whether combining two + # signatures has lost useful information mapping specific + # parameter types to specific return types. + # + # @return [Array, String>] + def type_arity + [generics, blockless_parameters.map(&:type_arity_decl), block&.arity] + end + # @param generics_to_resolve [Enumerable] # @param arg_types [Array, nil] # @param return_type_context [ComplexType, nil] @@ -101,6 +115,7 @@ def arity # @param yield_return_type_context [ComplexType, nil] # @param context [ComplexType, nil] # @param resolved_generic_values [Hash{String => ComplexType}] + # # @return [self] def resolve_generics_from_context(generics_to_resolve, arg_types = nil, @@ -150,6 +165,7 @@ def method_name # @param yield_return_type_context [ComplexType, nil] # @param context [ComplexType, nil] # @param resolved_generic_values [Hash{String => ComplexType}] + # # @return [self] def resolve_generics_from_context_until_complete(generics_to_resolve, arg_types = nil, diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 30afb8f48..a60b455d3 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -34,26 +34,6 @@ def initialize visibility: :public, explicit: true, block: :undefined, node: nil @anon_splat = anon_splat end - # @param signature_pins [Array] - # @return [Array] - def combine_all_signature_pins(*signature_pins) - # @type [Hash{String => Array}] - by_rbs = {} - signature_pins.each do |signature_pin| - by_rbs[signature_pin.parameters_to_rbs] ||= [] - by_rbs[signature_pin.parameters_to_rbs] << signature_pin - end - by_rbs.transform_values! do |same_rbs_pins| - # @param memo [Pin::Signature, nil] - # @param signature [Pin::Signature] - same_rbs_pins.reduce(nil) do |memo, signature| - next signature if memo.nil? - memo.combine_with(signature) - end - end - by_rbs.values.flatten - end - # @param other [Pin::Method] # @return [::Symbol] def combine_visibility(other) @@ -66,20 +46,6 @@ def combine_visibility(other) end end - # @param other [Pin::Method] - # @return [Array] - def combine_signatures(other) - all_undefined = signatures.all? { |sig| sig.return_type.undefined? } - other_all_undefined = other.signatures.all? { |sig| sig.return_type.undefined? } - if all_undefined && !other_all_undefined - other.signatures - elsif other_all_undefined && !all_undefined - signatures - else - combine_all_signature_pins(*signatures, *other.signatures) - end - end - def combine_with(other, attrs = {}) priority_choice = choose_priority(other) return priority_choice unless priority_choice.nil? @@ -485,6 +451,71 @@ def dodgy_visibility_source? private + # @param other [Pin::Method] + # @return [Array] + def combine_signatures(other) + all_undefined = signatures.all? { |sig| !sig.return_type&.defined? } + other_all_undefined = other.signatures.all? { |sig| !sig.return_type&.defined? } + if all_undefined && !other_all_undefined + other.signatures + elsif other_all_undefined && !all_undefined + signatures + else + combine_signatures_by_type_arity(*signatures, *other.signatures) + end + end + + # @param signature_pins [Array] + # + # @return [Array] + def combine_signatures_by_type_arity(*signature_pins) + # @type [Hash{Array => Array}] + by_type_arity = {} + signature_pins.each do |signature_pin| + by_type_arity[signature_pin.type_arity] ||= [] + by_type_arity[signature_pin.type_arity] << signature_pin + end + + by_type_arity.transform_values! do |same_type_arity_signatures| + combine_same_type_arity_signatures same_type_arity_signatures + end + by_type_arity.values.flatten + end + + # @param same_type_arity_signatures [Array] + # + # @return [Array] + def combine_same_type_arity_signatures(same_type_arity_signatures) + # This is an O(n^2) operation, so bail out if n is not small + return same_type_arity_signatures if same_type_arity_signatures.length > 10 + + # @param old_signatures [Array] + # @param new_signature [Pin::Signature] + same_type_arity_signatures.reduce([]) do |old_signatures, new_signature| + next [new_signature] if old_signatures.empty? + + found_merge = false + old_signatures.flat_map do |old_signature| + potential_new_signature = old_signature.combine_with(new_signature) + + if potential_new_signature.type_arity == old_signature.type_arity + # the number of types in each parameter and return type + # match, so we found compatible signatures to merge. If + # we increased the number of types, we'd potentially + # have taken away the ability to use parameter types to + # choose the correct return type (while Ruby doesn't + # dispatch based on type, RBS does distinguish overloads + # based on types, not just arity, allowing for type + # information describing how methods behave based on + # their input types) + old_signatures - [old_signature] + [potential_new_signature] + else + old_signatures + [new_signature] + end + end + end + end + # @param name [String] # @param asgn [Boolean] # diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 91c205921..ebcf7727f 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -72,6 +72,11 @@ def arity_decl end end + # @return [String] + def type_arity_decl + arity_decl + return_type.items.count.to_s + end + def arg? decl == :arg end From 11bf98728e3e4a6e8456521ef074cd47a8ee7ae2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 3 Jan 2026 10:41:42 -0500 Subject: [PATCH 803/930] 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 962ac9bb6..f811c2102 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -468,6 +468,7 @@ Metrics/ClassLength: Exclude: - 'lib/solargraph/api_map.rb' - 'lib/solargraph/language_server/host.rb' + - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/rbs_map/conversions.rb' - 'lib/solargraph/type_checker.rb' @@ -1175,7 +1176,6 @@ Style/StringLiterals: # This cop supports safe autocorrection (--autocorrect). Style/SuperArguments: Exclude: - - 'lib/solargraph/pin/base_variable.rb' - 'lib/solargraph/pin/callable.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/signature.rb' From 72131e89c0cdccdb2470107f6fa0c42037294f75 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 3 Jan 2026 11:02:40 -0500 Subject: [PATCH 804/930] Include return type arity in comparison --- lib/solargraph/pin/callable.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index ed1c2d7c9..de5869073 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -103,9 +103,16 @@ def arity # signatures has lost useful information mapping specific # parameter types to specific return types. # - # @return [Array, String>] + # @return [Array] def type_arity - [generics, blockless_parameters.map(&:type_arity_decl), block&.arity] + [generics, blockless_parameters.map(&:type_arity_decl), block&.type_arity] + end + + # Same as type_arity, but includes return type arity at the front. + # + # @return [Array] + def full_type_arity + [return_type&.items.count.to_s] + type_arity end # @param generics_to_resolve [Enumerable] From 334f4e06f86a814a5587a2abf62d54bc678dbb79 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 3 Jan 2026 11:46:36 -0500 Subject: [PATCH 805/930] Add dodgy return type --- lib/solargraph/pin/base.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 57d083453..3f9d95331 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -205,7 +205,9 @@ def combine_return_type(other) def dodgy_return_type_source? # uses a lot of 'Object' instead of 'self' - location&.filename&.include?('core_ext/object/') + location&.filename&.include?('core_ext/object/') || + # ditto + location&.filename&.include?('stdlib/date/0/date.rbs') end # when choices are arbitrary, make sure the choice is consistent From a2e59646a079dcafb6d54f493b61ccdfa232cc50 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 3 Jan 2026 17:00:35 -0500 Subject: [PATCH 806/930] Fix RuboCop issue --- lib/solargraph/pin/callable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index de5869073..8f247205c 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -112,7 +112,7 @@ def type_arity # # @return [Array] def full_type_arity - [return_type&.items.count.to_s] + type_arity + [return_type ? return_type.items.count.to_s : nil] + type_arity end # @param generics_to_resolve [Enumerable] From 0ac3cb444c962beea001daca71f64ab07650a681 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 4 Jan 2026 12:08:44 -0500 Subject: [PATCH 807/930] Add Ruby 4.0 jobs --- .github/workflows/rspec.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index f761b61aa..b9848b7ab 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -35,8 +35,6 @@ jobs: - ruby-version: '3.3' # only include the 3.4 variants we include later - ruby-version: '3.4' - # only include the 4.0 variants we include later - - ruby-version: '4.0' # Don't exclude 'head' - let's test all RBS versions we # can there. # @@ -64,8 +62,6 @@ jobs: rbs-version: '3.10.0' - ruby-version: '3.4' rbs-version: '3.10.0' - - ruby-version: '3.4' - rbs-version: '4.0.0.dev.4' steps: - uses: actions/checkout@v3 - name: Set up Ruby From 3e3e4d9ea08b46da2c050c43376384ff52777dd0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 4 Jan 2026 12:56:49 -0500 Subject: [PATCH 808/930] Exclude another combo --- .github/workflows/rspec.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index b9848b7ab..a5af74830 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -43,6 +43,8 @@ jobs: # work around: # # https://github.com/castwide/solargraph/actions/runs/20627923548/job/59241444380?pr=1102 + - ruby-version: '4.0' + rbs-version: '4.0.0.dev.4' - ruby-version: 'head' rbs-version: '3.6.1' - ruby-version: 'head' From c7eefc29b1a4768049ea59c521c147a7288ec42f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 4 Jan 2026 13:06:09 -0500 Subject: [PATCH 809/930] Exclude another combo --- .github/workflows/rspec.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index a5af74830..25ab551b2 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -35,6 +35,8 @@ jobs: - ruby-version: '3.3' # only include the 3.4 variants we include later - ruby-version: '3.4' + # only include the 4.0 variants we include later + - ruby-version: '4.0' # Don't exclude 'head' - let's test all RBS versions we # can there. # @@ -43,8 +45,6 @@ jobs: # work around: # # https://github.com/castwide/solargraph/actions/runs/20627923548/job/59241444380?pr=1102 - - ruby-version: '4.0' - rbs-version: '4.0.0.dev.4' - ruby-version: 'head' rbs-version: '3.6.1' - ruby-version: 'head' @@ -63,6 +63,8 @@ jobs: - ruby-version: '3.3' rbs-version: '3.10.0' - ruby-version: '3.4' + rbs-version: '4.0.0.dev.4' + - ruby-version: '4.0' rbs-version: '3.10.0' steps: - uses: actions/checkout@v3 From 6e6e85d9bec7ab7730aca9d477bd740a732dece3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 4 Jan 2026 15:12:59 -0500 Subject: [PATCH 810/930] Update rules to use report? --- lib/solargraph/type_checker/rules.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index 0f136301e..dd341caf2 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -56,10 +56,6 @@ def must_tag_or_infer? report?(:must_tag_or_infer, :strict) end - def require_all_unique_types_match_expected_on_lhs? - rank >= LEVELS[:alpha] - end - def validate_tags? report?(:validate_tags, :typed) end @@ -72,6 +68,10 @@ def require_all_unique_types_match_declared? report?(:require_all_unique_types_match_declared, :alpha) end + def require_all_unique_types_match_expected_on_lhs? + report?(:require_all_unique_types_match_expected_on_lhs, :alpha) + end + def require_no_undefined_args? report?(:require_no_undefined_args, :alpha) end From 79a2b2a7d8ba58d307854d85a96b1f2a45be9444 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 4 Jan 2026 15:18:18 -0500 Subject: [PATCH 811/930] Fix merge --- spec/type_checker/levels/strong_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 41344b8ac..b333529d2 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -168,6 +168,21 @@ def bar 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 60df579ceb8a12c141fd957d69e8b3380155f8db Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 4 Jan 2026 15:21:17 -0500 Subject: [PATCH 812/930] Fix merge --- lib/solargraph/type_checker.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 63a0796fc..167ee500d 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -20,12 +20,10 @@ class TypeChecker # @param filename [String, nil] # @param api_map [ApiMap, nil] - # @param rules [Rules] Type checker rules object # @param level [Symbol] Don't complain about anything above this level # @param workspace [Workspace, nil] Workspace to use for loading # type checker rules modified by user config - # @param type_checker_rules [Hash{Symbol => Symbol}] Overrides for - # type checker rules - e.g., :report_undefined => :strong + # @param rules [Rules] Type checker rules object def initialize filename, api_map: nil, level: :normal, From caa81d466d8f9414951ce13bdb29d9687e5c913f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 4 Jan 2026 19:09:23 -0500 Subject: [PATCH 813/930] Drop dead code --- lib/solargraph/api_map.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 04cfed55d..6c145e618 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -23,9 +23,6 @@ class ApiMap # @return [Array] attr_reader :missing_docs - # @return [Solargraph::Workspace::Gemspecs] - attr_reader :gemspecs - # @param pins [Array] def initialize pins: [] @source_map_hash = {} From 4c6a84e2b532c0ae5e3b3805849aeaeae08f223f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 5 Jan 2026 19:01:33 -0500 Subject: [PATCH 814/930] Bump version to 0.59.0.dev.1 --- lib/solargraph/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/version.rb b/lib/solargraph/version.rb index 5bb8e52f8..b98600a95 100755 --- a/lib/solargraph/version.rb +++ b/lib/solargraph/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Solargraph - VERSION = '0.58.1' + VERSION = '0.59.0.dev.1' end From 494b2957887751fbaff156a608e5367b1f5c3693 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 5 Jan 2026 19:07:58 -0500 Subject: [PATCH 815/930] Rename rule --- lib/solargraph/shell.rb | 2 +- lib/solargraph/type_checker.rb | 6 +++--- lib/solargraph/type_checker/rules.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 7c7bcc248..de5567394 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -179,7 +179,7 @@ def typecheck *files api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout, loose_unions: - !rules.require_all_unique_types_match_expected_on_lhs?) + !rules.require_all_unique_types_support_call?) probcount = 0 if files.empty? files = api_map.source_maps.map(&:filename) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 167ee500d..2ff6140a5 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -32,7 +32,7 @@ def initialize filename, @filename = filename # @todo Smarter directory resolution @api_map = api_map || Solargraph::ApiMap.load(File.dirname(filename), - loose_unions: !rules.require_all_unique_types_match_expected_on_lhs?) + loose_unions: !rules.require_all_unique_types_support_call?) @rules = rules # @type [Array] @marked_ranges = [] @@ -101,7 +101,7 @@ def load filename, level = :normal source = Solargraph::Source.load(filename) rules = Rules.new(level, {}) api_map = Solargraph::ApiMap.new(loose_unions: - !rules.require_all_unique_types_match_expected_on_lhs?) + !rules.require_all_unique_types_support_call?) api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end @@ -115,7 +115,7 @@ def load_string code, filename = nil, level = :normal, api_map: nil source = Solargraph::Source.load_string(code, filename) rules = Rules.new(level, {}) api_map ||= Solargraph::ApiMap.new(loose_unions: - !rules.require_all_unique_types_match_expected_on_lhs?) + !rules.require_all_unique_types_support_call?) api_map.map(source) new(filename, api_map: api_map, level: level, rules: rules) end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index dd341caf2..bc6826421 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -68,8 +68,8 @@ def require_all_unique_types_match_declared? report?(:require_all_unique_types_match_declared, :alpha) end - def require_all_unique_types_match_expected_on_lhs? - report?(:require_all_unique_types_match_expected_on_lhs, :alpha) + def require_all_unique_types_support_call? + report?(:require_all_unique_types_support_call, :alpha) end def require_no_undefined_args? From 0ccfcb0cfeb8053e616714faec492f1a79dce5d1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 5 Jan 2026 20:54:23 -0500 Subject: [PATCH 816/930] Update RuboCop todo file --- .rubocop_todo.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7ed4a62b1..e8e361e57 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -106,7 +106,6 @@ Layout/EndOfLine: Exclude: - 'Gemfile' - 'Rakefile' - - 'lib/solargraph/source/encoding_fixes.rb' - 'solargraph.gemspec' # This cop supports safe autocorrection (--autocorrect). @@ -484,7 +483,6 @@ Metrics/ParameterLists: - 'lib/solargraph/pin/callable.rb' - 'lib/solargraph/type_checker.rb' - 'lib/solargraph/yard_map/mapper/to_method.rb' - - 'lib/solargraph/yard_map/to_method.rb' # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/PerceivedComplexity: From c8f4751f3b01c520b4c4fc5e4eae98675552c187 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 5 Jan 2026 20:56:35 -0500 Subject: [PATCH 817/930] Update RuboCop todo file --- .rubocop_todo.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b949ae2da..53776bdc0 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -107,7 +107,6 @@ Layout/EndOfLine: Exclude: - 'Gemfile' - 'Rakefile' - - 'lib/solargraph/source/encoding_fixes.rb' - 'solargraph.gemspec' # This cop supports safe autocorrection (--autocorrect). @@ -1174,7 +1173,6 @@ Style/StringLiterals: # This cop supports safe autocorrection (--autocorrect). Style/SuperArguments: Exclude: - - 'lib/solargraph/pin/base_variable.rb' - 'lib/solargraph/pin/callable.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/signature.rb' From 4f16771b4cd69100672936b4bf4a663f64954bcd Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 5 Jan 2026 21:22:15 -0500 Subject: [PATCH 818/930] Ratchet rubocop TODO file --- .rubocop_todo.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 95dbfc1da..c907fdf5d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -107,7 +107,6 @@ Layout/EndOfLine: Exclude: - 'Gemfile' - 'Rakefile' - - 'lib/solargraph/source/encoding_fixes.rb' - 'solargraph.gemspec' # This cop supports safe autocorrection (--autocorrect). @@ -1178,7 +1177,6 @@ Style/StringLiterals: # This cop supports safe autocorrection (--autocorrect). Style/SuperArguments: Exclude: - - 'lib/solargraph/pin/base_variable.rb' - 'lib/solargraph/pin/callable.rb' - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/signature.rb' From 7ab85147dd588146c0ffed047daff15003fe12f8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 5 Jan 2026 21:57:12 -0500 Subject: [PATCH 819/930] Move to skip: --- spec/workspace/gemspecs_resolve_require_spec.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb index 4ffcd0bd1..64a4293db 100644 --- a/spec/workspace/gemspecs_resolve_require_spec.rb +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -219,9 +219,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', skip: 'needs improved logic for require lookups' do expect(specs&.map(&:name)).to include('bundler') end end From 3f1d1e5179354140d1755fdd566e8d232faf1283 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 5 Jan 2026 22:23:48 -0500 Subject: [PATCH 820/930] Mark spec as pending --- spec/api_map/index_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/api_map/index_spec.rb b/spec/api_map/index_spec.rb index 8afb74759..689d5e007 100644 --- a/spec/api_map/index_spec.rb +++ b/spec/api_map/index_spec.rb @@ -56,6 +56,8 @@ end it 'overrides #initialize method in signature' do + pending 'initialize fix' + 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') From 71f609c7000500ab07b205f33a40a6819048dcf1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 5 Jan 2026 22:36:54 -0500 Subject: [PATCH 821/930] Revert spec change --- spec/convention/gemfile_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/convention/gemfile_spec.rb b/spec/convention/gemfile_spec.rb index ab6a5ef1b..827da7993 100644 --- a/spec/convention/gemfile_spec.rb +++ b/spec/convention/gemfile_spec.rb @@ -24,7 +24,7 @@ def type_checker code it 'finds bad arguments to DSL methods' do checker = type_checker(%( - source 123 + source File gemspec bad_name: 'solargraph' @@ -35,7 +35,7 @@ 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 123'].sort) + 'Wrong argument type for Bundler::Dsl#source: source expected String, received Class'].sort) end it 'finds bad arguments to DSL ruby method' do From ba4f63eb2a8ee3d95f007d2cb93ea89672ee01b1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 5 Jan 2026 22:49:05 -0500 Subject: [PATCH 822/930] Drop old workaround --- .github/workflows/plugins.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index a12711e40..c7ad72cb4 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -55,8 +55,6 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: 3.4 - # See https://github.com/castwide/solargraph/actions/runs/19000135777/job/54265647107?pr=1119 - rubygems: latest bundler-cache: false - uses: awalsh128/cache-apt-pkgs-action@latest with: From 3296a3cea121af43d185620ce8334f6b5a0545a7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 6 Jan 2026 19:25:38 -0500 Subject: [PATCH 823/930] Fix merge --- lib/solargraph/complex_type.rb | 4 ++-- lib/solargraph/complex_type/unique_type.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 9967dd2e4..8af3632d4 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -368,9 +368,9 @@ def intersect_with intersection_type, api_map items.each do |ut| intersection_type.each do |int_type| if ut.conforms_to?(api_map, int_type, :assignment) - types << int_type - elsif int_type.conforms_to?(api_map, ut, :assignment) types << ut + elsif int_type.conforms_to?(api_map, ut, :assignment) + types << int_type end end end diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index feba4d7b0..36ef8d3fb 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -136,9 +136,9 @@ def intersect_with intersection_type, api_map items.each do |ut| intersection_type.each do |int_type| if ut.conforms_to?(api_map, int_type, :assignment) - types << int_type - elsif int_type.conforms_to?(api_map, ut, :assignment) types << ut + elsif int_type.conforms_to?(api_map, ut, :assignment) + types << int_type end end end From f376a9bc7d1a5c1f7e6b43c374874cca801face8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 6 Jan 2026 22:57:05 -0500 Subject: [PATCH 824/930] Fix typechecking issues --- lib/solargraph/workspace/gemspecs.rb | 2 -- sig/shims/ast/0/node.rbs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index f832cf57f..8db13a491 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -72,11 +72,9 @@ def fetch_dependencies gemspec, out: $stderr Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}" dep = Gem.loaded_specs[spec.name] # @todo is next line necessary? - # @sg-ignore Unresolved call to requirement on Gem::Dependency dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement) deps.merge fetch_dependencies(dep) if deps.add?(dep) rescue Gem::MissingSpecError - # @sg-ignore Unresolved call to requirement on Gem::Dependency Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for " \ "#{gemspec.name} not found in RubyGems." end.to_a diff --git a/sig/shims/ast/0/node.rbs b/sig/shims/ast/0/node.rbs index fab1a4de0..a9d8e06e1 100644 --- a/sig/shims/ast/0/node.rbs +++ b/sig/shims/ast/0/node.rbs @@ -1,5 +1,5 @@ module ::AST class Node - def children: () -> [self, Integer, String, Symbol, nil] + def children: () -> Array[self | Integer | String | Symbol | nil] end end From 3c947d299df4adc9dc0943ecdb2b796dd201446b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 7 Jan 2026 08:04:06 -0500 Subject: [PATCH 825/930] Revert doc --- lib/solargraph/yard_map/mapper.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/yard_map/mapper.rb b/lib/solargraph/yard_map/mapper.rb index 74584d603..592b3805e 100644 --- a/lib/solargraph/yard_map/mapper.rb +++ b/lib/solargraph/yard_map/mapper.rb @@ -39,11 +39,9 @@ def generate_pins code_object @namespace_pins[code_object.path] = nspin result.push nspin if code_object.is_a?(YARD::CodeObjects::ClassObject) and !code_object.superclass.nil? - # This method of superclass detection is a bit of a - # hack. If the superclass is a Proxy that can't be - # resolved', it is assumed to be undefined in its yardoc - # and converted to a fully qualified namespace. - # + # This method of superclass detection is a bit of a hack. If + # the superclass is a Proxy, it is assumed to be undefined in its + # yardoc and converted to a fully qualified namespace. superclass = if code_object.superclass.is_a?(YARD::CodeObjects::Proxy) "::#{code_object.superclass}" else From 19b28ada7e4a12c4634c2c4a576134c7b1af317f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 7 Jan 2026 08:07:09 -0500 Subject: [PATCH 826/930] Fix spelling --- lib/solargraph/pin/parameter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 08b371129..e4d5b474a 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -206,7 +206,7 @@ def param_tag def typify_block_param api_map block_pin = closure if block_pin.is_a?(Pin::Block) && block_pin.receiver && index - # @sg-ignore flow-sensivie typing should handle is_a? with && + # @sg-ignore flow-sensitive typing should handle is_a? with && return block_pin.typify_parameters(api_map)[index] end ComplexType::UNDEFINED From 6b5d2c3204e4ea29278ecf510b3965545e708eec Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 7 Jan 2026 08:10:18 -0500 Subject: [PATCH 827/930] Fix merge issue --- lib/solargraph/shell.rb | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 42a027144..d6420a26e 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -350,25 +350,5 @@ def print_pin(pin) puts pin.inspect end 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 b826d83af8a652c2dd796336399c443091bc2826 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 7 Jan 2026 19:00:59 -0500 Subject: [PATCH 828/930] Exclude the current gemspec from pins brought in from gem --- lib/solargraph/workspace/gemspecs.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index fd8a9f1d6..6f8e3166d 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -232,7 +232,7 @@ def all_gemspecs_from_this_bundle if specish_objects.first.respond_to?(:materialize_for_installation) specish_objects = specish_objects.map(&:materialize_for_installation) end - specish_objects.map do |specish| + all_gemspecs = 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 @@ -240,6 +240,13 @@ def all_gemspecs_from_this_bundle to_gem_specification(specish) end end.compact + # @param gemspec [Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification] + all_gemspecs.reject do |gemspec| + gemspec.respond_to?(:source) && + gemspec.source.instance_of?(Bundler::Source::Gemspec) && + gemspec.source.respond_to?(:path) && + gemspec.source.path == '.' + end end # @return [Array] From c9520024e5b59d543ea0310dc5d95b991d2e1d34 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 7 Jan 2026 19:08:57 -0500 Subject: [PATCH 829/930] Check pathname instead --- lib/solargraph/workspace/gemspecs.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 6f8e3166d..3e473679e 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -245,7 +245,7 @@ def all_gemspecs_from_this_bundle gemspec.respond_to?(:source) && gemspec.source.instance_of?(Bundler::Source::Gemspec) && gemspec.source.respond_to?(:path) && - gemspec.source.path == '.' + gemspec.source.path == Pathname.new('.') end end From bcebe89f34b798b3ff4842517432da46ea6f8096 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 7 Jan 2026 19:48:28 -0500 Subject: [PATCH 830/930] Add sg-ignore --- lib/solargraph/parser/parser_gem/node_chainer.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index d8d46319b..d5651f3ad 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -126,6 +126,7 @@ def generate_links n end end elsif n.type == :hash + # @sg-ignore Too many arguments to Hash.new result.push Chain::Hash.new('::Hash', n, hash_is_splatted?(n)) elsif n.type == :array chained_children = n.children.map { |c| NodeChainer.chain(c) } From 267c44534e4f9bfe6889354ff75f7aa13ec131f6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 8 Jan 2026 19:51:11 -0500 Subject: [PATCH 831/930] Avoid rbs pollution We were using the sig/shims directory for some internally helpful shims; unfortunately that exported them during gem installs, causing https://github.com/castwide/solargraph/issues/1144 --- {sig => rbs}/shims/ast/0/node.rbs | 0 {sig => rbs}/shims/ast/2.4/.rbs_meta.yaml | 0 {sig => rbs}/shims/ast/2.4/ast.rbs | 0 {sig => rbs}/shims/parser/3.2.0.1/builders/default.rbs | 0 {sig => rbs}/shims/parser/3.2.0.1/manifest.yaml | 0 {sig => rbs}/shims/parser/3.2.0.1/parser.rbs | 0 {sig => rbs}/shims/parser/3.2.0.1/polyfill.rbs | 0 {sig => rbs}/shims/thor/1.2.0.1/.rbs_meta.yaml | 0 {sig => rbs}/shims/thor/1.2.0.1/manifest.yaml | 0 {sig => rbs}/shims/thor/1.2.0.1/thor.rbs | 0 rbs_collection.yaml | 2 +- 11 files changed, 1 insertion(+), 1 deletion(-) rename {sig => rbs}/shims/ast/0/node.rbs (100%) rename {sig => rbs}/shims/ast/2.4/.rbs_meta.yaml (100%) rename {sig => rbs}/shims/ast/2.4/ast.rbs (100%) rename {sig => rbs}/shims/parser/3.2.0.1/builders/default.rbs (100%) rename {sig => rbs}/shims/parser/3.2.0.1/manifest.yaml (100%) rename {sig => rbs}/shims/parser/3.2.0.1/parser.rbs (100%) rename {sig => rbs}/shims/parser/3.2.0.1/polyfill.rbs (100%) rename {sig => rbs}/shims/thor/1.2.0.1/.rbs_meta.yaml (100%) rename {sig => rbs}/shims/thor/1.2.0.1/manifest.yaml (100%) rename {sig => rbs}/shims/thor/1.2.0.1/thor.rbs (100%) diff --git a/sig/shims/ast/0/node.rbs b/rbs/shims/ast/0/node.rbs similarity index 100% rename from sig/shims/ast/0/node.rbs rename to rbs/shims/ast/0/node.rbs diff --git a/sig/shims/ast/2.4/.rbs_meta.yaml b/rbs/shims/ast/2.4/.rbs_meta.yaml similarity index 100% rename from sig/shims/ast/2.4/.rbs_meta.yaml rename to rbs/shims/ast/2.4/.rbs_meta.yaml diff --git a/sig/shims/ast/2.4/ast.rbs b/rbs/shims/ast/2.4/ast.rbs similarity index 100% rename from sig/shims/ast/2.4/ast.rbs rename to rbs/shims/ast/2.4/ast.rbs diff --git a/sig/shims/parser/3.2.0.1/builders/default.rbs b/rbs/shims/parser/3.2.0.1/builders/default.rbs similarity index 100% rename from sig/shims/parser/3.2.0.1/builders/default.rbs rename to rbs/shims/parser/3.2.0.1/builders/default.rbs diff --git a/sig/shims/parser/3.2.0.1/manifest.yaml b/rbs/shims/parser/3.2.0.1/manifest.yaml similarity index 100% rename from sig/shims/parser/3.2.0.1/manifest.yaml rename to rbs/shims/parser/3.2.0.1/manifest.yaml diff --git a/sig/shims/parser/3.2.0.1/parser.rbs b/rbs/shims/parser/3.2.0.1/parser.rbs similarity index 100% rename from sig/shims/parser/3.2.0.1/parser.rbs rename to rbs/shims/parser/3.2.0.1/parser.rbs diff --git a/sig/shims/parser/3.2.0.1/polyfill.rbs b/rbs/shims/parser/3.2.0.1/polyfill.rbs similarity index 100% rename from sig/shims/parser/3.2.0.1/polyfill.rbs rename to rbs/shims/parser/3.2.0.1/polyfill.rbs diff --git a/sig/shims/thor/1.2.0.1/.rbs_meta.yaml b/rbs/shims/thor/1.2.0.1/.rbs_meta.yaml similarity index 100% rename from sig/shims/thor/1.2.0.1/.rbs_meta.yaml rename to rbs/shims/thor/1.2.0.1/.rbs_meta.yaml diff --git a/sig/shims/thor/1.2.0.1/manifest.yaml b/rbs/shims/thor/1.2.0.1/manifest.yaml similarity index 100% rename from sig/shims/thor/1.2.0.1/manifest.yaml rename to rbs/shims/thor/1.2.0.1/manifest.yaml diff --git a/sig/shims/thor/1.2.0.1/thor.rbs b/rbs/shims/thor/1.2.0.1/thor.rbs similarity index 100% rename from sig/shims/thor/1.2.0.1/thor.rbs rename to rbs/shims/thor/1.2.0.1/thor.rbs diff --git a/rbs_collection.yaml b/rbs_collection.yaml index 898239cac..d94e1c896 100644 --- a/rbs_collection.yaml +++ b/rbs_collection.yaml @@ -2,7 +2,7 @@ sources: - type: local name: shims - path: sig/shims + path: rbs/shims - type: git name: ruby/gem_rbs_collection From a5ecc28cc3b985a2fab5b7c3165026c67e1c4d5f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 8 Jan 2026 19:57:34 -0500 Subject: [PATCH 832/930] Test with RBS 4.0.0.dev.5 --- .github/workflows/rspec.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index c82ade49b..485827130 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -22,19 +22,14 @@ jobs: strategy: matrix: ruby-version: ['3.0', '3.1', '3.2', '3.3', '3.4', '4.0'] - rbs-version: ['3.6.1', '3.9.5', '4.0.0.dev.4'] - # Ruby 3.0 doesn't work with RBS 3.9.4 or 4.0.0.dev.4 + rbs-version: ['3.6.1', '3.9.5', '4.0.0.dev.5'] exclude: - ruby-version: '3.0' rbs-version: '3.9.5' - - ruby-version: '3.0' - rbs-version: '4.0.0.dev.4' # Missing require in 'rbs collection update' - hopefully # fixed in next RBS release - ruby-version: '4.0' rbs-version: '3.6.1' - - ruby-version: '4.0' - rbs-version: '4.0.0.dev.4' steps: - uses: actions/checkout@v3 - name: Set up Ruby From 47d749b0c57166fb3dfe1a1a61cb4331eb1c8870 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 8 Jan 2026 20:05:47 -0500 Subject: [PATCH 833/930] Open up in gemspec --- solargraph.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solargraph.gemspec b/solargraph.gemspec index 98f524d4d..0461d93fa 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -42,7 +42,7 @@ Gem::Specification.new do |s| 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.6.1', '<= 4.0.0.dev.4'] + s.add_runtime_dependency 'rbs', ['>= 3.6.1', '<= 4.0.0.dev.5'] s.add_runtime_dependency 'reverse_markdown', '~> 3.0' s.add_runtime_dependency 'rubocop', '~> 1.76' s.add_runtime_dependency 'thor', '~> 1.0' From 0a724ef46c0ae47b066b931a831a57ba08a87ea6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 8 Jan 2026 20:06:47 -0500 Subject: [PATCH 834/930] Fix missing spot --- .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 0ceae2bf8..fb7feec76 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -61,7 +61,7 @@ jobs: - ruby-version: '3.3' rbs-version: '3.10.0' - ruby-version: '3.4' - rbs-version: '4.0.0.dev.4' + rbs-version: '4.0.0.dev.5' - ruby-version: '4.0' rbs-version: '4.0.0.dev.5' steps: From 2b7a7869799ea8321b13cf59f020b3cb35df4843 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 8 Jan 2026 20:22:42 -0500 Subject: [PATCH 835/930] Typecheck using RBS prereleases --- .github/workflows/linting.yml | 2 +- .github/workflows/plugins.yml | 8 ++++---- .github/workflows/typecheck.yml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index b4ef26bfe..d8a627d1e 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -37,7 +37,7 @@ jobs: - name: Update to best available RBS run: | - bundle update rbs # use latest available for this Ruby version + bundle update --pre rbs # use latest available for this Ruby version - name: Restore cache of gem annotations id: dot-cache-restore diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index c7ad72cb4..79d95088b 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -34,7 +34,7 @@ jobs: echo 'gem "solargraph-rails"' > .Gemfile echo 'gem "solargraph-rspec"' >> .Gemfile bundle install - bundle update rbs + bundle update --pre rbs - name: Configure to use plugins run: | bundle exec solargraph config @@ -64,7 +64,7 @@ jobs: run: | echo 'gem "solargraph-rails"' > .Gemfile bundle install - bundle update rbs + bundle update --pre rbs - name: Configure to use plugins run: | bundle exec solargraph config @@ -93,7 +93,7 @@ jobs: run: | echo 'gem "solargraph-rspec"' >> .Gemfile bundle install - bundle update rbs + bundle update --pre rbs - name: Configure to use plugins run: | bundle exec solargraph config @@ -191,7 +191,7 @@ jobs: cd ../solargraph-rails echo "gem 'solargraph', path: '${GITHUB_WORKSPACE:?}'" >> Gemfile bundle install - bundle update rbs + bundle update --pre rbs RAILS_DIR="$(pwd)/spec/rails7" export RAILS_DIR cd ${RAILS_DIR} diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index f40977acf..4fe129c22 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -32,7 +32,7 @@ jobs: - name: Install gems run: | bundle install - bundle update rbs # use latest available for this Ruby version + bundle update --pre rbs # use latest available for this Ruby version - name: Install gem types run: bundle exec rbs collection install - name: Typecheck self From 44543d40927eb9c6db3661c9cae7ec2a50230c53 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 9 Jan 2026 09:18:41 -0500 Subject: [PATCH 836/930] Move point of ignoring cached gems for gem projects --- lib/solargraph/doc_map.rb | 12 ++++++++++++ lib/solargraph/parser/parser_gem/node_chainer.rb | 1 - lib/solargraph/workspace/gemspecs.rb | 9 +-------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 87ab7ce89..932a8740e 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -138,6 +138,18 @@ def load_serialized_gem_pins out: @out # @type [Array] gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies(out: out).to_a + # if we are type checking a gem project, we should not include + # pins from rbs or yard from that gem here - we use our own + # parser for those pins + + # @param gemspec [Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification] + gemspecs.reject! do |gemspec| + gemspec.respond_to?(:source) && + gemspec.source.instance_of?(Bundler::Source::Gemspec) && + gemspec.source.respond_to?(:path) && + gemspec.source.path == Pathname.new('.') + end + missing_paths.each do |path| # this will load from disk if needed; no need to manage # uncached_gemspecs to trigger that later diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index d5651f3ad..d8d46319b 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -126,7 +126,6 @@ def generate_links n end end elsif n.type == :hash - # @sg-ignore Too many arguments to Hash.new result.push Chain::Hash.new('::Hash', n, hash_is_splatted?(n)) elsif n.type == :array chained_children = n.children.map { |c| NodeChainer.chain(c) } diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 3e473679e..670fbc956 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -68,7 +68,7 @@ def resolve_require require end # look ourselves just in case this is hanging out somewhere - # that find_by_path doesn't index' + # that find_by_path doesn't index gemspec = all_gemspecs.find do |spec| spec = to_gem_specification(spec) unless spec.respond_to?(:files) @@ -240,13 +240,6 @@ def all_gemspecs_from_this_bundle to_gem_specification(specish) end end.compact - # @param gemspec [Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification] - all_gemspecs.reject do |gemspec| - gemspec.respond_to?(:source) && - gemspec.source.instance_of?(Bundler::Source::Gemspec) && - gemspec.source.respond_to?(:path) && - gemspec.source.path == Pathname.new('.') - end end # @return [Array] From c4a91f4149256225999d6de9fce7334e4a6523c4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 13:12:16 -0500 Subject: [PATCH 837/930] Fix issues resolving cgi escape functions --- lib/solargraph/rbs_map.rb | 24 +++++++++++++++--------- lib/solargraph/rbs_map/stdlib_map.rb | 7 +++++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index a5b998ff0..170100e9f 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -109,19 +109,19 @@ def cache_key # @param rbs_collection_config_path [String, Pathname, nil] # @return [RbsMap] def self.from_gemspec gemspec, rbs_collection_path, rbs_collection_config_path - rbs_map = RbsMap.new(gemspec.name, gemspec.version, - rbs_collection_paths: [rbs_collection_path].compact, - rbs_collection_config_path: rbs_collection_config_path) + # prefers stdlib RBS if available + rbs_map = RbsMap::StdlibMap.new(gemspec.name) return rbs_map if rbs_map.resolved? - # try any version of the gem in the collection - rbs_map = RbsMap.new(gemspec.name, nil, + rbs_map = RbsMap.new(gemspec.name, gemspec.version, 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) + # 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) end # @param out [IO, nil] where to log messages @@ -187,6 +187,13 @@ def conversions # @return [void] def log_caching lib, out:; end + def resolve_dependencies? + # we need to resolve dependencies via gemfile.lock manually for + # YARD regardless, so use same mechanism here so we don't + # duplicate work generating pins from dependencies + false + end + # @param loader [RBS::EnvironmentLoader] # @param library [String] # @param version [String, nil] the version of the library to load, or nil for any @@ -194,8 +201,7 @@ def log_caching lib, out:; end # @return [Boolean] true if adding the library succeeded def add_library loader, library, version, out: $stderr @resolved = if loader.has_library?(library: library, version: version) - # we find our own dependencies from gemfile.lock - loader.add library: library, version: version, resolve_dependencies: false + loader.add library: library, version: version, resolve_dependencies: resolve_dependencies? logger.debug { "#{short_name} successfully loaded library #{library}:#{version}" } true else diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index e7891bfe3..6c008954e 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -54,6 +54,13 @@ def self.stdlib_dependencies name, version = nil end end + def resolve_dependencies? + # there are 'virtual' dependencies for stdlib gems in RBS that + # aren't represented in the actual gemspecs that we'd + # otherwise use + true + end + # @param library [String] # @return [StdlibMap] def self.load library From 02151a52d49659aa3fe919b35cc143bf9ddc1d76 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 13:33:30 -0500 Subject: [PATCH 838/930] Be more careful marking things as stdlib --- lib/solargraph/rbs_map.rb | 8 ++++---- lib/solargraph/rbs_map/stdlib_map.rb | 2 +- spec/pin_cache_spec.rb | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index 170100e9f..a42f984ad 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -204,10 +204,10 @@ def add_library loader, library, version, out: $stderr loader.add library: library, version: version, resolve_dependencies: resolve_dependencies? logger.debug { "#{short_name} successfully loaded library #{library}:#{version}" } true - else - logger.info { "#{short_name} did not find data for library #{library}:#{version}" } - false - end + else + logger.info { "#{short_name} did not find data for library #{library}:#{version}" } + false + end end # @return [String] diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index 6c008954e..c2cfcabb6 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -25,7 +25,7 @@ def initialize library, out: $stderr @resolved = true @loaded = true logger.debug { "Deserialized #{cached_pins.length} cached pins for stdlib require #{library.inspect}" } - else + elsif self.class.source.has? library, nil super unless resolved? @pins = [] diff --git a/spec/pin_cache_spec.rb b/spec/pin_cache_spec.rb index 0a11686f5..0f766117a 100644 --- a/spec/pin_cache_spec.rb +++ b/spec/pin_cache_spec.rb @@ -136,8 +136,8 @@ end end - context 'with gem packaged with its own RBS gem' do - let(:gem_name) { 'base64' } + context 'with gem packaged with its own RBS' do + let(:gem_name) { 'rubocop-yard' } before do Solargraph::Shell.new.uncache(gem_name) @@ -150,7 +150,7 @@ 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 + expect(File).to have_received(:write).with(%r{combined/.*/rubocop-yard-.*-export.ser$}, any_args, mode: 'wb').once end end end From 14a84689739ec6b0547d4a5586411d71fa4f434b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 13:45:13 -0500 Subject: [PATCH 839/930] Reclassify rbs gem --- spec/rbs_map/stdlib_map_spec.rb | 24 ------------------------ spec/rbs_map_spec.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/spec/rbs_map/stdlib_map_spec.rb b/spec/rbs_map/stdlib_map_spec.rb index 03f0b547f..c9db9bb48 100644 --- a/spec/rbs_map/stdlib_map_spec.rb +++ b/spec/rbs_map/stdlib_map_spec.rb @@ -11,30 +11,6 @@ expect(pin).to be_a(Solargraph::Pin::Base) end - it 'processes RBS class variables' do - map = Solargraph::RbsMap::StdlibMap.load('rbs') - store = Solargraph::ApiMap::Store.new(map.pins) - class_variable_pins = store.pins_by_class(Solargraph::Pin::ClassVariable) - count_pins = class_variable_pins.select do |pin| - pin.name.to_s == '@@count' && pin.context.to_s == 'Class' - end - expect(count_pins.length).to eq(1) - count_pin = count_pins.first - expect(count_pin.return_type.to_s).to eq('Integer') - end - - it 'processes RBS class instance variables' do - map = Solargraph::RbsMap::StdlibMap.load('rbs') - store = Solargraph::ApiMap::Store.new(map.pins) - instance_variable_pins = store.pins_by_class(Solargraph::Pin::InstanceVariable) - root_pins = instance_variable_pins.select do |pin| - pin.name.to_s == '@root' && pin.context.to_s == 'Class' && pin.scope == :class - end - expect(root_pins.length).to eq(1) - root_pin = root_pins.first - expect(root_pin.return_type.to_s).to eq('RBS::Namespace, nil') - end - it 'processes RBS module aliases' do map = Solargraph::RbsMap::StdlibMap.load('yaml') store = Solargraph::ApiMap::Store.new(map.pins) diff --git a/spec/rbs_map_spec.rb b/spec/rbs_map_spec.rb index f3ca90a36..4631c9ca5 100644 --- a/spec/rbs_map_spec.rb +++ b/spec/rbs_map_spec.rb @@ -25,4 +25,30 @@ pin = rbs_map.path_pin('RBS::EnvironmentWalker::InstanceNode') expect(pin.return_type.tag).to eq('Class') end + + it 'processes RBS class variables' do + spec = Gem::Specification.find_by_name('rbs') + rbs_map = Solargraph::RbsMap.from_gemspec(spec, nil, nil) + store = Solargraph::ApiMap::Store.new(rbs_map.pins) + class_variable_pins = store.pins_by_class(Solargraph::Pin::ClassVariable) + count_pins = class_variable_pins.select do |pin| + pin.name.to_s == '@@count' && pin.context.to_s == 'Class' + end + expect(count_pins.length).to eq(1) + count_pin = count_pins.first + expect(count_pin.return_type.to_s).to eq('Integer') + end + + it 'processes RBS class instance variables' do + spec = Gem::Specification.find_by_name('rbs') + rbs_map = Solargraph::RbsMap.from_gemspec(spec, nil, nil) + store = Solargraph::ApiMap::Store.new(rbs_map.pins) + instance_variable_pins = store.pins_by_class(Solargraph::Pin::InstanceVariable) + root_pins = instance_variable_pins.select do |pin| + pin.name.to_s == '@root' && pin.context.to_s == 'Class' && pin.scope == :class + end + expect(root_pins.length).to eq(1) + root_pin = root_pins.first + expect(root_pin.return_type.to_s).to eq('RBS::Namespace, nil') + end end From d5668fe8f5aa0f7fdc05d6bbd8ccf0bfc431a07f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 14:16:56 -0500 Subject: [PATCH 840/930] Fix merge --- spec/doc_map_spec.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index ca03438fc..7c541da27 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -102,11 +102,6 @@ end 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 an uncached but valid gemspec' do let(:requires) { ['uncached_gem'] } let(:pre_cache) { false } From cde107371b6fd4c7447c437a6e520b50ed65d7bb Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 15:22:22 -0500 Subject: [PATCH 841/930] Add sg-ignore --- lib/solargraph/pin/callable.rb | 4 +++- lib/solargraph/yardoc.rb | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index edbc3f941..cb867d785 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -205,7 +205,9 @@ def arity_matches? arguments, with_block argcount = arguments.length parcount = mandatory_positional_param_count parcount -= 1 if !parameters.empty? && parameters.last.block? - return false if block? && !with_block + # @todo this and its caller should be changed so that this can + # look at the kwargs provided and check names against what + # we acccept return false if argcount < parcount && !(argcount == parcount - 1 && parameters.last.restarg?) true end diff --git a/lib/solargraph/yardoc.rb b/lib/solargraph/yardoc.rb index 907afb2de..084bb38f4 100644 --- a/lib/solargraph/yardoc.rb +++ b/lib/solargraph/yardoc.rb @@ -32,6 +32,8 @@ 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 Our fill won't work properly due to an issue in + # Callable#arity_matches? - see comment there 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}" } From e155ffab81f98ed38da9ab6bf5a29f38a89aae2a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 15:32:13 -0500 Subject: [PATCH 842/930] Fix merge --- lib/solargraph/pin/callable.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index cb867d785..732bca768 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -205,6 +205,7 @@ def arity_matches? arguments, with_block argcount = arguments.length parcount = mandatory_positional_param_count parcount -= 1 if !parameters.empty? && parameters.last.block? + return false if block? && !with_block # @todo this and its caller should be changed so that this can # look at the kwargs provided and check names against what # we acccept From d8dfcc02ed0967b5ff82ecf7200b2b0042ff4c1e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 16:13:22 -0500 Subject: [PATCH 843/930] Remove outdated workaround --- .github/workflows/plugins.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 0a83f8aba..9a9b80b70 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -55,8 +55,6 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: 3.4 - # See https://github.com/castwide/solargraph/actions/runs/19000135777/job/54265647107?pr=1119 - rubygems: latest bundler-cache: false - uses: awalsh128/cache-apt-pkgs-action@latest with: From 0fad08f752f36470bfd72bf11880a850d3e8bad5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 16:46:35 -0500 Subject: [PATCH 844/930] Fix @sg-ignore name --- lib/solargraph/language_server/host.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index d43f58a80..44d1ecdeb 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -301,7 +301,7 @@ def prepare directory, name = nil end # @return [String] - # @sg-ignore Need detailed hash types + # @sg-ignore Need to validate config def command_path options['commandPath'] || 'solargraph' end From 8656254513aa466947d66ed32f061b3be67a8848 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 17:15:04 -0500 Subject: [PATCH 845/930] Restore workaround --- .github/workflows/plugins.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 9a9b80b70..2631b70cc 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -56,6 +56,8 @@ jobs: with: ruby-version: 3.4 bundler-cache: false + # See https://github.com/castwide/solargraph/actions/runs/19000135777/job/54265647107?pr=1119 + rubygems: latest - uses: awalsh128/cache-apt-pkgs-action@latest with: packages: yq From 236734ed404d265d71a9b7191613876a61132e59 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 17:25:20 -0500 Subject: [PATCH 846/930] Restore workaround --- .github/workflows/plugins.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 2631b70cc..166db358b 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -56,8 +56,6 @@ jobs: with: ruby-version: 3.4 bundler-cache: false - # See https://github.com/castwide/solargraph/actions/runs/19000135777/job/54265647107?pr=1119 - rubygems: latest - uses: awalsh128/cache-apt-pkgs-action@latest with: packages: yq @@ -176,6 +174,8 @@ jobs: ruby-version: '3.0' bundler-cache: false bundler: latest + # See https://github.com/castwide/solargraph/actions/runs/19000135777/job/54265647107?pr=1119 + rubygems: latest env: MATRIX_RAILS_VERSION: "7.0" - name: Install gems From a575014fd316ede2ef17ca1167f62ce10f68f9f8 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 19:01:46 -0500 Subject: [PATCH 847/930] Merge branch 'flow_sensitive_typing_2_0' into 2025-01-06 --- lib/solargraph/complex_type/type_methods.rb | 2 +- lib/solargraph/complex_type/unique_type.rb | 12 ++++++------ lib/solargraph/equality.rb | 2 +- lib/solargraph/gem_pins.rb | 1 - .../parser/parser_gem/node_processors/send_node.rb | 3 +-- lib/solargraph/pin/parameter.rb | 1 - lib/solargraph/rbs_map/conversions.rb | 2 +- lib/solargraph/type_checker.rb | 4 ---- 8 files changed, 10 insertions(+), 17 deletions(-) diff --git a/lib/solargraph/complex_type/type_methods.rb b/lib/solargraph/complex_type/type_methods.rb index aeeb98f06..df7522c70 100644 --- a/lib/solargraph/complex_type/type_methods.rb +++ b/lib/solargraph/complex_type/type_methods.rb @@ -206,7 +206,7 @@ def scope # @param other [Object] def == other return false unless self.class == other.class - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 + # @sg-ignore Flow-sensitive typing should support .class == .class tag == other.tag end diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 4d6ca1387..c018bd544 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -182,17 +182,17 @@ def determine_non_literal_name def eql?(other) self.class == other.class && - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 + # @sg-ignore flow-sensitive typing should support .class == .class @name == other.name && - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 + # @sg-ignore flow-sensitive typing should support .class == .class @key_types == other.key_types && - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 + # @sg-ignore flow-sensitive typing should support .class == .class @subtypes == other.subtypes && - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 + # @sg-ignore flow-sensitive typing should support .class == .class @rooted == other.rooted? && - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 + # @sg-ignore flow-sensitive typing should support .class == .class @all_params == other.all_params && - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 + # @sg-ignore flow-sensitive typing should support .class == .class @parameters_type == other.parameters_type end diff --git a/lib/solargraph/equality.rb b/lib/solargraph/equality.rb index f8c50ff31..69f415e26 100644 --- a/lib/solargraph/equality.rb +++ b/lib/solargraph/equality.rb @@ -12,7 +12,7 @@ module Equality # @return [Boolean] def eql?(other) self.class.eql?(other.class) && - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 + # @sg-ignore flow-sensitive typing should support .class == .class equality_fields.eql?(other.equality_fields) end diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index 15103952f..790422065 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -57,7 +57,6 @@ def self.combine(yard_pins, rbs_pins) next yard_pin unless rbs_pin && yard_pin.is_a?(Pin::Method) unless rbs_pin - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 logger.debug { "GemPins.combine: No rbs pin for #{yard_pin.path} - using YARD's '#{yard_pin.inspect} (return_type=#{yard_pin.return_type}; signatures=#{yard_pin.signatures})" } next yard_pin end 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 861d6b157..55d5e58d2 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -221,6 +221,7 @@ def process_module_function node: ref.node, source: :parser) pins.push mm, cm + # @param ivar [Pin::InstanceVariable] pins.select{|pin| pin.is_a?(Pin::InstanceVariable) && pin.closure.path == ref.path}.each do |ivar| pins.delete ivar pins.push Solargraph::Pin::InstanceVariable.new( @@ -228,7 +229,6 @@ def process_module_function closure: cm, name: ivar.name, comments: ivar.comments, - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 assignment: ivar.assignment, source: :parser ) @@ -237,7 +237,6 @@ def process_module_function closure: mm, name: ivar.name, comments: ivar.comments, - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 assignment: ivar.assignment, source: :parser ) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index c5f86a6ea..bec8f6294 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -251,7 +251,6 @@ def param_tag def typify_block_param api_map block_pin = closure if block_pin.is_a?(Pin::Block) && block_pin.receiver && index - # @sg-ignore flow-sensitive typing should handle is_a? with && return block_pin.typify_parameters(api_map)[index] end ComplexType::UNDEFINED diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index ceeb5454b..739542516 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -65,7 +65,7 @@ def convert_decl_to_pin decl, closure # STDERR.puts "Skipping interface #{decl.name.relative!}" interface_decl_to_pin decl, closure when RBS::AST::Declarations::TypeAlias - # @sg-ignore https://github.com/castwide/solargraph/pull/1114 + # @sg-ignore flow-sensitive typing should support case/when type_aliases[decl.name.to_s] = decl when RBS::AST::Declarations::Module module_decl_to_pin decl diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 0b9eb7e00..dcddc23c6 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -332,11 +332,8 @@ def call_problems 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 - # @sg-ignore Change to something flow-sensitive typing understands if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) - # @sg-ignore Change to something flow-sensitive typing understands unless closest.generic? || ignored_pins.include?(found) - # @sg-ignore Change to something flow-sensitive typing understands if closest.defined? result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest}") else @@ -685,7 +682,6 @@ def declared_externally? pin end all_closest = all_found.map { |pin| pin.typify(api_map) } closest = ComplexType.new(all_closest.flat_map(&:items).uniq) - # @sg-ignore Change to something flow-sensitive typing understands if !found || closest.defined? || internal?(found) return false end From 16506e8f50d2a9ef23e8c8cc0f195ebc86151006 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 19:02:14 -0500 Subject: [PATCH 848/930] Fix method signature --- lib/solargraph/workspace.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 252fc5148..d0c3cb970 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -236,10 +236,11 @@ def rbs_collection_config_path # @param name [String] # @param version [String, nil] + # @param out [IO, nil] # # @return [Gem::Specification, nil] - def find_gem name, version = nil - gemspecs.find_gem(name, version) + def find_gem name, version = nil, out: nil + gemspecs.find_gem(name, version, out: out) end # @return [Array] From 740db4e7772129aae2d225baf5c3549d969a40a0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 19:03:03 -0500 Subject: [PATCH 849/930] Fix annotations --- lib/solargraph/library.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 7de06bacd..ca6253a17 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -590,7 +590,6 @@ def cache_errors def cache_next_gemspec return if @cache_progress - # @type [Gem::Specification] spec = cacheable_specs.first return end_cache_progress unless spec From 4d1c21a76943ad06057d8832379a5f2854c2195b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 19:51:21 -0500 Subject: [PATCH 850/930] Add regression test and fix for issue found during future merge --- lib/solargraph/source/chain.rb | 2 +- spec/type_checker/levels/alpha_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index f7a03b552..7aa8dede1 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -276,7 +276,7 @@ def infer_from_definitions pins, context, api_map, locals return type end - type.self_to_type(context.return_type) + type.self_to_type(context.context) end # @param type [ComplexType] diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 781907d33..2abe5ab7c 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -73,5 +73,25 @@ def catalog expect(checker.problems.map(&:message)).to eq([]) end + + it 'resolves self correctly in arguments' do + checker = type_checker(%( + class Foo + # @param other [self] + # + # @return [String] + def bar other + other.bing + end + + # @return [String] + def bing + 'bing' + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end end end From 29da3afe0647121035c1d5384f7b8cf10d30b97a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 10 Jan 2026 20:33:27 -0500 Subject: [PATCH 851/930] Add regression test and fix for issue found during future merge --- lib/solargraph/source/chain.rb | 2 +- spec/type_checker/levels/alpha_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index 7aa8dede1..e9722fcc4 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -271,7 +271,7 @@ def infer_from_definitions pins, context, api_map, locals else ComplexType.new(types) end - if context.nil? || context.return_type.undefined? + if context.nil? || context.context.undefined? # up to downstream to resolve self type return type end diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 2abe5ab7c..b9acfc83a 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -93,5 +93,26 @@ def bing expect(checker.problems.map(&:message)).to eq([]) end + + it 'resolves self correctly in arguments' do + checker = type_checker(%( + class Blah + # @return [String] + attr_reader :filename + + # @param filename [String] + def initialize filename + @filename = filename + end + + # @param location [self] + def contain? location + filename == location.filename + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end end end From b7b31a9e1746a01c8dffacbe5aca898c57f67d8a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 10:56:41 -0500 Subject: [PATCH 852/930] Fix merge --- lib/solargraph/pin/base_variable.rb | 8 -------- lib/solargraph/pin/local_variable.rb | 24 ------------------------ 2 files changed, 32 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 60fd5a392..c2f29edd9 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -276,14 +276,6 @@ def combine_closure(other) other.closure end - # @param other_closure [Pin::Closure] - # @param other_loc [Location] - def visible_at?(other_closure, other_loc) - location.filename == other_loc.filename && - (!presence || presence.include?(other_loc.range.start)) && - visible_in_closure?(other_closure) - end - def presence_certain? @presence_certain end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index b287bbfea..b7b0cf5f1 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -22,30 +22,6 @@ def combine_with(other, attrs={}) super end - # @param other [self] - # @return [Pin::Closure, nil] - def combine_closure(other) - return closure if self.closure == other.closure - - # choose first defined, as that establishes the scope of the variable - if closure.nil? || other.closure.nil? - Solargraph.assert_or_log(:varible_closure_missing) do - "One of the local variables being combined is missing a closure: " \ - "#{self.inspect} vs #{other.inspect}" - end - return closure || other.closure - end - - if closure.location.nil? || other.closure.location.nil? - return closure.location.nil? ? other.closure : closure - end - - # if filenames are different, this will just pick one - return closure if closure.location <= other.closure.location - - other.closure - end - # @param other_closure [Pin::Closure] # @param other_loc [Location] def visible_at?(other_closure, other_loc) From 9ac6e9319352f8fae58efa3e96de687825ae3a63 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 10:57:39 -0500 Subject: [PATCH 853/930] Fix merge --- lib/solargraph/pin/base_variable.rb | 8 ++++++++ lib/solargraph/pin/local_variable.rb | 8 -------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index c2f29edd9..60fd5a392 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -276,6 +276,14 @@ def combine_closure(other) other.closure end + # @param other_closure [Pin::Closure] + # @param other_loc [Location] + def visible_at?(other_closure, other_loc) + location.filename == other_loc.filename && + (!presence || presence.include?(other_loc.range.start)) && + visible_in_closure?(other_closure) + end + def presence_certain? @presence_certain end diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index b7b0cf5f1..9841a9fd3 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -22,14 +22,6 @@ def combine_with(other, attrs={}) super end - # @param other_closure [Pin::Closure] - # @param other_loc [Location] - def visible_at?(other_closure, other_loc) - location.filename == other_loc.filename && - (!presence || presence.include?(other_loc.range.start)) && - visible_in_closure?(other_closure) - end - def to_rbs (name || '(anon)') + ' ' + (return_type&.to_rbs || 'untyped') end From 861fc028268ca9432b5804e4d49d1b326db70c2d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 16:50:44 -0500 Subject: [PATCH 854/930] Fix merge --- lib/solargraph/pin_cache.rb | 1 - lib/solargraph/workspace/gemspecs.rb | 2 -- 2 files changed, 3 deletions(-) diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index f831d88b7..bc5bb9cdd 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -277,7 +277,6 @@ def deserialize_yard_pin_cache gemspec # @param rbs_version_cache_key [String, nil] # @return [Array, nil] 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 diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 41fa58d74..59457566d 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -99,8 +99,6 @@ def stdlib_dependencies stdlib_name # # @return [Gem::Specification, nil] def find_gem name, version = nil, out: $stderr - Bundler::StubSpecification - Bundler::RemoteSpecification gemspec = all_gemspecs_from_bundle.find { |gemspec| gemspec.name == name && gemspec.version == version } return gemspec if gemspec From 25691dea539c669e4eb82b58402a697710bdef91 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 16:57:50 -0500 Subject: [PATCH 855/930] Fix merge --- spec/type_checker/levels/strong_spec.rb | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 195840b5d..5c99216d8 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -741,23 +741,6 @@ def baz(other); end expect(checker.problems.map(&:message)).to be_empty end - it 'understands self type when passed as parameter' do - checker = type_checker(%( - class Location - # @return [String] - attr_reader :filename - - # @param other [self] - def <=>(other) - return nil unless other.is_a?(Location) - - filename <=> other.filename - end - end - )) - expect(checker.problems.map(&:message)).to be_empty - end - it 'uses cast type instead of defined type' do checker = type_checker(%( # frozen_string_literal: true From e0deadf2f665e6589ac5f1938a15abbd77a06d78 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 17:14:56 -0500 Subject: [PATCH 856/930] Use correct field for self type resolution Add a regression test and fix for self type resolution issue found on a future branch --- lib/solargraph/source/chain.rb | 2 +- spec/type_checker/levels/alpha_spec.rb | 29 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 spec/type_checker/levels/alpha_spec.rb diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index f7a03b552..7aa8dede1 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -276,7 +276,7 @@ def infer_from_definitions pins, context, api_map, locals return type end - type.self_to_type(context.return_type) + type.self_to_type(context.context) end # @param type [ComplexType] diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb new file mode 100644 index 000000000..118f4cccf --- /dev/null +++ b/spec/type_checker/levels/alpha_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +describe Solargraph::TypeChecker do + context 'when at alpha level' do + def type_checker code + Solargraph::TypeChecker.load_string(code, 'test.rb', :alpha) + end + + it 'resolves self correctly in arguments' do + checker = type_checker(%( + class Foo + # @param other [self] + # + # @return [String] + def bar other + other.bing + end + + # @return [String] + def bing + 'bing' + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end + end +end From dbcd966d5c3f350effbd2a8058a57f111c95bbea Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 17:49:37 -0500 Subject: [PATCH 857/930] Fix 'solargraph pin --references ClassName' private method call --- lib/solargraph/api_map.rb | 12 ++++++------ spec/shell_spec.rb | 32 +++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index cc3031ea5..6c0da316e 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -711,6 +711,12 @@ 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 + store.qualify_superclass fq_sub_tag + end + private # A hash of source maps with filename keys. @@ -804,12 +810,6 @@ def path_macros @path_macros ||= {} end - # @param fq_sub_tag [String] - # @return [String, nil] - def qualify_superclass fq_sub_tag - store.qualify_superclass fq_sub_tag - end - # Get the namespace's type (Class or Module). # # @param fqns [String] A fully qualified namespace diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index b9dc6b327..3b8dc0426 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -57,7 +57,37 @@ def bundle_exec(*cmd) end end - describe 'pin' do + describe 'pin on a class' do + let(:api_map) { instance_double(Solargraph::ApiMap) } + let(:string_pin) { instance_double(Solargraph::Pin::Namespace, name: 'String') } + + before do + allow(Solargraph::ApiMap).to receive(:load_with_cache).and_return(api_map) + allow(Solargraph::Pin::Namespace).to receive(:===).with(string_pin).and_return(true) + allow(string_pin).to receive(:return_type).and_return(Solargraph::ComplexType.parse('String')) + allow(api_map).to receive(:get_path_pins).with('String').and_return([string_pin]) + end + + context 'with --references option' do + let(:object_pin) { instance_double(Solargraph::Pin::Namespace, name: 'Object') } + + before do + allow(Solargraph::Pin::Namespace).to receive(:===).with(object_pin).and_return(true) + allow(api_map).to receive(:qualify_superclass).with('String').and_return('Object') + allow(api_map).to receive(:get_path_pins).with('Object').and_return([object_pin]) + end + + it 'prints a pin with info' do + out = capture_both do + shell.options = { references: true } + shell.pin('String') + end + expect(out).to include('# Superclass:') + end + end + end + + describe 'pin on a method' do let(:api_map) { instance_double(Solargraph::ApiMap) } let(:to_s_pin) { instance_double(Solargraph::Pin::Method, return_type: Solargraph::ComplexType.parse('String')) } From 4f4b6a05b6154f8d6461248f6f47705dc478c9e7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 17:58:09 -0500 Subject: [PATCH 858/930] Add error handling --- lib/solargraph/shell.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index d6420a26e..9983f82cf 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -132,6 +132,8 @@ def uncache *gems end spec = workspace.find_gem(gem) + raise Thor::InvocationError, "Gem '#{gem}' not found" if spec.nil? + PinCache.uncache_gem(spec, out: $stdout) end end From 176b50649e01ccbeb0013bc439093515f3d30760 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 18:05:51 -0500 Subject: [PATCH 859/930] Fix another location with another test case --- lib/solargraph/source/chain.rb | 2 +- spec/type_checker/levels/alpha_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index 7aa8dede1..e9722fcc4 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -271,7 +271,7 @@ def infer_from_definitions pins, context, api_map, locals else ComplexType.new(types) end - if context.nil? || context.return_type.undefined? + if context.nil? || context.context.undefined? # up to downstream to resolve self type return type end diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 118f4cccf..4fd591fda 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -25,5 +25,26 @@ def bing expect(checker.problems.map(&:message)).to eq([]) end + + it 'resolves self correctly in arguments (second case)' do + checker = type_checker(%( + class Blah + # @return [String] + attr_reader :filename + + # @param filename [String] + def initialize filename + @filename = filename + end + + # @param location [self] + def contain? location + filename == location.filename + end + end + )) + + expect(checker.problems.map(&:message)).to eq([]) + end end end From 45ea8955b5c0c3c95332489cbb93f5529690d9f0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 18:19:34 -0500 Subject: [PATCH 860/930] Drop now-unneeded @sg-ignore --- lib/solargraph/pin/method.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 86bf1cd09..e6e0f6503 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -92,7 +92,6 @@ def combine_with(other, attrs = {}) end new_attrs = { visibility: combine_visibility(other), - # @sg-ignore https://github.com/castwide/solargraph/pull/1050 explicit: explicit? || other.explicit?, block: combine_blocks(other), node: choose_node(other, :node), From 92286c13d17ec6b2209762b9c808abc4cf71e1a6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 18:34:20 -0500 Subject: [PATCH 861/930] Don't log caching for each dependent library This causes duplicate logging on standard libraries, many of which are esoteric (e.g., "cgi-escaping"). The current method as of the 2025-01-06 branch would result in each stdlib library being cached individually. --- lib/solargraph/rbs_map.rb | 4 ++-- lib/solargraph/rbs_map/stdlib_map.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index a42f984ad..04a718454 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -128,7 +128,7 @@ def self.from_gemspec gemspec, rbs_collection_path, rbs_collection_config_path # @return [Array] def pins out: $stderr @pins ||= if resolved? - loader.libs.each { |lib| log_caching(lib, out: out) } + log_caching(library, out: out) conversions.pins else [] @@ -182,7 +182,7 @@ def conversions @conversions ||= Conversions.new(loader: loader) end - # @param lib [RBS::EnvironmentLoader::Library] + # @param lib [String] # @param out [IO, nil] where to log messages # @return [void] def log_caching lib, out:; end diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index c2cfcabb6..0f812150c 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -13,7 +13,7 @@ class StdlibMap < RbsMap @stdlib_maps_hash = {} def log_caching lib, out: $stderr - out&.puts("Caching RBS pins for standard library #{lib.name}") + out&.puts("Caching RBS pins for standard library #{lib}") end # @param library [String] From 2e316d59d8cf02f95bc060e5384f15b5ece2edc4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 18:49:03 -0500 Subject: [PATCH 862/930] Drop logging entirely --- lib/solargraph/rbs_map.rb | 6 ------ lib/solargraph/rbs_map/stdlib_map.rb | 4 ---- 2 files changed, 10 deletions(-) diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index 04a718454..ca03f71e2 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -128,7 +128,6 @@ def self.from_gemspec gemspec, rbs_collection_path, rbs_collection_config_path # @return [Array] def pins out: $stderr @pins ||= if resolved? - log_caching(library, out: out) conversions.pins else [] @@ -182,11 +181,6 @@ def conversions @conversions ||= Conversions.new(loader: loader) end - # @param lib [String] - # @param out [IO, nil] where to log messages - # @return [void] - def log_caching lib, out:; end - def resolve_dependencies? # we need to resolve dependencies via gemfile.lock manually for # YARD regardless, so use same mechanism here so we don't diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index 0f812150c..ebfdadb61 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -12,10 +12,6 @@ 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}") - end - # @param library [String] # @param out [IO, nil] where to log messages def initialize library, out: $stderr From 458efed2e4591949052c47073932887dc6b7e6b9 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 20:36:13 -0500 Subject: [PATCH 863/930] Fix some types based on future branch feedback --- lib/solargraph/workspace/gemspecs.rb | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 20fe350ae..e44103c58 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -94,11 +94,11 @@ def stdlib_dependencies stdlib_name # # @return [Gem::Specification, 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 + specish = all_gemspecs_from_bundle.find { |specish| specish.name == name && specish.version == version } + return to_gem_specification specish if specish - gemspec = all_gemspecs_from_bundle.find { |gemspec| gemspec.name == name } - return gemspec if gemspec + specish = all_gemspecs_from_bundle.find { |specish| specish.name == name } + return to_gem_specification specish if specish resolve_gem_ignoring_local_bundle name, version, out: out end @@ -116,7 +116,6 @@ 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| - # @sg-ignore Unresolved call to requirement on Gem::Dependency dep = find_gem(runtime_dep.name, runtime_dep.requirement) next unless dep @@ -155,6 +154,7 @@ def self.gem_specification_cache private # @param specish [Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification] + # # @return [Gem::Specification, nil] def to_gem_specification specish # print time including milliseconds @@ -162,6 +162,7 @@ def to_gem_specification specish when Gem::Specification @@warned_on_rubygems ||= false if specish.respond_to?(:identifier) + # @type [Gem::Specification] specish else # see https://github.com/castwide/solargraph/actions/runs/17588131738/job/49961580698?pr=1006 - happened on Ruby 3.0 @@ -181,11 +182,12 @@ def to_gem_specification specish when Bundler::StubSpecification # turns a Bundler::StubSpecification into a # Gem::StubSpecification into a Gem::Specification - # @sg-ignore Flow-sensitive typing ought to be able to handle 'when ClassName' + # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' specish = specish.stub - # @sg-ignore Flow-sensitive typing ought to be able to handle 'when ClassName' + # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' if specish.respond_to?(:spec) - # @sg-ignore Flow-sensitive typing ought to be able to handle 'when ClassName' + # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' + # @type [Gem::Specification] specish.spec else # turn the crank again @@ -328,6 +330,7 @@ def all_gemspecs_from_external_bundle 'specish_objects = specish_objects.map(&:materialize_for_installation);' \ 'end;' \ 'specish_objects.map { |specish| [specish.name, specish.version] }' + # @type [Array] query_external_bundle(command).map do |name, version| resolve_gem_ignoring_local_bundle(name, version) end.compact From 45e5eca22dd540deda022fc28bd87728925d4c1f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 21:20:39 -0500 Subject: [PATCH 864/930] Provide Gem::Specification to outside interface --- lib/solargraph/workspace/gemspecs.rb | 28 ++++--------------- .../gemspecs_resolve_require_spec.rb | 8 ++---- 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index e44103c58..030707db7 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -116,6 +116,7 @@ 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| + # @sg-ignore Unresolved call to requirement on Gem::Dependency dep = find_gem(runtime_dep.name, runtime_dep.requirement) next unless dep @@ -155,25 +156,12 @@ def self.gem_specification_cache # @param specish [Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification] # - # @return [Gem::Specification, nil] + # @return [Gem::Specification] 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) - # @type [Gem::Specification] - 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! - + specish when Bundler::LazySpecification # materializing didn't work. Let's look in the local # rubygems without bundler's help @@ -194,13 +182,9 @@ def to_gem_specification specish to_gem_specification(specish) end else - @@warned_on_gem_type ||= false - unless @@warned_on_gem_type - # @sg-ignore Unresolved call to class on Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification - logger.warn "Unexpected type while resolving gem: #{specish.class}" - @@warned_on_gem_type = true - end - nil + # @sg-ignore Unresolved call to class on Gem::Specification, Bundler::LazySpecification, + # Bundler::StubSpecification + raise "Unexpected type while resolving gem: #{specish.class}" end end diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb index d53638600..e13166bf3 100644 --- a/spec/workspace/gemspecs_resolve_require_spec.rb +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -71,12 +71,8 @@ def install_gem gem_name, version 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']) + it 'raises a StandardException' do + expect { specs.size }.to raise_error(StandardError) end end From 2d5456a28a88c27276d2db5411067a21f126bb51 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 21:25:38 -0500 Subject: [PATCH 865/930] Provide Gem::Specification to outside interface --- lib/solargraph/workspace/gemspecs.rb | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 030707db7..9feba7524 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -169,21 +169,12 @@ def to_gem_specification specish specish.version when Bundler::StubSpecification # turns a Bundler::StubSpecification into a - # Gem::StubSpecification into a Gem::Specification + # Gem::StubSpecification + to_gem_specification specish.stub + when Gem::StubSpecification # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' - specish = specish.stub - # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' - if specish.respond_to?(:spec) - # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' - # @type [Gem::Specification] - specish.spec - else - # turn the crank again - to_gem_specification(specish) - end + specish.spec else - # @sg-ignore Unresolved call to class on Gem::Specification, Bundler::LazySpecification, - # Bundler::StubSpecification raise "Unexpected type while resolving gem: #{specish.class}" end end From 4ce641d4cec01d174c688dd9ea6d0aa7cca70a2c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 21:33:24 -0500 Subject: [PATCH 866/930] Use #to_spec --- lib/solargraph/workspace/gemspecs.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 9feba7524..7494f96e0 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -172,8 +172,7 @@ def to_gem_specification specish # Gem::StubSpecification to_gem_specification specish.stub when Gem::StubSpecification - # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' - specish.spec + specish.to_spec else raise "Unexpected type while resolving gem: #{specish.class}" end From aa99710e02aad4e476c665a3bddb696ed75c3cf3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 21:57:41 -0500 Subject: [PATCH 867/930] Provide Gem::Specification to outside interface --- lib/solargraph/workspace/gemspecs.rb | 13 ++++++++++--- spec/workspace/gemspecs_resolve_require_spec.rb | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 7494f96e0..e15f4da90 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -156,7 +156,7 @@ def self.gem_specification_cache # @param specish [Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification] # - # @return [Gem::Specification] + # @return [Gem::Specification, nil] def to_gem_specification specish # print time including milliseconds self.class.gem_specification_cache[specish] ||= case specish @@ -169,8 +169,15 @@ def to_gem_specification specish specish.version when Bundler::StubSpecification # turns a Bundler::StubSpecification into a - # Gem::StubSpecification - to_gem_specification specish.stub + # Gem::StubSpecification if we can + if specish.respond_to?(:stub) + to_gem_specification specish.stub + else + # A Bundler::StubSpecification is a Bundler:: + # RemoteSpecification which ought to proxy a Gem:: + # Specification + specish + end when Gem::StubSpecification specish.to_spec else diff --git a/spec/workspace/gemspecs_resolve_require_spec.rb b/spec/workspace/gemspecs_resolve_require_spec.rb index e13166bf3..8deba9ff8 100644 --- a/spec/workspace/gemspecs_resolve_require_spec.rb +++ b/spec/workspace/gemspecs_resolve_require_spec.rb @@ -92,6 +92,7 @@ def configure_bundler_spec stub_value 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(:respond_to?).with(:stub).and_return(false) allow(bundler_stub_spec).to receive_messages(name: 'solargraph', stub: stub_value) end From d9188e7f14e16c383333e221ebe5381852401922 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 22:08:37 -0500 Subject: [PATCH 868/930] Fix typechecking error --- 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 e15f4da90..feddf641b 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -178,6 +178,7 @@ def to_gem_specification specish # Specification specish end + # @sg-ignore Unresolved constant Gem::StubSpecification when Gem::StubSpecification specish.to_spec else From 8eea21f8bbe6f735ef9915095a597d192af937a6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 22:17:29 -0500 Subject: [PATCH 869/930] Use consistent bundler versions --- .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 ae885b4db..faeb330bf 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -31,9 +31,8 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: 3.4 - bundler: latest bundler-cache: true - cache-version: 2025-06-06 + cache-version: 2026-01-11 - name: Update to best available RBS run: | From ad6ee19005d45389fe1f9fc9952111971dff4e2e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 22:43:01 -0500 Subject: [PATCH 870/930] Fix type issue --- 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 7af70d474..3bf4edb73 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -182,6 +182,7 @@ def to_gem_specification specish # Specification specish end + # @sg-ignore Unresolved constant Gem::StubSpecification when Gem::StubSpecification specish.to_spec else From f88875cf426d29a294482a682886a3b712d66518 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 23:12:39 -0500 Subject: [PATCH 871/930] Fix annotations based on future branch feedback --- lib/solargraph/pin_cache.rb | 4 ++-- lib/solargraph/workspace.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 97f5f5d6a..c60a71234 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -27,7 +27,7 @@ def cached? gemspec combined_gem?(gemspec, rbs_version_cache_key) end - # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param gemspec [Gem::Specification] # @param rebuild [Boolean] whether to rebuild the cache regardless of whether it already exists # @param out [StringIO, IO, nil] output stream for logging # @return [void] @@ -167,7 +167,7 @@ def calculate_build_needs gemspec, rebuild:, rbs_version_cache_key: [build_yard, build_rbs_collection, build_combined] end - # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param gemspec [Gem::Specification] # @param rbs_version_cache_key [String, nil] # @param build_yard [Boolean] # @param build_rbs_collection [Boolean] diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 61ea253d6..ee9707477 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -73,7 +73,7 @@ def global_environ @global_environ ||= Convention.for_global(DocMap.new([], [], self)) end - # @param gemspec [Gem::Specification, Bundler::LazySpecification] + # @param gemspec [Gem::Specification] # @param out [StringIO, IO, nil] output stream for logging # @param rebuild [Boolean] whether to rebuild the pins even if they are cached # From 9268eae5bd58e4462aa3c499f58624ec489addc2 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 23:17:54 -0500 Subject: [PATCH 872/930] Add some @todos --- lib/solargraph/doc_map.rb | 1 + lib/solargraph/rbs_map/stdlib_map.rb | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 891bbabb3..cf5c40e6b 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -145,6 +145,7 @@ def load_serialized_gem_pins out: @out # try to resolve the stdlib name deps = workspace.stdlib_dependencies(stdlib_name_guess) || [] [stdlib_name_guess, *deps].compact.each do |potential_stdlib_name| + # @todo Need to support splatting in literal array rbs_pins = pin_cache.cache_stdlib_rbs_map potential_stdlib_name serialized_pins.concat rbs_pins if rbs_pins end diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index c2cfcabb6..fab2b3222 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -48,6 +48,8 @@ def self.source # @return [Array String}>, nil] def self.stdlib_dependencies name, version = nil if source.has?(name, version) + # @todo we are relying on undocumented behavior where + # passing version=nil gives the latest version it has source.dependencies_of(name, version) else [] From e9f83e633043a6102e954012e9555c8aff4dd7e7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 11 Jan 2026 23:28:19 -0500 Subject: [PATCH 873/930] Fix annotations --- lib/solargraph/doc_map.rb | 4 ++-- lib/solargraph/pin/method.rb | 4 ++-- lib/solargraph/rbs_map/stdlib_map.rb | 2 +- lib/solargraph/shell.rb | 1 + lib/solargraph/source_map/mapper.rb | 1 + 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index ba3905914..ae211ba2a 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -157,10 +157,10 @@ def load_serialized_gem_pins out: @out stdlib_name_guess = path.split('/').first # try to resolve the stdlib name - # @type Array + # @type [Array] deps = workspace.stdlib_dependencies(stdlib_name_guess) || [] [stdlib_name_guess, *deps].compact.each do |potential_stdlib_name| - # @todo Need to support splatting in literal array + # @sg-ignore Need to support splatting in literal array rbs_pins = pin_cache.cache_stdlib_rbs_map potential_stdlib_name serialized_pins.concat rbs_pins if rbs_pins end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 62a5f12ea..34eafa5de 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -382,7 +382,7 @@ def overloads generics: generics, # @param src [Array(String, String)] parameters: tag.parameters.map do |src| - # @todo Tuples need to support first method + # @sg-ignore Tuples need to support first method name, decl = parse_overload_param(src.first) Pin::Parameter.new( location: location, @@ -392,7 +392,7 @@ def overloads decl: decl, # @sg-ignore flow sensitive typing needs to handle attrs presence: location ? location.range : nil, - # @todo Tuple#first support + # @sg-ignore Tuple#first support return_type: param_type_from_name(tag, src.first), source: :overloads ) diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index 472885e8e..1ad1165e6 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -44,7 +44,7 @@ def self.source # @return [Array String}>, nil] def self.stdlib_dependencies name, version = nil if source.has?(name, version) - # @todo we are relying on undocumented behavior where + # @sg-ignore we are relying on undocumented behavior where # passing version=nil gives the latest version it has source.dependencies_of(name, version) else diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index f3a8113e1..e2c626528 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -135,6 +135,7 @@ def uncache *gems spec = workspace.find_gem(gem) raise Thor::InvocationError, "Gem '#{gem}' not found" if spec.nil? + # @sg-ignore flow sensitive typing needs to handle 'raise if' workspace.uncache_gem(spec, out: $stdout) end end diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 72ae19a80..6f86449b6 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -120,6 +120,7 @@ def process_directive source_position, comment_position, directive begin src = Solargraph::Source.load_string("def #{directive.tag.name};end", @source.filename) region = Parser::Region.new(source: src, closure: namespace) + # @sg-ignore flow sensitive typing should be able to handle redefinition # @type [Array] method_gen_pins = Parser.process_node(src.node, region).first.select { |pin| pin.is_a?(Pin::Method) } gen_pin = method_gen_pins.last From 0a52fef1168eef9b4c1626c62ebec7e934bd670d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 12 Jan 2026 03:55:33 -0500 Subject: [PATCH 874/930] Fix annotations --- lib/solargraph/language_server/host.rb | 1 - lib/solargraph/pin/method.rb | 2 -- lib/solargraph/source_map/mapper.rb | 1 - 3 files changed, 4 deletions(-) diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index f8dbe5a59..c59f7cd5e 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -771,7 +771,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/pin/method.rb b/lib/solargraph/pin/method.rb index 34eafa5de..e6d68c151 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -382,7 +382,6 @@ def overloads generics: generics, # @param src [Array(String, String)] parameters: tag.parameters.map do |src| - # @sg-ignore Tuples need to support first method name, decl = parse_overload_param(src.first) Pin::Parameter.new( location: location, @@ -392,7 +391,6 @@ def overloads decl: decl, # @sg-ignore flow sensitive typing needs to handle attrs presence: location ? location.range : nil, - # @sg-ignore Tuple#first support return_type: param_type_from_name(tag, src.first), source: :overloads ) diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 6f86449b6..72ae19a80 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -120,7 +120,6 @@ def process_directive source_position, comment_position, directive begin src = Solargraph::Source.load_string("def #{directive.tag.name};end", @source.filename) region = Parser::Region.new(source: src, closure: namespace) - # @sg-ignore flow sensitive typing should be able to handle redefinition # @type [Array] method_gen_pins = Parser.process_node(src.node, region).first.select { |pin| pin.is_a?(Pin::Method) } gen_pin = method_gen_pins.last From 91e2491207bab840a27247ab2f6bee9893b1223d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 12 Jan 2026 04:03:17 -0500 Subject: [PATCH 875/930] Fix annotation --- 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 c59f7cd5e..a0d0f6931 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -772,6 +772,7 @@ def check_diff uri, change diffs = Diff::LCS.diff(source.code, change['text']) return change if diffs.length.zero? || diffs.length > 1 || diffs.first.length > 1 # @type [Diff::LCS::Change] + # @sg-ignore push this upstream diff = diffs.first.first return change unless diff.adding? && ['.', ':', '(', ',', ' '].include?(diff.element) position = Solargraph::Position.from_offset(source.code, diff.position) From b270c48a1d97c98af737794f53e8e58082fe7ff5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 12 Jan 2026 04:34:40 -0500 Subject: [PATCH 876/930] Add diff::lcs shim --- lib/solargraph/language_server/host.rb | 1 - rbs/shims/diff-lcs/1.5/diff-lcs.rbs | 11 +++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 rbs/shims/diff-lcs/1.5/diff-lcs.rbs diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index a0d0f6931..c59f7cd5e 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -772,7 +772,6 @@ def check_diff uri, change diffs = Diff::LCS.diff(source.code, change['text']) return change if diffs.length.zero? || diffs.length > 1 || diffs.first.length > 1 # @type [Diff::LCS::Change] - # @sg-ignore push this upstream diff = diffs.first.first return change unless diff.adding? && ['.', ':', '(', ',', ' '].include?(diff.element) position = Solargraph::Position.from_offset(source.code, diff.position) diff --git a/rbs/shims/diff-lcs/1.5/diff-lcs.rbs b/rbs/shims/diff-lcs/1.5/diff-lcs.rbs new file mode 100644 index 000000000..40d4fc7e4 --- /dev/null +++ b/rbs/shims/diff-lcs/1.5/diff-lcs.rbs @@ -0,0 +1,11 @@ +module Diff +end + +module Diff::LCS + def self.LCS: (Array[String], Array[String]) -> Array[String] + | (String, String) -> Array[String] + def self.diff: (Array[String], Array[String]) -> Array[Array[String]] + | (String, String) -> Array[Array[Diff::LCS::Change]] + + def self.patch!: (Array[String], Array[String]) -> String +end From 7c35136dfdf51cd3684b81e27e247bacd6b824d1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 12 Jan 2026 05:06:45 -0500 Subject: [PATCH 877/930] Improve spec expectations --- spec/type_checker/levels/alpha_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 34e6a7029..ac164dec2 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -122,7 +122,7 @@ def bar end )) - expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round"]) + expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round on Integer, nil"]) end it 'understands &. in return position' do From a78d58d3faea39f6d2aba2db814cee23a0ba0770 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 12 Jan 2026 05:09:24 -0500 Subject: [PATCH 878/930] Add @sg-ignore --- lib/solargraph/pin/parameter.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index dbeba1b65..05a8cb490 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -178,6 +178,7 @@ def return_type @return_type = ComplexType::UNDEFINED found = param_tag @return_type = ComplexType.try_parse(*found.types) unless found.nil? or found.types.nil? + # @sg-ignore flow sensitive typing should be able to handle redefinition if @return_type.undefined? if decl == :restarg @return_type = ComplexType.try_parse('::Array') From d6ac3425e115b47527baf688a51755eaf8d21f0a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 12 Jan 2026 05:14:39 -0500 Subject: [PATCH 879/930] Fix rspec checks to run on all types of PRs --- .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 1f2c6d587..ad0d5c20b 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 From c5dbf24746b45336887092bd8ca9af57ed9a6419 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 12 Jan 2026 05:45:57 -0500 Subject: [PATCH 880/930] Fix merge --- lib/solargraph/doc_map.rb | 6 +++--- lib/solargraph/pin_cache.rb | 9 ++++++--- lib/solargraph/rbs_map/stdlib_map.rb | 8 +++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 5e48de70f..faf810f51 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -66,19 +66,19 @@ def initialize(requires, preferences, workspace = nil) # @return [void] def cache_all!(out, rebuild: false) load_serialized_gem_pins - PinCache.cache_core(out: out) unless PinCache.core? + PinCache.cache_core(out: out) unless PinCache.core? || rebuild 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) + cache(gemspec, rebuild: rebuild, 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) + PinCache.cache_all_stdlibs(rebuild: rebuild, out: out) out&.puts "Documentation cached for core, standard library and gems." diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 610994fc1..d2ca36c78 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -201,12 +201,15 @@ def cache_core out: nil RbsMap::CoreMap.new.cache_core(out: out) end - # @param out [IO, nil] output stream for logging + # @param rebuild [Boolean] build pins regardless of whether we + # have cached them already + # @param out [IO, nil] output stream + # for logging # # @return [void] - def cache_all_stdlibs out: $stderr + def cache_all_stdlibs rebuild: false, out: $stderr possible_stdlibs.each do |stdlib| - RbsMap::StdlibMap.new(stdlib, out: out) + RbsMap::StdlibMap.new(stdlib, rebuild: rebuild, out: out) end end diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index e7891bfe3..e11f8a06c 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -16,17 +16,19 @@ def log_caching lib, out: $stderr out&.puts("Caching RBS pins for standard library #{lib.name}") end + # @param rebuild [Boolean] build pins regardless of whether we + # have cached them already # @param library [String] # @param out [IO, nil] where to log messages - def initialize library, out: $stderr + def initialize library, rebuild: false, out: $stderr cached_pins = PinCache.deserialize_stdlib_require library - if cached_pins + if cached_pins && !rebuild @pins = cached_pins @resolved = true @loaded = true logger.debug { "Deserialized #{cached_pins.length} cached pins for stdlib require #{library.inspect}" } else - super + super(library, out: out) unless resolved? @pins = [] logger.debug { "StdlibMap could not resolve #{library.inspect}" } From 40b62b481acaa542f61418b5d67c6de7f329c777 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 12 Jan 2026 06:59:05 -0500 Subject: [PATCH 881/930] Fix merge --- 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 1cae74821..904edff8e 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -236,7 +236,7 @@ def self.load_with_cache directory, out = $stderr, loose_unions: true return api_map end - api_map.cache_all_for_doc_map!(out) + api_map.cache_all_for_doc_map!(out: out) load(directory, loose_unions: loose_unions) end From b9ebd26c6a21204d34f5a583d1cf98d64c886132 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 12 Jan 2026 07:32:41 -0500 Subject: [PATCH 882/930] Fix merge --- 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 764298177..a75428d22 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -146,7 +146,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!(out: $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 3a71df199..78b71ce3b 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -166,7 +166,7 @@ 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(pin_cache).to receive(:cache_all_stdlibs).with(out: nil, rebuild: false) allow(Solargraph::PinCache).to receive_messages(core?: true, possible_stdlibs: []) From 95ed300a4fceceaf4b0b95baef7701a1e0bc30aa Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 13 Jan 2026 05:08:14 -0500 Subject: [PATCH 883/930] Avoid rbs pollution (#1146) We were using the sig/shims directory for some internally helpful shims; unfortunately that exported them during gem installs, causing https://github.com/castwide/solargraph/issues/1144 --- {sig => rbs}/shims/ast/0/node.rbs | 0 {sig => rbs}/shims/ast/2.4/.rbs_meta.yaml | 0 {sig => rbs}/shims/ast/2.4/ast.rbs | 0 {sig => rbs}/shims/parser/3.2.0.1/builders/default.rbs | 0 {sig => rbs}/shims/parser/3.2.0.1/manifest.yaml | 0 {sig => rbs}/shims/parser/3.2.0.1/parser.rbs | 0 {sig => rbs}/shims/parser/3.2.0.1/polyfill.rbs | 0 {sig => rbs}/shims/thor/1.2.0.1/.rbs_meta.yaml | 0 {sig => rbs}/shims/thor/1.2.0.1/manifest.yaml | 0 {sig => rbs}/shims/thor/1.2.0.1/thor.rbs | 0 rbs_collection.yaml | 2 +- 11 files changed, 1 insertion(+), 1 deletion(-) rename {sig => rbs}/shims/ast/0/node.rbs (100%) rename {sig => rbs}/shims/ast/2.4/.rbs_meta.yaml (100%) rename {sig => rbs}/shims/ast/2.4/ast.rbs (100%) rename {sig => rbs}/shims/parser/3.2.0.1/builders/default.rbs (100%) rename {sig => rbs}/shims/parser/3.2.0.1/manifest.yaml (100%) rename {sig => rbs}/shims/parser/3.2.0.1/parser.rbs (100%) rename {sig => rbs}/shims/parser/3.2.0.1/polyfill.rbs (100%) rename {sig => rbs}/shims/thor/1.2.0.1/.rbs_meta.yaml (100%) rename {sig => rbs}/shims/thor/1.2.0.1/manifest.yaml (100%) rename {sig => rbs}/shims/thor/1.2.0.1/thor.rbs (100%) diff --git a/sig/shims/ast/0/node.rbs b/rbs/shims/ast/0/node.rbs similarity index 100% rename from sig/shims/ast/0/node.rbs rename to rbs/shims/ast/0/node.rbs diff --git a/sig/shims/ast/2.4/.rbs_meta.yaml b/rbs/shims/ast/2.4/.rbs_meta.yaml similarity index 100% rename from sig/shims/ast/2.4/.rbs_meta.yaml rename to rbs/shims/ast/2.4/.rbs_meta.yaml diff --git a/sig/shims/ast/2.4/ast.rbs b/rbs/shims/ast/2.4/ast.rbs similarity index 100% rename from sig/shims/ast/2.4/ast.rbs rename to rbs/shims/ast/2.4/ast.rbs diff --git a/sig/shims/parser/3.2.0.1/builders/default.rbs b/rbs/shims/parser/3.2.0.1/builders/default.rbs similarity index 100% rename from sig/shims/parser/3.2.0.1/builders/default.rbs rename to rbs/shims/parser/3.2.0.1/builders/default.rbs diff --git a/sig/shims/parser/3.2.0.1/manifest.yaml b/rbs/shims/parser/3.2.0.1/manifest.yaml similarity index 100% rename from sig/shims/parser/3.2.0.1/manifest.yaml rename to rbs/shims/parser/3.2.0.1/manifest.yaml diff --git a/sig/shims/parser/3.2.0.1/parser.rbs b/rbs/shims/parser/3.2.0.1/parser.rbs similarity index 100% rename from sig/shims/parser/3.2.0.1/parser.rbs rename to rbs/shims/parser/3.2.0.1/parser.rbs diff --git a/sig/shims/parser/3.2.0.1/polyfill.rbs b/rbs/shims/parser/3.2.0.1/polyfill.rbs similarity index 100% rename from sig/shims/parser/3.2.0.1/polyfill.rbs rename to rbs/shims/parser/3.2.0.1/polyfill.rbs diff --git a/sig/shims/thor/1.2.0.1/.rbs_meta.yaml b/rbs/shims/thor/1.2.0.1/.rbs_meta.yaml similarity index 100% rename from sig/shims/thor/1.2.0.1/.rbs_meta.yaml rename to rbs/shims/thor/1.2.0.1/.rbs_meta.yaml diff --git a/sig/shims/thor/1.2.0.1/manifest.yaml b/rbs/shims/thor/1.2.0.1/manifest.yaml similarity index 100% rename from sig/shims/thor/1.2.0.1/manifest.yaml rename to rbs/shims/thor/1.2.0.1/manifest.yaml diff --git a/sig/shims/thor/1.2.0.1/thor.rbs b/rbs/shims/thor/1.2.0.1/thor.rbs similarity index 100% rename from sig/shims/thor/1.2.0.1/thor.rbs rename to rbs/shims/thor/1.2.0.1/thor.rbs diff --git a/rbs_collection.yaml b/rbs_collection.yaml index 898239cac..d94e1c896 100644 --- a/rbs_collection.yaml +++ b/rbs_collection.yaml @@ -2,7 +2,7 @@ sources: - type: local name: shims - path: sig/shims + path: rbs/shims - type: git name: ruby/gem_rbs_collection From ce12a5f63cba2e72b04453c756751a3fafb2d0a0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 13 Jan 2026 05:11:44 -0500 Subject: [PATCH 884/930] Fix 'solargraph pin --references ClassName' private method call (#1150) --- lib/solargraph/api_map.rb | 12 ++++++------ spec/shell_spec.rb | 32 +++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index cc3031ea5..6c0da316e 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -711,6 +711,12 @@ 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 + store.qualify_superclass fq_sub_tag + end + private # A hash of source maps with filename keys. @@ -804,12 +810,6 @@ def path_macros @path_macros ||= {} end - # @param fq_sub_tag [String] - # @return [String, nil] - def qualify_superclass fq_sub_tag - store.qualify_superclass fq_sub_tag - end - # Get the namespace's type (Class or Module). # # @param fqns [String] A fully qualified namespace diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index b9dc6b327..3b8dc0426 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -57,7 +57,37 @@ def bundle_exec(*cmd) end end - describe 'pin' do + describe 'pin on a class' do + let(:api_map) { instance_double(Solargraph::ApiMap) } + let(:string_pin) { instance_double(Solargraph::Pin::Namespace, name: 'String') } + + before do + allow(Solargraph::ApiMap).to receive(:load_with_cache).and_return(api_map) + allow(Solargraph::Pin::Namespace).to receive(:===).with(string_pin).and_return(true) + allow(string_pin).to receive(:return_type).and_return(Solargraph::ComplexType.parse('String')) + allow(api_map).to receive(:get_path_pins).with('String').and_return([string_pin]) + end + + context 'with --references option' do + let(:object_pin) { instance_double(Solargraph::Pin::Namespace, name: 'Object') } + + before do + allow(Solargraph::Pin::Namespace).to receive(:===).with(object_pin).and_return(true) + allow(api_map).to receive(:qualify_superclass).with('String').and_return('Object') + allow(api_map).to receive(:get_path_pins).with('Object').and_return([object_pin]) + end + + it 'prints a pin with info' do + out = capture_both do + shell.options = { references: true } + shell.pin('String') + end + expect(out).to include('# Superclass:') + end + end + end + + describe 'pin on a method' do let(:api_map) { instance_double(Solargraph::ApiMap) } let(:to_s_pin) { instance_double(Solargraph::Pin::Method, return_type: Solargraph::ComplexType.parse('String')) } From 620f60afedd5a76ac4e955c1fd1fdbec4cf0c8d7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 13 Jan 2026 18:22:16 -0500 Subject: [PATCH 885/930] Manual rubocop fixes Some hand-changed fixes for RuboCop issues --- .rubocop.yml | 4 + .rubocop_todo.yml | 26 ----- lib/solargraph/complex_type.rb | 4 - lib/solargraph/location.rb | 4 - lib/solargraph/pin/base_variable.rb | 48 +------- lib/solargraph/pin/callable.rb | 5 - lib/solargraph/pin/signature.rb | 2 - lib/solargraph/rbs_map.rb | 5 - lib/solargraph/source/chain/link.rb | 10 -- lib/solargraph/workspace.rb | 16 --- spec/api_map/config_spec.rb | 2 +- spec/api_map_spec.rb | 6 +- spec/complex_type_spec.rb | 28 ++--- spec/doc_map_spec.rb | 2 +- spec/language_server/host/diagnoser_spec.rb | 2 +- .../host/message_worker_spec.rb | 2 +- spec/language_server/host_spec.rb | 2 +- .../message/completion_item/resolve_spec.rb | 4 +- .../extended/check_gem_version_spec.rb | 5 +- .../message/text_document/formatting_spec.rb | 2 +- .../did_change_watched_files_spec.rb | 2 +- spec/language_server/protocol_spec.rb | 15 +-- spec/library_spec.rb | 2 +- spec/parser/node_methods_spec.rb | 108 ++---------------- spec/pin/block_spec.rb | 9 +- spec/pin/method_spec.rb | 8 +- spec/pin/parameter_spec.rb | 6 +- spec/pin/symbol_spec.rb | 8 +- spec/source/chain/call_spec.rb | 9 +- spec/source/chain/class_variable_spec.rb | 2 +- spec/source/cursor_spec.rb | 8 +- spec/source/source_chainer_spec.rb | 10 +- spec/source_map/clip_spec.rb | 98 +--------------- spec/source_map/mapper_spec.rb | 2 +- spec/type_checker/levels/alpha_spec.rb | 14 +++ spec/type_checker/levels/normal_spec.rb | 2 +- spec/type_checker/levels/strict_spec.rb | 25 +--- spec/type_checker/levels/strong_spec.rb | 32 +++++- spec/type_checker/levels/typed_spec.rb | 29 +++-- spec/workspace_spec.rb | 6 +- 40 files changed, 147 insertions(+), 427 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 51b022f51..672b41637 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -31,6 +31,10 @@ Style/MethodDefParentheses: Layout/EmptyLineAfterGuardClause: Enabled: false +Lint/EmptyClass: + Exclude: + - spec/fixtures/**/*.rb + Lint/UnusedMethodArgument: AllowUnusedKeywordArguments: true diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0616ea8d2..772ccc04a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -263,7 +263,6 @@ Layout/SpaceInsideParens: Layout/TrailingWhitespace: Exclude: - 'lib/solargraph/language_server/message/client/register_capability.rb' - - 'spec/api_map/config_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedMethods, AllowedPatterns. @@ -317,13 +316,6 @@ Lint/DuplicateBranch: - 'lib/solargraph/pin/base.rb' - 'lib/solargraph/rbs_map/conversions.rb' -Lint/DuplicateMethods: - Enabled: false - -# Configuration parameters: AllowComments. -Lint/EmptyClass: - Enabled: false - # Configuration parameters: AllowComments. Lint/EmptyFile: Exclude: @@ -569,11 +561,6 @@ RSpec/BeforeAfterAll: - 'spec/language_server/host/dispatch_spec.rb' - 'spec/language_server/protocol_spec.rb' -# Configuration parameters: Prefixes, AllowedPatterns. -# Prefixes: when, with, without -RSpec/ContextWording: - Enabled: false - # Configuration parameters: IgnoredMetadata. RSpec/DescribeClass: Exclude: @@ -651,11 +638,6 @@ RSpec/MultipleExpectations: RSpec/NestedGroups: Max: 4 -# Configuration parameters: AllowedPatterns. -# AllowedPatterns: ^expect_, ^assert_ -RSpec/NoExpectationExample: - Enabled: false - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: not_to, to_not @@ -689,13 +671,9 @@ RSpec/RemoveConst: Exclude: - 'spec/diagnostics/rubocop_helpers_spec.rb' -RSpec/RepeatedDescription: - Enabled: false - RSpec/RepeatedExample: Exclude: - 'spec/api_map_spec.rb' - - 'spec/parser/node_methods_spec.rb' - 'spec/source/cursor_spec.rb' - 'spec/source_map/clip_spec.rb' - 'spec/type_checker/levels/strict_spec.rb' @@ -705,10 +683,6 @@ RSpec/ScatteredLet: Exclude: - 'spec/complex_type_spec.rb' -# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. -RSpec/VerifiedDoubles: - Enabled: false - Security/MarshalLoad: Exclude: - 'lib/solargraph/pin_cache.rb' diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index c67f9c2a4..fa481b3cc 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -351,10 +351,6 @@ def rooted? attr_reader :items - def rooted? - @items.all?(&:rooted?) - end - # @param exclude_types [ComplexType, nil] # @param api_map [ApiMap] # @return [ComplexType, self] diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index a7b15e42f..d51458470 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -46,10 +46,6 @@ def contain? location range.contain?(location.range.start) && range.contain?(location.range.ending) && filename == location.filename end - def inspect - "<#{self.class.name}: filename=#{filename}, range=#{range.inspect}>" - end - def to_s inspect end diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 1e021f86b..0a3132b29 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -45,9 +45,8 @@ class BaseVariable < Base # @see https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types # @see https://en.wikipedia.org/wiki/Intersection_type#TypeScript_example # @param presence [Range, nil] - # @param presence_certain [Boolean] def initialize assignment: nil, assignments: [], mass_assignment: nil, - presence: nil, presence_certain: false, return_type: nil, + presence: nil, return_type: nil, intersection_return_type: nil, exclude_return_type: nil, **splat super(**splat) @@ -58,7 +57,6 @@ def initialize assignment: nil, assignments: [], mass_assignment: nil, @intersection_return_type = intersection_return_type @exclude_return_type = exclude_return_type @presence = presence - @presence_certain = presence_certain end # @param presence [Range] @@ -96,8 +94,7 @@ def combine_with(other, attrs={}) return_type: combine_return_type(other), intersection_return_type: combine_types(other, :intersection_return_type), exclude_return_type: combine_types(other, :exclude_return_type), - presence: combine_presence(other), - presence_certain: combine_presence_certain(other) + presence: combine_presence(other) }) super(other, new_attrs) end @@ -111,16 +108,6 @@ def combine_mass_assignment(other) mass_assignment || other.mass_assignment end - # If a certain pin is being combined with an uncertain pin, we - # end up with a certain result - # - # @param other [self] - # - # @return [Boolean] - def combine_presence_certain(other) - presence_certain? || other.presence_certain? - end - # @return [Parser::AST::Node, nil] def assignment @assignment ||= assignments.last @@ -298,10 +285,6 @@ def visible_at?(other_closure, other_loc) visible_in_closure?(other_closure) end - def presence_certain? - @presence_certain - end - protected attr_accessor :exclude_return_type, :intersection_return_type @@ -322,33 +305,6 @@ def adjust_type(api_map, raw_return_type) minus_exclusions.intersect_with qualified_intersection, api_map end - # @param other [self] - # @return [Pin::Closure, nil] - def combine_closure(other) - return closure if self.closure == other.closure - - # choose first defined, as that establishes the scope of the variable - if closure.nil? || other.closure.nil? - Solargraph.assert_or_log(:varible_closure_missing) do - "One of the local variables being combined is missing a closure: " \ - "#{self.inspect} vs #{other.inspect}" - end - return closure || other.closure - end - - # @sg-ignore Need to add nil check here - if closure.location.nil? || other.closure.location.nil? - # @sg-ignore Need to add nil check here - return closure.location.nil? ? other.closure : closure - end - - # if filenames are different, this will just pick one - # @sg-ignore flow sensitive typing needs to handle attrs - return closure if closure.location <= other.closure.location - - other.closure - end - # See if this variable is visible within 'viewing_closure' # # @param viewing_closure [Pin::Closure] diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 034cef03f..e4c35f5f3 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -246,11 +246,6 @@ 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/signature.rb b/lib/solargraph/pin/signature.rb index 0a6dbbafb..60967274f 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -18,8 +18,6 @@ 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? diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index a04568587..d5a4d93ed 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -176,11 +176,6 @@ def self.load library private - # @return [RBS::EnvironmentLoader] - def loader - @loader ||= RBS::EnvironmentLoader.new(core_root: nil, repository: repository) - end - # @return [Conversions] def conversions @conversions ||= Conversions.new(loader: loader) diff --git a/lib/solargraph/source/chain/link.rb b/lib/solargraph/source/chain/link.rb index 344f7affd..56df1d135 100644 --- a/lib/solargraph/source/chain/link.rb +++ b/lib/solargraph/source/chain/link.rb @@ -44,12 +44,6 @@ def resolve api_map, name_pin, locals [] end - # debugging description of contents; not for machine use - # @return [String] - def desc - word - end - def to_s desc end @@ -87,10 +81,6 @@ def desc word end - def inspect - "#<#{self.class} - `#{self.desc}`>" - end - include Logging protected diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index f3de99242..01c58ef99 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -80,14 +80,6 @@ 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 @@ -251,14 +243,6 @@ def all_gemspecs_from_bundle gemspecs.all_gemspecs_from_bundle 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 [StringIO, IO, nil] output stream for logging # @param rebuild [Boolean] whether to rebuild the pins even if they are cached # @return [void] diff --git a/spec/api_map/config_spec.rb b/spec/api_map/config_spec.rb index 5790265cd..79ad924c9 100644 --- a/spec/api_map/config_spec.rb +++ b/spec/api_map/config_spec.rb @@ -49,7 +49,7 @@ it "overrides global config with workspace config" do File.write(File.join(workspace_path, 'foo.rb'), 'test') File.write(File.join(workspace_path, 'bar.rb'), 'test') - + File.open(File.join(workspace_path, '.solargraph.yml'), 'w') do |file| file.puts "include:" file.puts " - foo.rb" diff --git a/spec/api_map_spec.rb b/spec/api_map_spec.rb index f76adb078..de0cdfada 100755 --- a/spec/api_map_spec.rb +++ b/spec/api_map_spec.rb @@ -536,15 +536,11 @@ module Includer end # @todo Qualify methods might not accept parametrized types anymore - xit 'handles multiple type parameters without losing cache coherence' do + it 'handles multiple type parameters without losing cache coherence' do tag = @api_map.qualify('Array') expect(tag).to eq('Array') tag = @api_map.qualify('Array') expect(tag).to eq('Array') - end - - # @todo Qualify methods might not accept parametrized types anymore - xit 'handles multiple type parameters without losing cache coherence' do tag = @api_map.qualify('Hash{Integer => String}') expect(tag).to eq('Hash{Integer => String}') end diff --git a/spec/complex_type_spec.rb b/spec/complex_type_spec.rb index 8a5b273dd..36bb335b1 100644 --- a/spec/complex_type_spec.rb +++ b/spec/complex_type_spec.rb @@ -1,5 +1,5 @@ describe 'YARD type specifier list parsing' do - context 'in compliance with https://www.rubydoc.info/gems/yard/file/docs/Tags.md#type-list-conventions' do + context 'with https://www.rubydoc.info/gems/yard/file/docs/Tags.md#type-list-conventions compliance' do # Types Specifier List # # In some cases, a tag will allow for a "types specifier list"; this @@ -121,7 +121,7 @@ # types. This type does not exist in Ruby, however. it 'typifies Booleans' do - api_map = double(Solargraph::ApiMap, qualify: nil) + api_map = instance_double(Solargraph::ApiMap, qualify: nil) type = Solargraph::ComplexType.parse('::Boolean') qualified = type.qualify(api_map) expect(qualified.tag).to eq('Boolean') @@ -340,7 +340,7 @@ xit 'understands reference tags' end - context 'offers machine users error messages given non-sensical types' do + context 'when given non-sensical types by machine users' do it 'raises ComplexTypeError for unmatched brackets' do expect do Solargraph::ComplexType.parse('Array and Module<> from type' do + context 'when offering type queries orthogonal to YARD spec' do + context 'when defining namespace concept which strips Class<> and Module<> from type' do # # Solargraph extensions and library features # @@ -409,7 +409,7 @@ end end - context 'simplifies type representation on output' do + context 'when simplifying type representation on output' do it 'throws away other types when in union with an undefined' do type = Solargraph::ComplexType.parse('Symbol, String, Array(Integer, Integer), undefined') expect(type.to_s).to eq('undefined') @@ -447,7 +447,7 @@ end end - context 'defines rooted and unrooted concept' do + context 'when defining rooted and unrooted concept' do it 'identify rooted types' do types = Solargraph::ComplexType.parse '::Array' expect(types.map(&:rooted?)).to eq([true]) @@ -467,7 +467,7 @@ end end - context 'allows users to define their own generic types' do + context 'when allowing users to define their own generic types' do it 'recognizes param types' do type = Solargraph::ComplexType.parse('generic') expect(type).to be_generic @@ -539,7 +539,7 @@ ] UNIQUE_METHOD_GENERIC_TESTS.each do |tag, context_type_tag, unfrozen_input_map, expected_tag, expected_output_map| - context "resolves #{tag} with context #{context_type_tag} and existing resolved generics #{unfrozen_input_map}" do + context "when resolveing #{tag} with context #{context_type_tag} and existing resolved generics #{unfrozen_input_map}" do let(:complex_type) { Solargraph::ComplexType.parse(tag) } let(:unique_type) { complex_type.first } @@ -562,7 +562,7 @@ end end - context 'identifies type of parameter syntax used' do + context 'when identifying type of parameter syntax used' do it 'raises NoMethodError for missing methods' do type = Solargraph::ComplexType.parse('String') expect { type.undefined_method }.to raise_error(NoMethodError) @@ -589,9 +589,9 @@ end end - context "'qualifies' types by resolving relative references to types to absolute references (fully qualified types)" do + context "when 'qualifying' types by resolving relative references to types to absolute references (fully qualified types)" do it 'returns undefined for unqualified types' do - api_map = double(Solargraph::ApiMap, qualify: nil) + api_map = intance_double(Solargraph::ApiMap, qualify: nil) type = Solargraph::ComplexType.parse('UndefinedClass') qualified = type.qualify(api_map) expect(qualified).to be_undefined @@ -599,7 +599,7 @@ end end - context 'allows list-of-types to be destructively cast down to a single type' do + context 'when allowing list-of-types to be destructively cast down to a single type' do it 'returns the first type when multiple were parsed with #tag' do type = Solargraph::ComplexType.parse('String, Array') expect(type.tag).to eq('String') @@ -607,7 +607,7 @@ end end - context "supports arbitrary combinations of the above syntax and features" do + context "when supporting arbitrary combinations of the above syntax and features" do it 'returns string representations of the entire type array' do type = Solargraph::ComplexType.parse('String', 'Array') expect(type.to_s).to eq('String, Array') diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 7c541da27..4a6ce1f4b 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -33,7 +33,7 @@ end end - context 'understands rspec + rspec-mocks require pattern' do + context 'when understanding rspec + rspec-mocks require pattern' do let(:requires) do ['rspec-mocks'] end diff --git a/spec/language_server/host/diagnoser_spec.rb b/spec/language_server/host/diagnoser_spec.rb index 697d352bd..4e9a7208f 100644 --- a/spec/language_server/host/diagnoser_spec.rb +++ b/spec/language_server/host/diagnoser_spec.rb @@ -1,6 +1,6 @@ describe Solargraph::LanguageServer::Host::Diagnoser do it "diagnoses on ticks" do - host = double(Solargraph::LanguageServer::Host, options: { 'diagnostics' => true }, synchronizing?: false) + host = instance_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' diff --git a/spec/language_server/host/message_worker_spec.rb b/spec/language_server/host/message_worker_spec.rb index 5e5bef481..d5ce91da6 100644 --- a/spec/language_server/host/message_worker_spec.rb +++ b/spec/language_server/host/message_worker_spec.rb @@ -1,6 +1,6 @@ describe Solargraph::LanguageServer::Host::MessageWorker do it "handle requests on queue" do - host = double(Solargraph::LanguageServer::Host) + host = instance_double(Solargraph::LanguageServer::Host) message = {'method' => '$/example'} allow(host).to receive(:receive).with(message).and_return(nil) diff --git a/spec/language_server/host_spec.rb b/spec/language_server/host_spec.rb index 5635680e3..6700b41cb 100644 --- a/spec/language_server/host_spec.rb +++ b/spec/language_server/host_spec.rb @@ -83,7 +83,7 @@ it "handles DiagnosticsErrors" do host = Solargraph::LanguageServer::Host.new - library = double(:Library) + library = instance_double(Solargraph::Library) allow(library).to receive(:diagnose).and_raise(Solargraph::DiagnosticsError) allow(library).to receive(:contain?).and_return(true) allow(library).to receive(:synchronized?).and_return(true) diff --git a/spec/language_server/message/completion_item/resolve_spec.rb b/spec/language_server/message/completion_item/resolve_spec.rb index 3ae66dbda..2afd201f9 100644 --- a/spec/language_server/message/completion_item/resolve_spec.rb +++ b/spec/language_server/message/completion_item/resolve_spec.rb @@ -9,7 +9,7 @@ visibility: :public, parameters: [] ) - host = double(Solargraph::LanguageServer::Host, locate_pins: [pin], probe: pin, detail: nil, options: { 'enablePages' => true }) + host = intance_double(Solargraph::LanguageServer::Host, locate_pins: [pin], probe: pin, detail: nil, options: { 'enablePages' => true }) resolve = Solargraph::LanguageServer::Message::CompletionItem::Resolve.new(host, { 'params' => pin.completion_item }) @@ -25,7 +25,7 @@ name: '@bar', comments: '' ) - host = double(Solargraph::LanguageServer::Host, locate_pins: [pin], probe: pin, detail: nil) + host = instance_double(Solargraph::LanguageServer::Host, locate_pins: [pin], probe: pin, detail: nil) resolve = Solargraph::LanguageServer::Message::CompletionItem::Resolve.new(host, { 'params' => pin.completion_item }) diff --git a/spec/language_server/message/extended/check_gem_version_spec.rb b/spec/language_server/message/extended/check_gem_version_spec.rb index 935917442..4bc914bc9 100644 --- a/spec/language_server/message/extended/check_gem_version_spec.rb +++ b/spec/language_server/message/extended/check_gem_version_spec.rb @@ -1,7 +1,8 @@ describe Solargraph::LanguageServer::Message::Extended::CheckGemVersion do before :each do - version = double(:GemVersion, version: Gem::Version.new('1.0.0')) - Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = double(:fetcher, search_for_dependency: [version]) + version = instance_double(:GemVersion, version: Gem::Version.new('1.0.0')) + Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = + instance_double(Gem::SpecFetcher, search_for_dependency: [version]) end after :each do diff --git a/spec/language_server/message/text_document/formatting_spec.rb b/spec/language_server/message/text_document/formatting_spec.rb index 10797e8db..778046005 100644 --- a/spec/language_server/message/text_document/formatting_spec.rb +++ b/spec/language_server/message/text_document/formatting_spec.rb @@ -1,6 +1,6 @@ describe Solargraph::LanguageServer::Message::TextDocument::Formatting do it 'gracefully handles empty files' do - host = double(:Host, read_text: '', formatter_config: {}) + host = instance_double(Solargraph::LanguageServer::Host, read_text: '', formatter_config: {}) request = { 'params' => { 'textDocument' => { diff --git a/spec/language_server/message/workspace/did_change_watched_files_spec.rb b/spec/language_server/message/workspace/did_change_watched_files_spec.rb index b78aa06d3..034e66726 100644 --- a/spec/language_server/message/workspace/did_change_watched_files_spec.rb +++ b/spec/language_server/message/workspace/did_change_watched_files_spec.rb @@ -81,7 +81,7 @@ end it 'sets errors for invalid change types' do - host = double(Solargraph::LanguageServer::Host, catalog: nil) + host = instance_double(Solargraph::LanguageServer::Host, catalog: nil) allow(host).to receive(:create) allow(host).to receive(:delete) changed = Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles.new(host, { diff --git a/spec/language_server/protocol_spec.rb b/spec/language_server/protocol_spec.rb index e88fb9c05..bb3c6fe37 100644 --- a/spec/language_server/protocol_spec.rb +++ b/spec/language_server/protocol_spec.rb @@ -44,8 +44,9 @@ def stop end before :each do - version = double(:GemVersion, version: Gem::Version.new('1.0.0')) - Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = double(:fetcher, search_for_dependency: [version]) + version = insance_double(Gem::Version, version: Gem::Version.new('1.0.0')) + Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = + instance_double(Gem::SpecFetcher, search_for_dependency: [version]) end after :each do @@ -149,7 +150,7 @@ def bar baz ] } response = @protocol.response - # @todo What to expect? + expect(response).not_to be_nil end it "handles textDocument/completion" do @@ -207,14 +208,6 @@ def bar baz expect(response['result']['documentation']).not_to be_empty end - it "handles workspace/symbol" do - @protocol.request 'workspace/symbol', { - 'query' => 'test' - } - response = @protocol.response - expect(response['error']).to be_nil - end - it "handles textDocument/definition" do sleep 0.5 # HACK: Give the Host::Sources thread time to work @protocol.request 'textDocument/definition', { diff --git a/spec/library_spec.rb b/spec/library_spec.rb index f7daafdf4..2e1017502 100644 --- a/spec/library_spec.rb +++ b/spec/library_spec.rb @@ -623,7 +623,7 @@ def bar; end end end - context 'unsynchronized' do + context 'when unsynchronized' do let(:library) { Solargraph::Library.load File.absolute_path(File.join('spec', 'fixtures', 'workspace')) } let(:good_file) { File.join(library.workspace.directory, 'lib', 'thing.rb') } let(:bad_file) { File.join(library.workspace.directory, 'lib', 'not_a_thing.rb') } diff --git a/spec/parser/node_methods_spec.rb b/spec/parser/node_methods_spec.rb index 867180bc6..0c4c5f325 100644 --- a/spec/parser/node_methods_spec.rb +++ b/spec/parser/node_methods_spec.rb @@ -62,17 +62,19 @@ def parse source expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(false_ast)).to eq '::Boolean' end - it "handles return nodes with implicit nil values" do + it "handles empty return nodes with implicit nil values" do node = parse(%( return if true )) rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) # @todo Should there be two returns, the second being nil? expect(rets.map(&:to_s)).to eq(['(nil)', '(nil)']) + # The expectation is changing from previous versions. If conditions + # have an implicit else branch, so this node should return [nil, nil]. expect(rets.length).to eq(2) end - it "handles return nodes with implicit nil values" do + it "handles local return nodes with implicit nil values" do node = parse(%( return bla if true )) @@ -81,7 +83,7 @@ def parse source expect(rets.length).to eq(2) end - it 'handles return nodes from case statements' do + it 'handles boolean return nodes from case statements without else' do node = parse(%( case x when 100 @@ -131,33 +133,15 @@ def parse source end it "handles return nodes in reduceable (begin) nodes" do - # @todo Temporarily disabled. Result is 3 nodes instead of 2. - # node = parse(%( - # begin - # return if true - # end - # )) - # rets = Solargraph::Parser::NodeMethods.returns_from(node) - # expect(rets.length).to eq(2) - end + pending('Temporarily disabled. Result is 3 nodes instead of 2.') - it "handles return nodes after other nodes" do node = parse(%( - x = 1 - return x - )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.length).to eq(1) - end - - it "handles return nodes with unreachable code" do - node = parse(%( - x = 1 - return x - y + begin + return if true + end )) rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.length).to eq(1) + expect(rets.length).to eq(2) end it "handles conditional returns with following code" do @@ -170,30 +154,6 @@ def parse source expect(rets.length).to eq(2) end - it "handles return nodes with reduceable code" do - node = parse(%( - return begin - x if foo - y - end - )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.length).to eq(1) - end - - it "handles top 'and' nodes" do - node = parse('1 && "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.length).to eq(1) - expect(rets[0].type.to_s.downcase).to eq('and') - end - - it "handles top 'or' nodes" do - node = parse('1 || "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.length).to eq(1) - end - it "handles nested 'and' nodes" do node = parse('return 1 && "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) @@ -243,16 +203,6 @@ def parse source expect(rets.map(&:type)).to eq([:lvar, :nil]) end - it "handles return nodes with implicit nil values" do - node = parse(%( - return if true - )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - # The expectation is changing from previous versions. If conditions - # have an implicit else branch, so this node should return [nil, nil]. - expect(rets.length).to eq(2) - end - it "handles return nodes with implicit nil values" do node = parse(%( return bla if true @@ -261,17 +211,6 @@ def parse source expect(rets.map(&:type)).to eq([:send, :nil]) end - it "handles return nodes in reduceable (begin) nodes" do - # @todo Temporarily disabled. Result is 3 nodes instead of 2 in legacy. - # node = parse(%( - # begin - # return if true - # end - # )) - # rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - # expect(rets.length).to eq(2) - end - it "handles return nodes after other nodes" do node = parse(%( x = 1 @@ -357,31 +296,6 @@ def parse source expect(rets.map(&:type)).to eq([:int, :str]) end - it 'finds return nodes in blocks' do - node = parse(%( - array.each do |item| - return item if foo - end - )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:block, :lvar]) - # expect(rets[1].type).to eq(:DVAR) - end - - it 'returns nested return blocks' do - node = parse(%( - if foo - array.each do |item| - return item if foo - end - end - nil - )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:lvar, :nil]) - # expect(rets[0].type).to eq(:DVAR) - end - it 'handles return nodes from case statements' do node = parse(%( case 1 @@ -394,7 +308,7 @@ def parse source expect(rets.map(&:type)).to eq([:str, :str]) end - it 'handles return nodes from case statements without else' do + it 'handles String return nodes from case statements without else' do node = parse(%( case 1 when 1 diff --git a/spec/pin/block_spec.rb b/spec/pin/block_spec.rb index 0ca36e950..be62e8acd 100644 --- a/spec/pin/block_spec.rb +++ b/spec/pin/block_spec.rb @@ -1,7 +1,10 @@ describe Solargraph::Pin::Block do + let(:foo) { instance_double(Solargraph::Pin::Parameter, name: 'foo') } + let(:bar) { instance_double(Solargraph::Pin::Parameter, name: 'bar') } + let(:block) { instance_double(Solargraph::Pin::Parameter, name: 'block') } + it 'strips prefixes from parameter names' do - # @todo Method parameters are pins now - # pin = Solargraph::Pin::Block.new(args: ['foo', '*bar', '&block']) - # expect(pin.parameter_names).to eq(['foo', 'bar', 'block']) + pin = Solargraph::Pin::Block.new(args: [foo, bar, block]) + expect(pin.parameter_names).to eq(['foo', 'bar', 'block']) end end diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index c2ef40448..b255a53fa 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -264,12 +264,6 @@ def bar expect(type.tag).to eq('Boolean') end - it 'strips prefixes from parameter names' do - # @todo Method pin parameters are pins now - # pin = Solargraph::Pin::Method.new(args: ['foo', '*bar', '&block']) - # expect(pin.parameter_names).to eq(['foo', 'bar', 'block']) - end - it 'does not include yielded blocks in return nodes' do source = Solargraph::Source.load_string(%( class Foo @@ -512,7 +506,7 @@ def bar param1, param2 expect(pin.docstring.tags(:param).map(&:type)).to eq(['String', 'Integer']) end - context 'as attribute' do + context 'when attr_reader is used' do it 'is a kind of attribute/property' do source = Solargraph::Source.load_string(%( class Foo diff --git a/spec/pin/parameter_spec.rb b/spec/pin/parameter_spec.rb index 14c39f3fe..8605bd441 100644 --- a/spec/pin/parameter_spec.rb +++ b/spec/pin/parameter_spec.rb @@ -367,7 +367,7 @@ def use_string str expect(type.tag).to eq('String') end - context 'for instance methods' do + context 'when used in an instance methods' do it 'infers types from optarg values' do source = Solargraph::Source.load_string(%( class Example @@ -397,7 +397,7 @@ def foo bar: 'bar' end end - context 'for class methods' do + context 'when used in an class method' do it 'infers types from optarg values' do source = Solargraph::Source.load_string(%( class Example @@ -441,7 +441,7 @@ def self.foo bar: Hash.new end end - context 'for singleton methods' do + context 'when used in a singleton method' do it 'infers types from optarg values' do source = Solargraph::Source.load_string(%( class Example diff --git a/spec/pin/symbol_spec.rb b/spec/pin/symbol_spec.rb index 16961cadc..f83a528cf 100644 --- a/spec/pin/symbol_spec.rb +++ b/spec/pin/symbol_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Pin::Symbol do - context "as an unquoted literal" do + context "when an unquoted literal" 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) @@ -16,7 +16,7 @@ end end - context "as a double quoted literal" do + context "when a double quoted literal" do it "is a kind of keyword" do pin = Solargraph::Pin::Symbol.new(nil, ':"symbol"') expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) @@ -28,7 +28,7 @@ end end - context "as a double quoted interpolatd literal" do + context "when a double quoted interpolated literal" do it "is a kind of keyword" do pin = Solargraph::Pin::Symbol.new(nil, ':"symbol #{variable}"') expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) @@ -41,7 +41,7 @@ end - context "as a single quoted literal" do + context "when a single quoted literal" do it "is a kind of keyword" do pin = Solargraph::Pin::Symbol.new(nil, ":'symbol'") expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index eaedfa998..67aa1398c 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -82,6 +82,7 @@ def my_method(&block) api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( class Foo + # @return [String] def self.new; end end Foo.new @@ -89,9 +90,7 @@ def self.new; end api_map.map source chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(4, 11)) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map(nil).locals) - # @todo This test looks invalid now. If `Foo.new` is an empty method, - # shouldn't it return `nil` or `undefined`? - # expect(type.tag).to eq('Foo') + expect(type.tag).to eq('String') end it "infers types from macros" do @@ -110,7 +109,7 @@ def self.bar; end expect(type.tag).to eq('String') end - it 'infers generic types' do + it 'infers generic types from Array#reverse' do source = Solargraph::Source.load_string(%( # @type [Array] list = array_of_strings @@ -292,7 +291,7 @@ def baz(&block) expect(type.simple_tags).to eq('Integer') end - it 'infers generic types' do + it 'infers generic types from @generic tag' do source = Solargraph::Source.load_string(%( # @generic GenericTypeParam class Foo diff --git a/spec/source/chain/class_variable_spec.rb b/spec/source/chain/class_variable_spec.rb index 4121e9948..05e7876f5 100644 --- a/spec/source/chain/class_variable_spec.rb +++ b/spec/source/chain/class_variable_spec.rb @@ -2,7 +2,7 @@ it "resolves class variable pins" do foo_pin = Solargraph::Pin::ClassVariable.new(name: '@@foo') bar_pin = Solargraph::Pin::ClassVariable.new(name: '@@bar') - api_map = double(Solargraph::ApiMap, :get_class_variable_pins => [foo_pin, bar_pin]) + api_map = instance_double(Solargraph::ApiMap, :get_class_variable_pins => [foo_pin, bar_pin]) link = Solargraph::Source::Chain::ClassVariable.new('@@bar') pins = link.resolve(api_map, Solargraph::Pin::ROOT_PIN, []) expect(pins.length).to eq(1) diff --git a/spec/source/cursor_spec.rb b/spec/source/cursor_spec.rb index 150e99449..2b1a0b3a5 100644 --- a/spec/source/cursor_spec.rb +++ b/spec/source/cursor_spec.rb @@ -44,19 +44,19 @@ end it "detects class variables" do - source = double(:Source, :code => '@@foo') + source = instance_double(Solargraph::Source, :code => '@@foo') cur = described_class.new(source, Solargraph::Position.new(0, 2)) expect(cur.word).to eq('@@foo') end it "detects instance variables" do - source = double(:Source, :code => '@foo') + source = instance_double(Solargraph::Source, :code => '@foo') cur = described_class.new(source, Solargraph::Position.new(0, 1)) expect(cur.word).to eq('@foo') end it "detects global variables" do - source = double(:Source, :code => '@foo') + source = instance_double(Solargraph::Source, :code => '@foo') cur = described_class.new(source, Solargraph::Position.new(0, 1)) expect(cur.word).to eq('@foo') end @@ -77,7 +77,7 @@ end it "detects constant words" do - source = double(:Source, :code => 'Foo::Bar') + source = instance_double(Solargraph::Source, :code => 'Foo::Bar') cur = described_class.new(source, Solargraph::Position.new(0, 5)) expect(cur.word).to eq('Bar') end diff --git a/spec/source/source_chainer_spec.rb b/spec/source/source_chainer_spec.rb index 7a8eb9fb8..b89aae62e 100644 --- a/spec/source/source_chainer_spec.rb +++ b/spec/source/source_chainer_spec.rb @@ -151,7 +151,7 @@ end it "chains instance variables from unsynchronized sources" do - source = double(Solargraph::Source, + source = instance_double(Solargraph::Source, :synchronized? => false, :code => '@foo.', :filename => 'test.rb', @@ -169,7 +169,7 @@ end it "chains class variables from unsynchronized sources" do - source = double(Solargraph::Source, + source = instance_double(Solargraph::Source, :synchronized? => false, :code => '@@foo.', :filename => 'test.rb', @@ -236,7 +236,7 @@ expect(chain.links.last).to be_undefined end - it 'detects whole constant with cursor at double colon' do + it 'detects whole constant with cursor at double colon - double level' do source = Solargraph::Source.load_string(%( class Outer class Inner @@ -248,7 +248,7 @@ class Inner expect(chain.links.last.word).to eq('Outer::Inner') end - it 'detects whole constant with cursor at double colon' do + it 'detects whole constant with cursor at double colon - triple level' do source = Solargraph::Source.load_string(%( class Outer class Inner1 @@ -295,7 +295,7 @@ class Inner2 expect(type.tag).to eq('Array(String, Integer)') end - it 'infers tuple type when types in literal differ' do + it 'allows Array methods when tuple type in literal inferred' do source = Solargraph::Source.load_string(%( b = ['a', 'b', 123] c = b.include?('a') diff --git a/spec/source_map/clip_spec.rb b/spec/source_map/clip_spec.rb index 1afa3f12c..1e79dcea5 100644 --- a/spec/source_map/clip_spec.rb +++ b/spec/source_map/clip_spec.rb @@ -288,7 +288,7 @@ def bar klass expect(clip.infer.tag).to eq('String') end - it 'infers method types from return nodes' do + it 'infers method types from return nodes - initialization' do source = Solargraph::Source.load_string(%( def foo String.new(from_object) @@ -302,7 +302,7 @@ def foo expect(type.tag).to eq('String') end - it 'infers method types from return nodes' do + it 'infers method types from return nodes - method return type' do source = Solargraph::Source.load_string(%( class Foo # @return [self] @@ -1883,17 +1883,6 @@ def bad_passthrough; yield; end expect(type.to_s).to eq('undefined') end - it 'infers block-pass symbols from generics' do - source = Solargraph::Source.load_string(%( - array = [0, 1, 2] - array.max_by(&:abs) - ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [2, 13]) - type = clip.infer - expect(type.to_s).to eq('Integer, nil') - end - it 'infers block-pass symbols with variant yields' do source = Solargraph::Source.load_string(%( array = [0] @@ -2519,72 +2508,6 @@ def find(index); end expect(clip.infer.to_s).to eq('Integer') end - it 'uses types to determine overload of [] to match' do - source = Solargraph::Source.load_string(%( - # @generic A - # @generic B - class Foo - # @overload [](index) - # @param [String] index - # @return [generic] - # @overload [](index) - # @param [Symbol] index - # @return [generic] - def [](index); end - end - - # @type [Foo(String, Integer)] - m = blah - mb = m['foo'] - mb - mc = m[:bar] - mc -), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [16, 6]) - expect(clip.infer.to_s).to eq('String') - - clip = api_map.clip_at('test.rb', [18, 6]) - expect(clip.infer.to_s).to eq('Integer') - end - - it 'uses literal types to determine overload of [] to match' do - source = Solargraph::Source.load_string(%( - # @generic A - # @generic B - class Foo - # @overload [](index) - # @param [1] index - # @return [generic] - # @overload [](index) - # @param [2] index - # @return [generic] - # @overload [](index) - # @param [Integer] index - # @return [Float] - def [](index); end - end - - # @type [Foo(String, Integer)] - m = blah - mb = m[1] - mb - mc = m[2] - mc - md = m[3] - md -), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [19, 6]) - expect(clip.infer.to_s).to eq('String') - - clip = api_map.clip_at('test.rb', [21, 6]) - expect(clip.infer.to_s).to eq('Integer') - - clip = api_map.clip_at('test.rb', [23, 6]) - expect(clip.infer.to_s).to eq('Float') - end - it 'interprets self type in superclass method return type' do source = Solargraph::Source.load_string(%( class Foo @@ -3016,21 +2939,6 @@ def foo a expect(clip.infer.to_s).to eq('Array') end - 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 = {} - a = raw_data['domains'] - a - ), 'test.rb') - - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [4, 6]) - expect(clip.infer.to_s).to eq('Array, Hash, Integer, nil') - end - it 'handles block method super scenarios' do source = Solargraph::Source.load_string(%( class Foo @@ -3312,7 +3220,7 @@ def foo expect(names).not_to include('bar=', 'baz=') end - it 'completes Struct methods via const assignment without a block' do + it 'completes Data methods via const assignment without a block' do source = Solargraph::Source.load_string(%( # @param bar [String] # @param baz [Integer] diff --git a/spec/source_map/mapper_spec.rb b/spec/source_map/mapper_spec.rb index 96d2bdab5..c98499c6d 100644 --- a/spec/source_map/mapper_spec.rb +++ b/spec/source_map/mapper_spec.rb @@ -1007,7 +1007,7 @@ class Foo }.not_to raise_error end - it "handles invalid byte sequences" do + it "handles invalid utf8 sequences" do expect { Solargraph::SourceMap.load(File.join('spec', 'fixtures', 'invalid_utf8.rb')) }.not_to raise_error diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index ac164dec2..1cceb0211 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -6,6 +6,20 @@ def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :alpha) end + it 'reports use of superclass when subclass is required' do + checker = type_checker(%( + class Sup; end + class Sub < Sup + # @return [Sub] + def foo + Sup.new + end + end + )) + expect(checker.problems).to be_one + expect(checker.problems.first.message).to include('does not match inferred type') + end + it 'allows a compatible function call from two distinct types in a union' do checker = type_checker(%( class Foo diff --git a/spec/type_checker/levels/normal_spec.rb b/spec/type_checker/levels/normal_spec.rb index 668e62886..15639130b 100644 --- a/spec/type_checker/levels/normal_spec.rb +++ b/spec/type_checker/levels/normal_spec.rb @@ -201,7 +201,7 @@ def bar; end expect(checker.problems).to be_empty end - it 'reports unresolved return tags' do + it 'reports unresolved type tags' do checker = type_checker(%( # @type [UnknownClass] x = unknown_method diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index d327199e3..da3f5fc73 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::TypeChecker do - context 'strict level' do + context 'when at strict level' do # @return [Solargraph::TypeChecker] def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strict) @@ -147,18 +147,6 @@ def bar *baz expect(checker.problems).to be_empty end - it 'reports mismatched argument types' do - checker = type_checker(%( - class Foo - # @param baz [Integer] - def bar(baz); end - end - Foo.new.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 chained calls' do checker = type_checker(%( # @param baz [Integer] @@ -581,9 +569,7 @@ def bar(baz:, bing:) expect(checker.problems).to be_empty end - it 'requires strict return tags' do - pending 'nil? support in flow sensitive typing' - + it 'does not require nil correctness in return tags when nil is involved and used second in a ternary' do checker = type_checker(%( class Foo # The tag is [String] but the inference is [String, nil] @@ -598,9 +584,7 @@ def bar expect(checker.problems.first.message).to include('does not match inferred type') end - it 'requires strict return tags' do - pending 'nil? support in flow sensitive typing' - + it 'does not require nil correctness in return tags when nil is involved and used first in a ternary' do checker = type_checker(%( class Foo # The tag is [String] but the inference is [String, nil] @@ -611,8 +595,7 @@ def bar end end )) - expect(checker.problems).to be_one - expect(checker.problems.first.message).to include('does not match inferred type') + expect(checker.problems.first.message).not_to include('does not match inferred type') end it 'validates strict return tags' do diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index a2f053ef9..4451a47f4 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -1,9 +1,39 @@ describe Solargraph::TypeChecker do - context 'strong level' do + context 'with level set to strong' do def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end + it 'requires strict return tags when nil is involved and used second in a ternary' do + checker = type_checker(%( + class Foo + # The tag is [String] but the inference is [String, nil] + # + # @return [String] + def bar + false ? 'bar' : nil + end + end + )) + expect(checker.problems).to be_one + expect(checker.problems.first.message).to include('does not match inferred type') + end + + it 'requires strict return tags when nil is involved and used first in a ternary' do + checker = type_checker(%( + class Foo + # The tag is [String] but the inference is [String, nil] + # + # @return [String] + def bar + true ? nil : 'bar' + end + end + )) + expect(checker.problems).to be_one + expect(checker.problems.first.message).to include('does not match inferred type') + end + it 'understands self type when passed as parameter' do checker = type_checker(%( class Location diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index 4add0903d..bb5d7e054 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::TypeChecker do - context 'typed level' do + context 'when level set to typed' do def type_checker(code) Solargraph::TypeChecker.load_string(code, 'test.rb', :typed) end @@ -189,21 +189,18 @@ def foo expect(checker.problems).to be_empty end - it 'reports superclasses of return types' do - # @todo This test might be invalid. There are use cases where inheritance - # between inferred and expected classes should be acceptable in either - # direction. - # checker = type_checker(%( - # class Sup; end - # class Sub < Sup - # # @return [Sub] - # def foo - # Sup.new - # end - # end - # )) - # expect(checker.problems).to be_one - # expect(checker.problems.first.message).to include('does not match inferred type') + it 'allows superclass of return types' do + checker = type_checker(%( + class Sup; end + class Sub < Sup + # @return [Sub] + def foo + Sup.new + end + end + )) + expect(checker.problems).to be_one + expect(checker.problems.first.message).to include('does not match inferred type') end it 'validates generic subclasses of return types' do diff --git a/spec/workspace_spec.rb b/spec/workspace_spec.rb index 78b71ce3b..2d3fccec1 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -50,7 +50,7 @@ end it "raises an exception for workspace size limits" do - config = double(:config, calculated: Array.new(Solargraph::Workspace::Config::MAX_FILES + 1), max_files: Solargraph::Workspace::Config::MAX_FILES) + config = instance_double(Solargraph::Config, calculated: Array.new(Solargraph::Workspace::Config::MAX_FILES + 1), max_files: Solargraph::Workspace::Config::MAX_FILES) expect { Solargraph::Workspace.new('.', config) @@ -62,7 +62,7 @@ File.write(gemspec_file, '') calculated = Array.new(Solargraph::Workspace::Config::MAX_FILES + 1) { gemspec_file } # @todo Mock reveals tight coupling - config = double(:config, calculated: calculated, max_files: 0, allow?: true, require_paths: [], plugins: []) + config = instance_double(Solargraph::Config, calculated: calculated, max_files: 0, allow?: true, require_paths: [], plugins: []) expect { Solargraph::Workspace.new('.', config) }.not_to raise_error @@ -135,7 +135,7 @@ end it 'rescues errors loading files into sources' do - config = double(:Config, directory: './path', calculated: ['./path/does_not_exist.rb'], max_files: 5000, require_paths: [], plugins: []) + config = instance_double(Solargraph::Config, directory: './path', calculated: ['./path/does_not_exist.rb'], max_files: 5000, require_paths: [], plugins: []) expect { Solargraph::Workspace.new('./path', config) }.not_to raise_error From 40e3b8dac0b7f3b003635fcc7de435c85717b9a3 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 13 Jan 2026 19:40:36 -0500 Subject: [PATCH 886/930] RuboCop manual fixes --- .rubocop.yml | 4 ++ .rubocop_todo.yml | 28 ------------ lib/solargraph/complex_type.rb | 45 ++++++++++--------- lib/solargraph/complex_type/unique_type.rb | 4 +- lib/solargraph/language_server/host.rb | 8 ++-- .../language_server/host/dispatch.rb | 2 +- lib/solargraph/language_server/request.rb | 2 +- .../parser/parser_gem/node_methods.rb | 3 -- lib/solargraph/pin/base.rb | 1 - lib/solargraph/pin/callable.rb | 2 - lib/solargraph/pin/compound_statement.rb | 3 -- lib/solargraph/pin/until.rb | 3 -- lib/solargraph/pin/while.rb | 3 -- lib/solargraph/pin_cache.rb | 1 + lib/solargraph/rbs_map/conversions.rb | 2 + lib/solargraph/source/chain/call.rb | 1 - lib/solargraph/source/chain/z_super.rb | 2 - lib/solargraph/type_checker.rb | 5 +-- spec/api_map_spec.rb | 25 +++++------ spec/diagnostics/rubocop_helpers_spec.rb | 4 +- spec/doc_map_spec.rb | 8 +--- spec/language_server/protocol_spec.rb | 2 +- spec/pin/base_variable_spec.rb | 4 +- spec/pin/local_variable_spec.rb | 4 +- spec/source/cursor_spec.rb | 4 +- spec/source_map/clip_spec.rb | 40 ----------------- spec/type_checker/levels/strict_spec.rb | 18 ++------ spec/workspace_spec.rb | 6 +-- 28 files changed, 69 insertions(+), 165 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 672b41637..48e65199d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -31,6 +31,10 @@ Style/MethodDefParentheses: Layout/EmptyLineAfterGuardClause: Enabled: false +Naming/AsciiIdentifiers: + Exclude: + - 'spec/fixtures/unicode.rb' + Lint/EmptyClass: Exclude: - spec/fixtures/**/*.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 772ccc04a..ae7b8999a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -418,10 +418,6 @@ Lint/UnusedMethodArgument: Lint/UselessAssignment: Enabled: false -Lint/UselessConstantScoping: - Exclude: - - 'lib/solargraph/rbs_map/conversions.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Lint/UselessMethodDefinition: Exclude: @@ -480,11 +476,6 @@ Naming/AccessorMethodName: - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/language_server/message/base.rb' -# Configuration parameters: AsciiConstants. -Naming/AsciiIdentifiers: - Exclude: - - 'spec/fixtures/unicode.rb' - # Configuration parameters: ForbiddenDelimiters. # ForbiddenDelimiters: (?i-mx:(^|\s)(EO[A-Z]{1}|END)(\s|$)) Naming/HeredocDelimiterNaming: @@ -627,10 +618,6 @@ RSpec/LetBeforeExamples: Exclude: - 'spec/complex_type_spec.rb' -RSpec/MissingExampleGroupArgument: - Exclude: - - 'spec/diagnostics/rubocop_helpers_spec.rb' - RSpec/MultipleExpectations: Max: 14 @@ -646,14 +633,6 @@ RSpec/NotToNot: - 'spec/api_map_spec.rb' - 'spec/rbs_map/core_map_spec.rb' -RSpec/PendingWithoutReason: - Exclude: - - 'spec/api_map_spec.rb' - - 'spec/doc_map_spec.rb' - - 'spec/pin/base_variable_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. # SupportedStyles: inflected, explicit @@ -671,13 +650,6 @@ RSpec/RemoveConst: Exclude: - 'spec/diagnostics/rubocop_helpers_spec.rb' -RSpec/RepeatedExample: - Exclude: - - 'spec/api_map_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - # This cop supports safe autocorrection (--autocorrect). RSpec/ScatteredLet: Exclude: diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index fa481b3cc..0b5618d2c 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -34,7 +34,8 @@ def initialize types = [UniqueType::UNDEFINED] end # @param api_map [ApiMap] - # @param context [String] + # @param gates [Array] + # # @return [ComplexType] def qualify api_map, *gates red = reduce_object @@ -109,7 +110,7 @@ def each_unique_type &block # @param new_name [String, nil] # @param make_rooted [Boolean, nil] # @param new_key_types [Array, nil] - # @param rooted [Boolean, nil] + # @param make_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) @@ -154,6 +155,7 @@ def namespaces end # @param name [Symbol] + # # @return [Object, nil] def method_missing name, *args, &block return if @items.first.nil? @@ -198,18 +200,23 @@ def desc # @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 conform to a more specific - # expected type - # @param allow_any_match [Boolean] if true, any unique type - # 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>] + # + # allow_subtype_skew: if not provided, check if any subtypes of + # the expected type match the inferred type + # + # allow_reverse_match: check if any subtypes + # of the expected type match the inferred type + # + # allow_empty_params: allow a general inferred type without + # parameters to conform to a more specific expected type + # + # allow_any_match: any unique type matched in the inferred + # qualifies as a match + # + # allow_undefined: treat undefined as a wildcard that matches + # anything + # # @param variance [:invariant, :covariant, :contravariant] # @return [Boolean] def conforms_to?(api_map, expected, @@ -406,13 +413,11 @@ class << self # @example # ComplexType.parse 'String', 'Foo', 'nil' #=> [String, Foo, nil] # - # @note - # The `partial` parameter is used to indicate that the method is - # receiving a string that will be used inside another ComplexType. - # It returns arrays of ComplexTypes instead of a single cohesive one. - # Consumers should not need to use this parameter; it should only be - # used internally. - # + # @param partial [Boolean] if true, method is receiving a string + # that will be used inside another ComplexType. It returns + # arrays of ComplexTypes instead of a single cohesive one. + # Consumers should not need to use this parameter; it should + # only be used internally. # @param strings [Array] The type definitions to parse # @return [ComplexType] # # @overload parse(*strings, partial: false) diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index fa0184fbf..c701d3ac8 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -493,7 +493,7 @@ def to_a # @param new_name [String, nil] # @param make_rooted [Boolean, nil] # @param new_key_types [Array, nil] - # @param rooted [Boolean, nil] + # @param make_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) @@ -547,7 +547,7 @@ def transform(new_name = nil, &transform_type) # Generate a ComplexType that fully qualifies this type's namespaces. # # @param api_map [ApiMap] The ApiMap that performs qualification - # @param context [String] The namespace from which to resolve names + # @param gates [Array] The namespaces from which to resolve names # @return [self, ComplexType, UniqueType] The generated ComplexType def qualify api_map, *gates transform do |t| diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index c59f7cd5e..3dc4b450a 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -53,7 +53,7 @@ def configure update logger.level = LOG_LEVELS[options['logLevel']] || DEFAULT_LOG_LEVEL end - # @return [Hash{String => [Boolean, String]}] + # @return [Hash{String => Boolean, String}] def options @options ||= default_configuration end @@ -648,7 +648,7 @@ def show_message text, type = LanguageServer::MessageTypes::INFO # @param text [String] # @param type [Integer] A MessageType constant # @param actions [Array] Response options for the client - # @param block The block that processes the response + # @param block [Proc] The block that processes the response # @yieldparam [String] The action received from the client # @return [void] def show_message_request text, type, actions, &block @@ -667,7 +667,7 @@ def pending_requests requests.keys end - # @return [Hash{String => [Boolean,String]}] + # @return [Hash{String => Boolean,String}] def default_configuration { 'completion' => true, @@ -863,7 +863,7 @@ def library_map library end # @param library [Library] - # @param uuid [String, nil] + # # @return [void] def sync_library_map library total = library.workspace.sources.length diff --git a/lib/solargraph/language_server/host/dispatch.rb b/lib/solargraph/language_server/host/dispatch.rb index f64b4ac96..4cb135169 100644 --- a/lib/solargraph/language_server/host/dispatch.rb +++ b/lib/solargraph/language_server/host/dispatch.rb @@ -119,8 +119,8 @@ def generic_library .tap { |lib| lib.add_observer self } 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/request.rb b/lib/solargraph/language_server/request.rb index 2cc874613..112ce64b2 100644 --- a/lib/solargraph/language_server/request.rb +++ b/lib/solargraph/language_server/request.rb @@ -4,7 +4,7 @@ module Solargraph module LanguageServer class Request # @param id [Integer] - # @param &block The block that processes the client's response + # @param block [Proc] The block that processes the client's response def initialize id, &block @id = id @block = block diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 9b7d94827..f8859de76 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -321,9 +321,6 @@ class << self CASE_STATEMENT = [:case] # @param node [AST::Node] a method body compound statement - # @param include_explicit_returns [Boolean] If true, - # include the value nodes of the parameter of the - # 'return' statements in the type returned. # @return [Array] low-level value nodes from # both nodes in value position as well as explicit # return statements in the method's closure. diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index d0e53d741..752c2f0df 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -406,7 +406,6 @@ def comments # @param generics_to_resolve [Enumerable] # @param return_type_context [ComplexType, ComplexType::UniqueType, nil] - # @param context [ComplexType] # @param resolved_generic_values [Hash{String => ComplexType}] # @return [self] def resolve_generics_from_context(generics_to_resolve, return_type_context = nil, resolved_generic_values: {}) diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index e4c35f5f3..a7c74ab35 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -129,7 +129,6 @@ def full_type_arity # @param return_type_context [ComplexType, nil] # @param yield_arg_types [Array, nil] # @param yield_return_type_context [ComplexType, nil] - # @param context [ComplexType, nil] # @param resolved_generic_values [Hash{String => ComplexType}] # # @return [self] @@ -181,7 +180,6 @@ def method_name # @param return_type_context [ComplexType, nil] # @param yield_arg_types [Array, nil] # @param yield_return_type_context [ComplexType, nil] - # @param context [ComplexType, nil] # @param resolved_generic_values [Hash{String => ComplexType}] # # @return [self] diff --git a/lib/solargraph/pin/compound_statement.rb b/lib/solargraph/pin/compound_statement.rb index 4598d677a..9194e6975 100644 --- a/lib/solargraph/pin/compound_statement.rb +++ b/lib/solargraph/pin/compound_statement.rb @@ -42,10 +42,7 @@ module Pin class CompoundStatement < Pin::Base attr_reader :node - # @param receiver [Parser::AST::Node, nil] # @param node [Parser::AST::Node, nil] - # @param context [ComplexType, nil] - # @param args [::Array] def initialize node: nil, **splat super(**splat) @node = node diff --git a/lib/solargraph/pin/until.rb b/lib/solargraph/pin/until.rb index 7e050fea6..7497c0f09 100644 --- a/lib/solargraph/pin/until.rb +++ b/lib/solargraph/pin/until.rb @@ -5,10 +5,7 @@ module Pin class Until < CompoundStatement include Breakable - # @param receiver [Parser::AST::Node, nil] # @param node [Parser::AST::Node, nil] - # @param context [ComplexType, nil] - # @param args [::Array] def initialize node: nil, **splat super(**splat) @node = node diff --git a/lib/solargraph/pin/while.rb b/lib/solargraph/pin/while.rb index ac8c31c97..19b0bdc8b 100644 --- a/lib/solargraph/pin/while.rb +++ b/lib/solargraph/pin/while.rb @@ -5,10 +5,7 @@ module Pin class While < CompoundStatement include Breakable - # @param receiver [Parser::AST::Node, nil] # @param node [Parser::AST::Node, nil] - # @param context [ComplexType, nil] - # @param args [::Array] def initialize node: nil, **splat super(**splat) @node = node diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 11c825787..4b1ec1153 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -408,6 +408,7 @@ def exist? *path # @return [void] # @param path_segments [Array] + # @param out [StringIO, IO, nil] def uncache_by_prefix *path_segments, out: nil path = File.join(*path_segments) glob = "#{path}*" diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index b958895fb..7daad23e8 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -361,6 +361,7 @@ def global_decl_to_pin decl ["AST::Node", :instance, "original_dup"] => :private, ["Rainbow::Presenter", :instance, "wrap_with_sgr"] => :private, } + private_constant :VISIBILITY_OVERRIDE # @param decl [RBS::AST::Members::MethodDefinition, RBS::AST::Members::AttrReader, RBS::AST::Members::AttrWriter, RBS::AST::Members::AttrAccessor] # @param closure [Pin::Closure] @@ -722,6 +723,7 @@ def alias_to_pin decl, closure 'untyped' => '', 'NilClass' => 'nil' } + private_constant :RBS_TO_YARD_TYPE # @param type [RBS::MethodType, RBS::Types::Block] # @return [String] diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index eb83ead98..32d8a15e4 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -353,7 +353,6 @@ def find_block_pin(api_map) # @param api_map [ApiMap] # @param name_pin [Pin::Base] - # @param block_parameter_types [::Array] # @param locals [::Array] # @return [ComplexType, nil] def block_call_type(api_map, name_pin, locals) diff --git a/lib/solargraph/source/chain/z_super.rb b/lib/solargraph/source/chain/z_super.rb index 5b0106c92..92f3bc313 100644 --- a/lib/solargraph/source/chain/z_super.rb +++ b/lib/solargraph/source/chain/z_super.rb @@ -11,9 +11,7 @@ class ZSuper < Call attr_reader :arguments # @param word [String] - # @param arguments [::Array] # @param with_block [Boolean] True if the chain is inside a block - # @param head [Boolean] True if the call is the start of its chain def initialize word, with_block = false super(word, nil, [], with_block) end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index fb76ba7b5..ddd7d9f3d 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -431,7 +431,6 @@ def argument_problems_for chain, api_map, closure_pin, locals, location # @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 @@ -552,7 +551,7 @@ def kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pi # @param locals [Array] # @param location [Location] # @param pin [Pin::Method] - # @param params [Hash{String => [nil, Hash]}] + # @param params [Hash{String => nil, Hash}] # @param kwargs [Hash{Symbol => Source::Chain}] # @return [Array] def kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, kwargs) @@ -804,7 +803,7 @@ def required_param_count(parameters) end # @param parameters [Enumerable] - # @param pin [Pin::Method] + # # @return [Integer] def optional_param_count(parameters) parameters.select { |p| p.decl == :optarg }.length diff --git a/spec/api_map_spec.rb b/spec/api_map_spec.rb index de0cdfada..2340c8bf9 100755 --- a/spec/api_map_spec.rb +++ b/spec/api_map_spec.rb @@ -114,8 +114,9 @@ class Baz expect(paths).to include('Foo::Baz') end - # @todo Working on context resolution - xit 'finds nested namespaces within a context' do + it 'finds nested namespaces within a context' do + pending('better context resolution') + map = Solargraph::SourceMap.load_string(%( module Foo class Bar @@ -130,8 +131,9 @@ class Baz expect(pins.map(&:path)).to include('Foo::Bar::BAR_CONSTANT') end - # @todo This might be invalid now - xit 'checks constant visibility' do + it 'checks constant visibility' do + pending('This might be invalid now') + map = Solargraph::SourceMap.load_string(%( module Foo FOO_CONSTANT = 'foo' @@ -158,13 +160,6 @@ module Foo expect(pins.map(&:path)).to include('String#upcase') end - it 'gets class methods for complex types' do - @api_map.index [] - type = Solargraph::ComplexType.parse('Class') - pins = @api_map.get_complex_type_methods(type) - expect(pins.map(&:path)).to include('String.try_convert') - end - it 'checks visibility of complex type methods' do map = Solargraph::SourceMap.load_string(%( class Foo @@ -462,8 +457,7 @@ class Container expect(pins.map(&:path)).to include('Mixin::FOO') end - # @todo This test needs changed - xit 'sorts constants by name' do + it 'sorts constants by name' do source = Solargraph::Source.load_string(%( module Foo AAB = 'aaa' @@ -675,8 +669,9 @@ class Container expect(paths).to eq(['Prepended::PRE_CONST']) end - # @todo This test fails with lazy dynamic rebinding - xit 'finds instance variables in yieldreceiver blocks' do + it 'finds instance variables in yieldreceiver blocks' do + pending('lazy dynamic rebinding fixes') + source = Solargraph::Source.load_string(%( module Container # @yieldreceiver [Container] diff --git a/spec/diagnostics/rubocop_helpers_spec.rb b/spec/diagnostics/rubocop_helpers_spec.rb index d59dac4ae..b1c78726c 100644 --- a/spec/diagnostics/rubocop_helpers_spec.rb +++ b/spec/diagnostics/rubocop_helpers_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Diagnostics::RubocopHelpers do - context do + context 'with custom version' do around do |example| old_gem_path = Gem.paths.path custom_gem_path = File.absolute_path('spec/fixtures/rubocop-custom-version').gsub(/\\/, '/') @@ -26,7 +26,7 @@ end end - context do + context 'with real version' do let(:default_version) { Gem::Specification.find_by_name('rubocop').full_gem_path[/[^-]+$/] } it "requires the default version of rubocop" do diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 4a6ce1f4b..eaeb65a13 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -38,11 +38,7 @@ ['rspec-mocks'] end - # 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') - + 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 @@ -58,7 +54,7 @@ # # 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 = [ diff --git a/spec/language_server/protocol_spec.rb b/spec/language_server/protocol_spec.rb index bb3c6fe37..1d7d98637 100644 --- a/spec/language_server/protocol_spec.rb +++ b/spec/language_server/protocol_spec.rb @@ -44,7 +44,7 @@ def stop end before :each do - version = insance_double(Gem::Version, version: Gem::Version.new('1.0.0')) + version = instance_double(Gem::Version, version: Gem::Version.new('1.0.0')) Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = instance_double(Gem::SpecFetcher, search_for_dependency: [version]) end diff --git a/spec/pin/base_variable_spec.rb b/spec/pin/base_variable_spec.rb index 03e6b1a11..2a49ac9f1 100644 --- a/spec/pin/base_variable_spec.rb +++ b/spec/pin/base_variable_spec.rb @@ -45,9 +45,7 @@ def bar expect(type.simplify_literals.to_rbs).to eq('(::Integer | ::NilClass)') end - xit "understands proc kwarg parameters aren't affected by @type" do - pending "understanding restarg in block param in Block#typify_parameters" - + it "understands proc kwarg parameters aren't affected by @type" do code = %( # @return [Proc] def foo diff --git a/spec/pin/local_variable_spec.rb b/spec/pin/local_variable_spec.rb index 39fd22c28..478056f41 100644 --- a/spec/pin/local_variable_spec.rb +++ b/spec/pin/local_variable_spec.rb @@ -1,5 +1,7 @@ describe Solargraph::Pin::LocalVariable do - xit "merges presence changes so that [not currently used]" do + it "merges presence changes so that [not currently used]" do + pending 'but not sure why' + map1 = Solargraph::SourceMap.load_string(%( class Foo foo = 'foo' diff --git a/spec/source/cursor_spec.rb b/spec/source/cursor_spec.rb index 2b1a0b3a5..b9cae07b3 100644 --- a/spec/source/cursor_spec.rb +++ b/spec/source/cursor_spec.rb @@ -56,9 +56,9 @@ end it "detects global variables" do - source = instance_double(Solargraph::Source, :code => '@foo') + source = instance_double(Solargraph::Source, :code => '$foo') cur = described_class.new(source, Solargraph::Position.new(0, 1)) - expect(cur.word).to eq('@foo') + expect(cur.word).to eq('$foo') end it "generates word ranges" do diff --git a/spec/source_map/clip_spec.rb b/spec/source_map/clip_spec.rb index 1e79dcea5..64edbd0c6 100644 --- a/spec/source_map/clip_spec.rb +++ b/spec/source_map/clip_spec.rb @@ -2290,17 +2290,6 @@ def meth arg, arg2 expect(type.to_s).to eq('Array(String, Integer)') end - it 'infers array of identical diverse arrays into tuples' do - source = Solargraph::Source.load_string(%( - h = [['foo', 1], ['bar', 2]] - h - ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [2, 6]) - type = clip.infer - expect(type.to_s).to eq('Array') - end - it 'infers literal diverse array of diverse arrays into tuple of tuples' do source = Solargraph::Source.load_string(%( h = [['foo', 1], ['bar', :baz]] @@ -2479,35 +2468,6 @@ def [](index); end expect(clip.infer.to_s).to eq('Float') end - it 'can use strings and symbols to choose a signature' do - source = Solargraph::Source.load_string(%( - # @generic A - # @generic B - class Foo - # @overload find(index) - # @param [String] index - # @return [generic] - # @overload find(index) - # @param [Symbol] index - # @return [generic] - def find(index); end - end - - # @type [Foo(String, Integer)] - m = blah - mb = m.find('foo') - mb - mc = m.find(:bar) - mc -), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [16, 6]) - expect(clip.infer.to_s).to eq('String') - - clip = api_map.clip_at('test.rb', [18, 6]) - expect(clip.infer.to_s).to eq('Integer') - end - it 'interprets self type in superclass method return type' do source = Solargraph::Source.load_string(%( class Foo diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index da3f5fc73..3c391e7f9 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -184,7 +184,9 @@ def bar(baz); "foo"; end xit 'complains about calling a non-existent method' - xit 'complains about inserting the wrong type into a tuple slot' do + it 'complains about inserting the wrong type into a tuple slot' do + pending 'Better error message from tuple support' + checker = type_checker(%( # @param a [::Solargraph::Fills::Tuple(String, Integer)] def foo(a) @@ -479,20 +481,6 @@ def bar(baz) expect(checker.problems.first.message).to include('Not enough arguments') end - it 'does not attempt to account for splats' do - checker = type_checker(%( - class Foo - def bar(baz, bing) - end - - def blah(args) - bar *args - end - end - )) - expect(checker.problems).to be_empty - end - it 'does not attempt to account for splats in arg counts' do checker = type_checker(%( class Foo diff --git a/spec/workspace_spec.rb b/spec/workspace_spec.rb index 2d3fccec1..b9e223be4 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -50,7 +50,7 @@ end it "raises an exception for workspace size limits" do - config = instance_double(Solargraph::Config, calculated: Array.new(Solargraph::Workspace::Config::MAX_FILES + 1), max_files: Solargraph::Workspace::Config::MAX_FILES) + config = instance_double(Solargraph::Workspace::Config, calculated: Array.new(Solargraph::Workspace::Config::MAX_FILES + 1), max_files: Solargraph::Workspace::Config::MAX_FILES) expect { Solargraph::Workspace.new('.', config) @@ -62,7 +62,7 @@ File.write(gemspec_file, '') calculated = Array.new(Solargraph::Workspace::Config::MAX_FILES + 1) { gemspec_file } # @todo Mock reveals tight coupling - config = instance_double(Solargraph::Config, calculated: calculated, max_files: 0, allow?: true, require_paths: [], plugins: []) + config = instance_double(Solargraph::Workspace::Config, calculated: calculated, max_files: 0, allow?: true, require_paths: [], plugins: []) expect { Solargraph::Workspace.new('.', config) }.not_to raise_error @@ -135,7 +135,7 @@ end it 'rescues errors loading files into sources' do - config = instance_double(Solargraph::Config, directory: './path', calculated: ['./path/does_not_exist.rb'], max_files: 5000, require_paths: [], plugins: []) + config = instance_double(Solargraph::Workspace::Config, directory: './path', calculated: ['./path/does_not_exist.rb'], max_files: 5000, require_paths: [], plugins: []) expect { Solargraph::Workspace.new('./path', config) }.not_to raise_error From 523d12717237a0b07f1a601461df0d2beb132bba Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 13 Jan 2026 19:46:24 -0500 Subject: [PATCH 887/930] RuboCop manual fixes --- 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 ddd7d9f3d..bf03071bf 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -558,6 +558,7 @@ def kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, result = [] kwargs.each_pair do |pname, argchain| next unless params.key?(pname.to_s) + # @sg-ignore # @type [ComplexType] ptype = params[pname.to_s][:qualified] ptype = ptype.self_to_type(pin.context) From 5ae55584e399822af0a12b386229acffdd9bd7bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lek=C3=AB=20Mula?= Date: Wed, 14 Jan 2026 01:47:12 +0100 Subject: [PATCH 888/930] Improve memory efficiency of Position class (#1054) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use each_line instead of text.lines Avoid allocating additional strings, instead use sliced substrings * gitignore vendor/cache * Remove redundant end_with? Co-authored-by: Jean Boussier * Remove benchamrks https://github.com/castwide/solargraph/pull/1054#discussion_r2312388042 * String#index(offset:) FTW 🚀 https://github.com/castwide/solargraph/pull/1054#issuecomment-3240029936 Co-authored-by: Jean Boussier * fix rubocop --------- Co-authored-by: Jean Boussier --- .gitignore | 1 + lib/solargraph/position.rb | 32 +++++++++++++++++++++----------- spec/position_spec.rb | 16 ++++++++++++++++ 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 2819165b1..75510d96b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ coverage /Makefile /.pryrc /.rspec-local +vendor/cache diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 74606f142..e47ed8bc8 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -58,8 +58,22 @@ 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 + newline_index = -1 + cursor = 0 + line = -1 + + last_line_index = 0 + while (newline_index = text.index("\n", newline_index + 1)) && line <= position.line + line += 1 + break if line == position.line + line_length = newline_index - last_line_index + + cursor += line_length.zero? ? 1 : line_length + + last_line_index = newline_index + end + + cursor + position.character end # Get a numeric offset for the specified text and a position identified @@ -81,16 +95,12 @@ def self.line_char_to_offset text, line, character def self.from_offset text, offset cursor = 0 line = 0 - character = nil - text.lines.each do |l| - line_length = l.length - char_length = l.chomp.length - if cursor + char_length >= offset - character = offset - cursor - break - end - cursor += line_length + character = offset + newline_index = -1 + + while (newline_index = text.index("\n", newline_index + 1)) && newline_index < offset line += 1 + character = offset - newline_index - 1 end character = 0 if character.nil? and (cursor - offset).between?(0, 1) raise InvalidOffsetError if character.nil? diff --git a/spec/position_spec.rb b/spec/position_spec.rb index fa30cf7d9..88dedcd26 100644 --- a/spec/position_spec.rb +++ b/spec/position_spec.rb @@ -12,6 +12,22 @@ expect(orig).to be(norm) end + it 'finds offset from position' do + text = "\n class Foo\n def bar baz, boo = 'boo'\n end\n end\n " + expect(Solargraph::Position.to_offset(text, Solargraph::Position.new(0, 0))).to eq(0) + expect(Solargraph::Position.to_offset(text, Solargraph::Position.new(0, 4))).to eq(4) + expect(Solargraph::Position.to_offset(text, Solargraph::Position.new(2, 12))).to eq(29) + expect(Solargraph::Position.to_offset(text, Solargraph::Position.new(2, 27))).to eq(44) + end + + it 'constructs position from offset' do + text = "\n class Foo\n def bar baz, boo = 'boo'\n end\n end\n " + expect(Solargraph::Position.from_offset(text, 0)).to eq(Solargraph::Position.new(0, 0)) + expect(Solargraph::Position.from_offset(text, 4)).to eq(Solargraph::Position.new(1, 3)) + expect(Solargraph::Position.from_offset(text, 29)).to eq(Solargraph::Position.new(2, 12)) + expect(Solargraph::Position.from_offset(text, 44)).to eq(Solargraph::Position.new(2, 27)) + end + it "raises an error for objects that cannot be normalized" do expect { Solargraph::Position.normalize('0, 1') From 1388af43b8a434c0a7ec3823894eaabb90c8d1cc Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 13 Jan 2026 19:52:51 -0500 Subject: [PATCH 889/930] RuboCop manual fixes --- 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 bf03071bf..ab4ca68c2 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -561,6 +561,7 @@ def kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, # @sg-ignore # @type [ComplexType] ptype = params[pname.to_s][:qualified] + # @sg-ignore flow sensitive typing should be able to handle redefinition ptype = ptype.self_to_type(pin.context) argtype = argchain.infer(api_map, closure_pin, locals) argtype = argtype.self_to_type(closure_pin.context) From f9590f14b45c2c5432f5a86fec2367a416760846 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 13 Jan 2026 20:05:46 -0500 Subject: [PATCH 890/930] RuboCop manual fixes --- spec/complex_type_spec.rb | 2 +- .../message/completion_item/resolve_spec.rb | 4 ++-- .../message/extended/check_gem_version_spec.rb | 2 +- spec/type_checker/levels/strict_spec.rb | 5 ++--- spec/type_checker/levels/typed_spec.rb | 3 +-- spec/workspace_spec.rb | 15 --------------- 6 files changed, 7 insertions(+), 24 deletions(-) diff --git a/spec/complex_type_spec.rb b/spec/complex_type_spec.rb index 36bb335b1..4b5eac972 100644 --- a/spec/complex_type_spec.rb +++ b/spec/complex_type_spec.rb @@ -591,7 +591,7 @@ context "when 'qualifying' types by resolving relative references to types to absolute references (fully qualified types)" do it 'returns undefined for unqualified types' do - api_map = intance_double(Solargraph::ApiMap, qualify: nil) + api_map = instance_double(Solargraph::ApiMap, qualify: nil) type = Solargraph::ComplexType.parse('UndefinedClass') qualified = type.qualify(api_map) expect(qualified).to be_undefined diff --git a/spec/language_server/message/completion_item/resolve_spec.rb b/spec/language_server/message/completion_item/resolve_spec.rb index 2afd201f9..e10f17672 100644 --- a/spec/language_server/message/completion_item/resolve_spec.rb +++ b/spec/language_server/message/completion_item/resolve_spec.rb @@ -9,7 +9,7 @@ visibility: :public, parameters: [] ) - host = intance_double(Solargraph::LanguageServer::Host, locate_pins: [pin], probe: pin, detail: nil, options: { 'enablePages' => true }) + host = instance_double(Solargraph::LanguageServer::Host, locate_pins: [pin], options: { 'enablePages' => true }) resolve = Solargraph::LanguageServer::Message::CompletionItem::Resolve.new(host, { 'params' => pin.completion_item }) @@ -25,7 +25,7 @@ name: '@bar', comments: '' ) - host = instance_double(Solargraph::LanguageServer::Host, locate_pins: [pin], probe: pin, detail: nil) + host = instance_double(Solargraph::LanguageServer::Host, locate_pins: [pin]) resolve = Solargraph::LanguageServer::Message::CompletionItem::Resolve.new(host, { 'params' => pin.completion_item }) diff --git a/spec/language_server/message/extended/check_gem_version_spec.rb b/spec/language_server/message/extended/check_gem_version_spec.rb index 4bc914bc9..fc3e46a8e 100644 --- a/spec/language_server/message/extended/check_gem_version_spec.rb +++ b/spec/language_server/message/extended/check_gem_version_spec.rb @@ -1,6 +1,6 @@ describe Solargraph::LanguageServer::Message::Extended::CheckGemVersion do before :each do - version = instance_double(:GemVersion, version: Gem::Version.new('1.0.0')) + version = instance_double(Gem::Version, version: Gem::Version.new('1.0.0')) Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = instance_double(Gem::SpecFetcher, search_for_dependency: [version]) end diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 3c391e7f9..7838f30bc 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -568,8 +568,7 @@ def bar end end )) - expect(checker.problems).to be_one - expect(checker.problems.first.message).to include('does not match inferred type') + expect(checker.problems.map(&:message)).not_to include('does not match inferred type') end it 'does not require nil correctness in return tags when nil is involved and used first in a ternary' do @@ -583,7 +582,7 @@ def bar end end )) - expect(checker.problems.first.message).not_to include('does not match inferred type') + expect(checker.problems.map(&:message)).not_to include('does not match inferred type') end it 'validates strict return tags' do diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index bb5d7e054..0b970b52a 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -199,8 +199,7 @@ def foo end end )) - expect(checker.problems).to be_one - expect(checker.problems.first.message).to include('does not match inferred type') + expect(checker.problems.map(&:message)).not_to include('does not match inferred type') end it 'validates generic subclasses of return types' do diff --git a/spec/workspace_spec.rb b/spec/workspace_spec.rb index b9e223be4..30b8d7fbc 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -161,20 +161,5 @@ 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, rebuild: false) - - 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 0fdb09fb98603d16a456df36337a0b917e448305 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 13 Jan 2026 20:10:02 -0500 Subject: [PATCH 891/930] RuboCop manual fixes --- lib/solargraph/type_checker.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index ab4ca68c2..bf03071bf 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -561,7 +561,6 @@ def kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, # @sg-ignore # @type [ComplexType] ptype = params[pname.to_s][:qualified] - # @sg-ignore flow sensitive typing should be able to handle redefinition ptype = ptype.self_to_type(pin.context) argtype = argchain.infer(api_map, closure_pin, locals) argtype = argtype.self_to_type(closure_pin.context) From b3affb075076270b9fa0ef7f5d36d7ee6047d2c1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 13 Jan 2026 20:24:27 -0500 Subject: [PATCH 892/930] RuboCop manual fixes --- 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 bf03071bf..d4886afae 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -560,8 +560,8 @@ def kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, next unless params.key?(pname.to_s) # @sg-ignore # @type [ComplexType] - ptype = params[pname.to_s][:qualified] - ptype = ptype.self_to_type(pin.context) + raw_ptype = params[pname.to_s][:qualified] + ptype = raw_ptype.self_to_type(pin.context) argtype = argchain.infer(api_map, closure_pin, locals) argtype = argtype.self_to_type(closure_pin.context) if argtype.defined? && ptype && !arg_conforms_to?(argtype, ptype) From 68cd96dd3002fe73dc012ef67af1d5e0a4adbc39 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 13 Jan 2026 21:22:46 -0500 Subject: [PATCH 893/930] RuboCop manual fixes --- .rubocop.yml | 29 +- .rubocop_todo.yml | 90 +----- Gemfile | 14 +- Rakefile | 276 +++++++++--------- lib/solargraph/complex_type/type_methods.rb | 5 +- lib/solargraph/language_server/host.rb | 2 +- .../message/completion_item/resolve.rb | 2 +- .../message/text_document/completion.rb | 2 +- lib/solargraph/pin/base.rb | 12 +- lib/solargraph/pin/method.rb | 2 +- lib/solargraph/pin_cache.rb | 6 - lib/solargraph/source/chain/call.rb | 3 +- lib/solargraph/source/chain/constant.rb | 2 + lib/solargraph/source/chain/if.rb | 6 +- lib/solargraph/source/chain/literal.rb | 8 +- lib/solargraph/source/chain/or.rb | 6 +- lib/solargraph/type_checker.rb | 2 +- solargraph.gemspec | 152 +++++----- spec/api_map_method_spec.rb | 2 +- spec/rbs_map/stdlib_map_spec.rb | 2 +- spec/source/source_chainer_spec.rb | 3 +- spec/source_map/node_processor_spec.rb | 2 +- 22 files changed, 290 insertions(+), 338 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 48e65199d..96ec6c6b2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,6 +21,25 @@ AllCops: - "vendor/**/.*" TargetRubyVersion: 3.0 +Gemspec/RequiredRubyVersion: + Exclude: + - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' + - 'spec/fixtures/rubocop-custom-version/specifications/rubocop-0.0.0.gemspec' + - 'spec/fixtures/vendored/vendor/do_not_use.gemspec' + +Gemspec/DevelopmentDependencies: + EnforcedStyle: gemspec + Exclude: + - 'spec/fixtures/**/*' + +Lint/EmptyFile: + Exclude: + - 'spec/fixtures/vendored/vendor/do_not_use.gemspec' + +Naming/VariableName: + Exclude: + - 'spec/fixtures/unicode.rb' + # We don't use the spec/solargraph directory RSpec/SpecFilePathFormat: Enabled: false @@ -56,17 +75,17 @@ Style/ClassVars: # improve existing code! # Metrics/AbcSize: - Max: 65 + Max: 110 Metrics/MethodLength: - Max: 60 + Max: 70 Metrics/ClassLength: Max: 500 Metrics/CyclomaticComplexity: - Max: 23 + Max: 40 Metrics/PerceivedComplexity: - Max: 29 + Max: 40 RSpec/ExampleLength: - Max: 17 + Max: 310 plugins: - rubocop-rspec diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ae7b8999a..01d15c52f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -18,12 +18,6 @@ Gemspec/DeprecatedAttributeAssignment: - 'solargraph.gemspec' - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' -# Configuration parameters: EnforcedStyle, AllowedGems. -# SupportedStyles: Gemfile, gems.rb, gemspec -Gemspec/DevelopmentDependencies: - Exclude: - - 'solargraph.gemspec' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation. Gemspec/OrderedDependencies: @@ -37,13 +31,6 @@ Gemspec/RequireMFA: - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' - 'spec/fixtures/rubocop-custom-version/specifications/rubocop-0.0.0.gemspec' -# Configuration parameters: Severity. -Gemspec/RequiredRubyVersion: - Exclude: - - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' - - 'spec/fixtures/rubocop-custom-version/specifications/rubocop-0.0.0.gemspec' - - 'spec/fixtures/vendored/vendor/do_not_use.gemspec' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: with_first_argument, with_fixed_indentation @@ -100,14 +87,6 @@ Layout/EmptyLinesAroundModuleBody: Layout/EndAlignment: Enabled: false -# Configuration parameters: EnforcedStyle. -# SupportedStyles: native, lf, crlf -Layout/EndOfLine: - Exclude: - - 'Gemfile' - - 'Rakefile' - - 'solargraph.gemspec' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. Layout/ExtraSpacing: @@ -311,16 +290,10 @@ Lint/ConstantDefinitionInBlock: # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch. Lint/DuplicateBranch: Exclude: - - 'lib/solargraph/complex_type/type_methods.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/pin/base.rb' - 'lib/solargraph/rbs_map/conversions.rb' -# Configuration parameters: AllowComments. -Lint/EmptyFile: - Exclude: - - 'spec/fixtures/vendored/vendor/do_not_use.gemspec' - # This cop supports unsafe autocorrection (--autocorrect-all). Lint/InterpolationCheck: Exclude: @@ -329,15 +302,6 @@ Lint/InterpolationCheck: - 'spec/source/chain_spec.rb' - 'spec/source/cursor_spec.rb' -# Configuration parameters: AllowedParentClasses. -Lint/MissingSuper: - Exclude: - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/chain/constant.rb' - - 'lib/solargraph/source/chain/if.rb' - - 'lib/solargraph/source/chain/literal.rb' - - 'lib/solargraph/source/chain/or.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Lint/NonAtomicFileOperation: Exclude: @@ -373,11 +337,6 @@ Lint/RedundantStringCoercion: - 'lib/solargraph/pin/namespace.rb' - 'lib/solargraph/rbs_map/conversions.rb' -# This cop supports safe autocorrection (--autocorrect). -Lint/RedundantWithIndex: - Exclude: - - 'lib/solargraph/language_server/message/completion_item/resolve.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: strict, consistent @@ -390,16 +349,6 @@ Lint/UnderscorePrefixedVariableName: Exclude: - 'lib/solargraph/library.rb' -# Configuration parameters: Methods. -Lint/UnexpectedBlockArity: - Exclude: - - 'lib/solargraph/language_server/message/completion_item/resolve.rb' - - 'lib/solargraph/type_checker.rb' - -Lint/UnmodifiedReduceAccumulator: - Exclude: - - 'lib/solargraph/pin/method.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: @@ -425,7 +374,12 @@ Lint/UselessMethodDefinition: # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max. Metrics/AbcSize: - Enabled: false + Exclude: + - 'lib/solargraph/api_map/source_to_yard.rb' + - 'lib/solargraph/parser/parser_gem/node_chainer.rb' + - 'lib/solargraph/source/source_chainer.rb' + - 'lib/solargraph/source_map/clip.rb' + - 'lib/solargraph/source_map/mapper.rb' # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. # AllowedMethods: refine @@ -447,11 +401,16 @@ Metrics/ClassLength: # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/CyclomaticComplexity: - Enabled: false + Exclude: + - 'lib/solargraph/type_checker.rb' # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: - Enabled: false + Exclude: + - 'lib/solargraph/complex_type.rb' + - 'lib/solargraph/convention/struct_definition.rb' + - 'lib/solargraph/parser/parser_gem/node_chainer.rb' + - 'lib/solargraph/source_map/mapper.rb' # Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: @@ -468,7 +427,9 @@ Metrics/ParameterLists: # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/PerceivedComplexity: - Enabled: false + Exclude: + - 'lib/solargraph/parser/parser_gem/node_chainer.rb' + - 'lib/solargraph/type_checker.rb' Naming/AccessorMethodName: Exclude: @@ -493,7 +454,6 @@ Naming/MemoizedInstanceVariableName: 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' @@ -513,22 +473,10 @@ Naming/PredicatePrefix: Exclude: - 'spec/**/*' - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/parser/parser_gem/class_methods.rb' - 'lib/solargraph/pin_cache.rb' - 'lib/solargraph/workspace.rb' -# Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns, ForbiddenIdentifiers, ForbiddenPatterns. -# SupportedStyles: snake_case, camelCase -Naming/VariableName: - Exclude: - - 'spec/fixtures/unicode.rb' - -RSpec/Be: - Exclude: - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/source/source_chainer_spec.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). RSpec/BeEq: Exclude: @@ -560,9 +508,7 @@ 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' # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. @@ -575,10 +521,6 @@ RSpec/EmptyLineAfterFinalLet: Exclude: - 'spec/workspace/config_spec.rb' -# Configuration parameters: Max, CountAsOne. -RSpec/ExampleLength: - Enabled: false - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: CustomTransform, IgnoredWords, DisallowedExamples. # DisallowedExamples: works diff --git a/Gemfile b/Gemfile index 3a5ae2b84..8de7ad88e 100755 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ -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 +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 diff --git a/Rakefile b/Rakefile index f27abfeb6..146aeac45 100755 --- a/Rakefile +++ b/Rakefile @@ -1,138 +1,138 @@ -require 'rake' -require 'bundler/gem_tasks' -require 'fileutils' -require 'open3' - -desc "Open a Pry session preloaded with this library" -task :console do - sh "pry -I lib -r solargraph.rb" -end - -desc "Run the type checker" -task typecheck: [:typecheck_strong] - -desc "Run the type checker at typed level - return code issues provable without annotations being correct" -task :typecheck_typed do - sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level typed" -end - -desc "Run the type checker at strict level - report issues using type annotations" -task :typecheck_strict do - sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strict" -end - -desc "Run the type checker at strong level - enforce that type annotations exist" -task :typecheck_strong do - sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strong" -end - -desc "Run the type checker at alpha level - run high-false-alarm checks" -task :typecheck_alpha do - sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level alpha" -end - -desc "Run RSpec tests, starting with the ones that failed last time" -task spec: %i[spec_failed undercover_no_fail full_spec] do - undercover -end - -desc "Run all RSpec tests" -task :full_spec do - warn 'starting spec' - sh 'TEST_COVERAGE_COMMAND_NAME=full-new bundle exec rspec' # --profile' - warn 'ending spec' - # move coverage/full-new to coverage/full on success so that we - # always have the last successful run's 'coverage info - FileUtils.rm_rf('coverage/full') - FileUtils.mv('coverage/full-new', 'coverage/full') -end - -# @sg-ignore #undercover return type could not be inferred -# @return [Process::Status] -def undercover - simplecov_collate - cmd = 'bundle exec undercover ' \ - '--simplecov coverage/combined/coverage.json ' \ - '--exclude-files "Rakefile,spec/*,spec/**/*,lib/solargraph/version.rb" ' \ - '--compare origin/master' - output, status = Bundler.with_unbundled_env do - Open3.capture2e(cmd) - end - puts output - $stdout.flush - status -rescue StandardError => e - warn "hit error: #{e.message}" - # @sg-ignore Need to add nil check here - warn "Backtrace:\n#{e.backtrace.join("\n")}" - warn "output: #{output}" - puts "Flushing" - $stdout.flush - raise -end - -desc "Check PR coverage" -task :undercover do - raise "Undercover failed" unless undercover.success? -end - -desc "Branch-focused fast-feedback quality/spec/coverage checks" -task test: %i[overcommit spec typecheck] do - # do these in order - Rake::Task['typecheck_strict'].invoke - Rake::Task['typecheck_strong'].invoke - Rake::Task['typecheck_alpha'].invoke -end - -desc "Re-run failed specs. Add --fail-fast in your .rspec-local file if desired." -task :spec_failed do - # allow user to check out any persistent failures while looking for - # more in the whole test suite - sh 'TEST_COVERAGE_COMMAND_NAME=next-failure bundle exec rspec --only-failures || true' -end - -desc "Run undercover and show output without failing the task if it fails" -task :undercover_no_fail do - undercover -rescue StandardError - puts "Undercover failed, but continuing with other tasks." -end - -# @return [void] -def simplecov_collate - require 'simplecov' - require 'simplecov-lcov' - require 'undercover/simplecov_formatter' - - SimpleCov.collate(Dir["coverage/{next-failure,full,ad-hoc}/.resultset.json"]) do - cname = 'combined' - command_name cname - new_dir = File.join('coverage', cname) - coverage_dir new_dir - - formatter \ - SimpleCov::Formatter::MultiFormatter - .new([ - SimpleCov::Formatter::HTMLFormatter, - SimpleCov::Formatter::Undercover, - SimpleCov::Formatter::LcovFormatter - ]) - SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true - end - puts "Simplecov collated results into coverage/combined/.resultset.json" -rescue StandardError => e - puts "Simplecov collate failed: #{e.message}" -ensure - $stdout.flush -end - -desc 'Add incremental coverage for rapid iteration with undercover' -task :simplecov_collate do - simplecov_collate -end - -desc "Show quality checks on this development branch so far, including any staged files" -task :overcommit do - # OVERCOMMIT_DEBUG=1 will show more detail - sh 'SOLARGRAPH_ASSERTS=on bundle exec overcommit --run --diff origin/master' -end +require 'rake' +require 'bundler/gem_tasks' +require 'fileutils' +require 'open3' + +desc "Open a Pry session preloaded with this library" +task :console do + sh "pry -I lib -r solargraph.rb" +end + +desc "Run the type checker" +task typecheck: [:typecheck_strong] + +desc "Run the type checker at typed level - return code issues provable without annotations being correct" +task :typecheck_typed do + sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level typed" +end + +desc "Run the type checker at strict level - report issues using type annotations" +task :typecheck_strict do + sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strict" +end + +desc "Run the type checker at strong level - enforce that type annotations exist" +task :typecheck_strong do + sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strong" +end + +desc "Run the type checker at alpha level - run high-false-alarm checks" +task :typecheck_alpha do + sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level alpha" +end + +desc "Run RSpec tests, starting with the ones that failed last time" +task spec: %i[spec_failed undercover_no_fail full_spec] do + undercover +end + +desc "Run all RSpec tests" +task :full_spec do + warn 'starting spec' + sh 'TEST_COVERAGE_COMMAND_NAME=full-new bundle exec rspec' # --profile' + warn 'ending spec' + # move coverage/full-new to coverage/full on success so that we + # always have the last successful run's 'coverage info + FileUtils.rm_rf('coverage/full') + FileUtils.mv('coverage/full-new', 'coverage/full') +end + +# @sg-ignore #undercover return type could not be inferred +# @return [Process::Status] +def undercover + simplecov_collate + cmd = 'bundle exec undercover ' \ + '--simplecov coverage/combined/coverage.json ' \ + '--exclude-files "Rakefile,spec/*,spec/**/*,lib/solargraph/version.rb" ' \ + '--compare origin/master' + output, status = Bundler.with_unbundled_env do + Open3.capture2e(cmd) + end + puts output + $stdout.flush + status +rescue StandardError => e + warn "hit error: #{e.message}" + # @sg-ignore Need to add nil check here + warn "Backtrace:\n#{e.backtrace.join("\n")}" + warn "output: #{output}" + puts "Flushing" + $stdout.flush + raise +end + +desc "Check PR coverage" +task :undercover do + raise "Undercover failed" unless undercover.success? +end + +desc "Branch-focused fast-feedback quality/spec/coverage checks" +task test: %i[overcommit spec typecheck] do + # do these in order + Rake::Task['typecheck_strict'].invoke + Rake::Task['typecheck_strong'].invoke + Rake::Task['typecheck_alpha'].invoke +end + +desc "Re-run failed specs. Add --fail-fast in your .rspec-local file if desired." +task :spec_failed do + # allow user to check out any persistent failures while looking for + # more in the whole test suite + sh 'TEST_COVERAGE_COMMAND_NAME=next-failure bundle exec rspec --only-failures || true' +end + +desc "Run undercover and show output without failing the task if it fails" +task :undercover_no_fail do + undercover +rescue StandardError + puts "Undercover failed, but continuing with other tasks." +end + +# @return [void] +def simplecov_collate + require 'simplecov' + require 'simplecov-lcov' + require 'undercover/simplecov_formatter' + + SimpleCov.collate(Dir["coverage/{next-failure,full,ad-hoc}/.resultset.json"]) do + cname = 'combined' + command_name cname + new_dir = File.join('coverage', cname) + coverage_dir new_dir + + formatter \ + SimpleCov::Formatter::MultiFormatter + .new([ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::Undercover, + SimpleCov::Formatter::LcovFormatter + ]) + SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true + end + puts "Simplecov collated results into coverage/combined/.resultset.json" +rescue StandardError => e + puts "Simplecov collate failed: #{e.message}" +ensure + $stdout.flush +end + +desc 'Add incremental coverage for rapid iteration with undercover' +task :simplecov_collate do + simplecov_collate +end + +desc "Show quality checks on this development branch so far, including any staged files" +task :overcommit do + # OVERCOMMIT_DEBUG=1 will show more detail + sh 'SOLARGRAPH_ASSERTS=on bundle exec overcommit --run --diff origin/master' +end diff --git a/lib/solargraph/complex_type/type_methods.rb b/lib/solargraph/complex_type/type_methods.rb index 1725189a4..9ba833c36 100644 --- a/lib/solargraph/complex_type/type_methods.rb +++ b/lib/solargraph/complex_type/type_methods.rb @@ -180,9 +180,8 @@ def rooted_substring def generate_substring_from(&to_str) key_types_str = key_types.map(&to_str).join(', ') subtypes_str = subtypes.map(&to_str).join(', ') - if key_types.none?(&:defined?) && subtypes.none?(&:defined?) - '' - elsif key_types.empty? && subtypes.empty? + if (key_types.none?(&:defined?) && subtypes.none?(&:defined?)) || + (key_types.empty? && subtypes.empty?) '' elsif hash_parameters? "{#{key_types_str} => #{subtypes_str}}" diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index 3dc4b450a..5a08d44b6 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -547,7 +547,7 @@ def completions_at uri, line, column end # @return [Bool] if has pending completion request - def has_pending_completions? + def pending_completions? message_worker.messages.reverse_each.any? { |req| req['method'] == 'textDocument/completion' } end diff --git a/lib/solargraph/language_server/message/completion_item/resolve.rb b/lib/solargraph/language_server/message/completion_item/resolve.rb index 85e03ad4f..0e181460f 100644 --- a/lib/solargraph/language_server/message/completion_item/resolve.rb +++ b/lib/solargraph/language_server/message/completion_item/resolve.rb @@ -43,7 +43,7 @@ def markup_content text def join_docs pins result = [] last_link = nil - pins.each_with_index do |pin| + pins.each do |pin| this_link = host.options['enablePages'] ? pin.link_documentation : pin.text_documentation if this_link && this_link != last_link && this_link != 'undefined' result.push this_link diff --git a/lib/solargraph/language_server/message/text_document/completion.rb b/lib/solargraph/language_server/message/text_document/completion.rb index 5b9acec33..550ae40ef 100644 --- a/lib/solargraph/language_server/message/text_document/completion.rb +++ b/lib/solargraph/language_server/message/text_document/completion.rb @@ -6,7 +6,7 @@ module Message module TextDocument class Completion < Base def process - return set_error(ErrorCodes::REQUEST_CANCELLED, "cancelled by so many request") if host.has_pending_completions? + return set_error(ErrorCodes::REQUEST_CANCELLED, "cancelled by so many request") if host.pending_completions? line = params['position']['line'] col = params['position']['character'] diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 752c2f0df..a057fef31 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -716,13 +716,13 @@ def parse_comments # True if two docstrings have the same tags, regardless of any other # differences. # - # @param d1 [YARD::Docstring] - # @param d2 [YARD::Docstring] + # @param docstring1 [YARD::Docstring] + # @param docstring2 [YARD::Docstring] # @return [Boolean] - def compare_docstring_tags d1, d2 - return false if d1.tags.length != d2.tags.length - d1.tags.each_index do |i| - return false unless compare_tags(d1.tags[i], d2.tags[i]) + def compare_docstring_tags docstring1, docstring2 + return false if docstring1.tags.length != docstring2.tags.length + docstring1.tags.each_index do |i| + return false unless compare_tags(docstring1.tags[i], docstring2.tags[i]) end true end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index e6d68c151..d23adb599 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -504,7 +504,7 @@ def combine_same_type_arity_signatures(same_type_arity_signatures) # @param old_signatures [Array] # @param new_signature [Pin::Signature] same_type_arity_signatures.reduce([]) do |old_signatures, new_signature| - next [new_signature] if old_signatures.empty? + next old_signatures + [new_signature] if old_signatures.empty? found_merge = false old_signatures.flat_map do |old_signature| diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 4b1ec1153..de41ede82 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -550,12 +550,6 @@ def serialize_yard_gem(gemspec, pins) save(yard_gem_path(gemspec), pins) end - # @param gemspec [Gem::Specification] - # @return [Boolean] - def has_yard?(gemspec) - exist?(yard_gem_path(gemspec)) - end - # @param gemspec [Gem::Specification] # @param hash [String, nil] # @return [String] diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 32d8a15e4..f66f95f53 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -28,7 +28,8 @@ class Call < Chain::Link # @param arguments [::Array] # @param block [Chain, nil] def initialize word, location = nil, arguments = [], block = nil - @word = word + super(word) + @location = location @arguments = arguments @block = block diff --git a/lib/solargraph/source/chain/constant.rb b/lib/solargraph/source/chain/constant.rb index b1c25fab9..688705150 100644 --- a/lib/solargraph/source/chain/constant.rb +++ b/lib/solargraph/source/chain/constant.rb @@ -6,6 +6,8 @@ class Chain class Constant < Link def initialize word @word = word + + super end def resolve api_map, name_pin, locals diff --git a/lib/solargraph/source/chain/if.rb b/lib/solargraph/source/chain/if.rb index db0b2481c..de578419e 100644 --- a/lib/solargraph/source/chain/if.rb +++ b/lib/solargraph/source/chain/if.rb @@ -4,12 +4,10 @@ module Solargraph class Source class Chain class If < Link - def word - '' - end - # @param links [::Array] def initialize links + super('') + @links = links end diff --git a/lib/solargraph/source/chain/literal.rb b/lib/solargraph/source/chain/literal.rb index 03c6149c1..9f1b64057 100644 --- a/lib/solargraph/source/chain/literal.rb +++ b/lib/solargraph/source/chain/literal.rb @@ -6,15 +6,13 @@ module Solargraph class Source class Chain class Literal < Link - def word - @word ||= "<#{@type}>" - end - - attr_reader :value + attr_reader :word, :value # @param type [String] # @param node [Parser::AST::Node, Object] def initialize type, node + super("<#{@type}>") + if node.is_a?(::Parser::AST::Node) # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check if node.type == :true diff --git a/lib/solargraph/source/chain/or.rb b/lib/solargraph/source/chain/or.rb index 655abe4aa..327d465b7 100644 --- a/lib/solargraph/source/chain/or.rb +++ b/lib/solargraph/source/chain/or.rb @@ -4,14 +4,12 @@ module Solargraph class Source class Chain class Or < Link - def word - '' - end - attr_reader :links # @param links [::Array] def initialize links + super('') + @links = links end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index d4886afae..afb0aaa03 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -407,7 +407,7 @@ def argument_problems_for chain, api_map, closure_pin, locals, location return [] 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| + pin.signatures.sort_by { |sig| sig.parameters.length }.each do |sig| params = param_details_from_stack(sig, pins) signature_errors = signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin diff --git a/solargraph.gemspec b/solargraph.gemspec index a3e8e88cd..bd358cde9 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -1,76 +1,76 @@ -# @sg-ignore Should better support meaning of '&' in RBS -$LOAD_PATH.unshift File.dirname(__FILE__) + '/lib' -require 'solargraph/version' -require 'date' - -# @param s [Gem::Specification] -Gem::Specification.new do |s| - s.name = 'solargraph' - s.version = Solargraph::VERSION - s.date = Date.today.strftime("%Y-%m-%d") - s.summary = "A Ruby language server" - s.description = "IDE tools for code completion, inline documentation, and static analysis" - s.authors = ["Fred Snyder"] - s.email = 'admin@castwide.com' - s.files = Dir.chdir(File.expand_path('..', __FILE__)) do - # @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' - s.executables = ['solargraph'] - s.metadata["funding_uri"] = "https://www.patreon.com/castwide" - s.metadata["bug_tracker_uri"] = "https://github.com/castwide/solargraph/issues" - s.metadata["changelog_uri"] = "https://github.com/castwide/solargraph/blob/master/CHANGELOG.md" - s.metadata["source_code_uri"] = "https://github.com/castwide/solargraph" - s.metadata["rubygems_mfa_required"] = "true" - - 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' - s.add_runtime_dependency 'diff-lcs', '~> 1.4' - s.add_runtime_dependency 'jaro_winkler', '~> 1.6', '>= 1.6.1' - s.add_runtime_dependency 'kramdown', '~> 2.3' - s.add_runtime_dependency 'kramdown-parser-gfm', '~> 1.1' - 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.6.1', '<= 4.0.0.dev.5'] - s.add_runtime_dependency 'reverse_markdown', '~> 3.0' - s.add_runtime_dependency 'rubocop', '~> 1.76' - s.add_runtime_dependency 'thor', '~> 1.0' - s.add_runtime_dependency 'tilt', '~> 2.0' - s.add_runtime_dependency 'yard', '~> 0.9', '>= 0.9.24' - s.add_runtime_dependency 'yard-solargraph', '~> 0.1' - s.add_runtime_dependency 'yard-activesupport-concern', '~> 0.0' - - s.add_development_dependency 'pry', '~> 0.15' - s.add_development_dependency 'public_suffix', '~> 3.1' - s.add_development_dependency 'rake', '~> 13.2' - s.add_development_dependency 'rspec', '~> 3.5' - # - # 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.80.0.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' - s.add_development_dependency 'overcommit', '~> 0.68.0' - s.add_development_dependency 'webmock', '~> 3.6' - # work around missing yard dependency needed as of Ruby 3.5 - s.add_development_dependency 'irb', '~> 1.15' -end +# @sg-ignore Should better support meaning of '&' in RBS +$LOAD_PATH.unshift File.dirname(__FILE__) + '/lib' +require 'solargraph/version' +require 'date' + +# @param s [Gem::Specification] +Gem::Specification.new do |s| + s.name = 'solargraph' + s.version = Solargraph::VERSION + s.date = Date.today.strftime("%Y-%m-%d") + s.summary = "A Ruby language server" + s.description = "IDE tools for code completion, inline documentation, and static analysis" + s.authors = ["Fred Snyder"] + s.email = 'admin@castwide.com' + s.files = Dir.chdir(File.expand_path('..', __FILE__)) do + # @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' + s.executables = ['solargraph'] + s.metadata["funding_uri"] = "https://www.patreon.com/castwide" + s.metadata["bug_tracker_uri"] = "https://github.com/castwide/solargraph/issues" + s.metadata["changelog_uri"] = "https://github.com/castwide/solargraph/blob/master/CHANGELOG.md" + s.metadata["source_code_uri"] = "https://github.com/castwide/solargraph" + s.metadata["rubygems_mfa_required"] = "true" + + 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' + s.add_runtime_dependency 'diff-lcs', '~> 1.4' + s.add_runtime_dependency 'jaro_winkler', '~> 1.6', '>= 1.6.1' + s.add_runtime_dependency 'kramdown', '~> 2.3' + s.add_runtime_dependency 'kramdown-parser-gfm', '~> 1.1' + 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.6.1', '<= 4.0.0.dev.5'] + s.add_runtime_dependency 'reverse_markdown', '~> 3.0' + s.add_runtime_dependency 'rubocop', '~> 1.76' + s.add_runtime_dependency 'thor', '~> 1.0' + s.add_runtime_dependency 'tilt', '~> 2.0' + s.add_runtime_dependency 'yard', '~> 0.9', '>= 0.9.24' + s.add_runtime_dependency 'yard-solargraph', '~> 0.1' + s.add_runtime_dependency 'yard-activesupport-concern', '~> 0.0' + + s.add_development_dependency 'pry', '~> 0.15' + s.add_development_dependency 'public_suffix', '~> 3.1' + s.add_development_dependency 'rake', '~> 13.2' + s.add_development_dependency 'rspec', '~> 3.5' + # + # 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.80.0.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' + s.add_development_dependency 'overcommit', '~> 0.68.0' + s.add_development_dependency 'webmock', '~> 3.6' + # work around missing yard dependency needed as of Ruby 3.5 + s.add_development_dependency 'irb', '~> 1.15' +end diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index a75428d22..e488c2ada 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe 'Solargraph::ApiMap methods' do +describe Solargraph::ApiMap do let(:api_map) { Solargraph::ApiMap.new } let(:bench) do Solargraph::Bench.new(external_requires: external_requires, workspace: Solargraph::Workspace.new('.')) diff --git a/spec/rbs_map/stdlib_map_spec.rb b/spec/rbs_map/stdlib_map_spec.rb index c9db9bb48..77d99a426 100644 --- a/spec/rbs_map/stdlib_map_spec.rb +++ b/spec/rbs_map/stdlib_map_spec.rb @@ -2,7 +2,7 @@ it "finds stdlib require paths" do rbs_map = Solargraph::RbsMap::StdlibMap.load('fileutils') pin = rbs_map.path_pin('FileUtils#chdir') - expect(pin).to be + expect(pin).not_to be_nil end it 'maps YAML' do diff --git a/spec/source/source_chainer_spec.rb b/spec/source/source_chainer_spec.rb index b89aae62e..8378dfd43 100644 --- a/spec/source/source_chainer_spec.rb +++ b/spec/source/source_chainer_spec.rb @@ -348,7 +348,8 @@ def strings; end end )) chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 9)) - expect(chain.links.map(&:class)).to be + expect(chain.links.map(&:class)) + .to eq([Solargraph::Source::Chain::Call, Solargraph::Source::Chain::Call]) end it 'infers specific array type from block sent to Array#map' do diff --git a/spec/source_map/node_processor_spec.rb b/spec/source_map/node_processor_spec.rb index a0ce0bc91..d8428dd58 100644 --- a/spec/source_map/node_processor_spec.rb +++ b/spec/source_map/node_processor_spec.rb @@ -1,4 +1,4 @@ -describe 'Node processor (generic)' do +describe Solargraph::SourceMap::NodeProcessor do it 'maps arg parameters' do map = Solargraph::SourceMap.load_string(%( class Foo From fecbb8e3647b54bf3f5f496bbb73ea4988c9af41 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 13 Jan 2026 21:31:17 -0500 Subject: [PATCH 894/930] RuboCop manual fixes --- lib/solargraph/source/chain/literal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/source/chain/literal.rb b/lib/solargraph/source/chain/literal.rb index 9f1b64057..59afa32b3 100644 --- a/lib/solargraph/source/chain/literal.rb +++ b/lib/solargraph/source/chain/literal.rb @@ -11,7 +11,7 @@ class Literal < Link # @param type [String] # @param node [Parser::AST::Node, Object] def initialize type, node - super("<#{@type}>") + super("<#{type}>") if node.is_a?(::Parser::AST::Node) # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check From aed854a6411448be8a31eebad8944b2e4cc1b34f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 13 Jan 2026 21:33:46 -0500 Subject: [PATCH 895/930] RuboCop manual fixes --- spec/source_map/node_processor_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/source_map/node_processor_spec.rb b/spec/source_map/node_processor_spec.rb index d8428dd58..7b8c8ccdc 100644 --- a/spec/source_map/node_processor_spec.rb +++ b/spec/source_map/node_processor_spec.rb @@ -1,4 +1,4 @@ -describe Solargraph::SourceMap::NodeProcessor do +describe Solargraph::Parser::NodeProcessor do it 'maps arg parameters' do map = Solargraph::SourceMap.load_string(%( class Foo From 80b8e7b63634eb30fa4b5be7c1c17db6ec1f04d4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 13 Jan 2026 21:47:21 -0500 Subject: [PATCH 896/930] RuboCop manual fixes --- spec/doc_map_spec.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index eaeb65a13..6f28689a4 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -49,17 +49,13 @@ ['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 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', + 'actionpack', 'activerecord', 'shoulda-matchers', 'rspec-sidekiq', From 4c05f2806078c3433b08ce3703eeb8a34e9325fc Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 14 Jan 2026 03:15:31 -0500 Subject: [PATCH 897/930] Raise InvalidOffsetError for offsets > text (#1155) * Raise InvalidOffsetError for offsets > text * Linting * Fix fencepost error * Additional fencepost test * Document exception --- lib/solargraph/position.rb | 15 +++++++++------ spec/position_spec.rb | 13 +++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index e47ed8bc8..a0d7bbc2e 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -58,22 +58,21 @@ def inspect # @return [Integer] def self.to_offset text, position return 0 if text.empty? + newline_index = -1 - cursor = 0 line = -1 - last_line_index = 0 + while (newline_index = text.index("\n", newline_index + 1)) && line <= position.line line += 1 break if line == position.line - line_length = newline_index - last_line_index - - cursor += line_length.zero? ? 1 : line_length + line_length = newline_index - last_line_index last_line_index = newline_index end - cursor + position.character + last_line_index += 1 if position.line > 0 + last_line_index + position.character end # Get a numeric offset for the specified text and a position identified @@ -89,10 +88,14 @@ def self.line_char_to_offset text, line, character # Get a position for the specified text and offset. # + # @raise [InvalidOffsetError] if the offset is outside the text range + # # @param text [String] # @param offset [Integer] # @return [Position] def self.from_offset text, offset + raise InvalidOffsetError if offset > text.length + cursor = 0 line = 0 character = offset diff --git a/spec/position_spec.rb b/spec/position_spec.rb index 88dedcd26..e8dab1960 100644 --- a/spec/position_spec.rb +++ b/spec/position_spec.rb @@ -18,6 +18,7 @@ expect(Solargraph::Position.to_offset(text, Solargraph::Position.new(0, 4))).to eq(4) expect(Solargraph::Position.to_offset(text, Solargraph::Position.new(2, 12))).to eq(29) expect(Solargraph::Position.to_offset(text, Solargraph::Position.new(2, 27))).to eq(44) + expect(Solargraph::Position.to_offset(text, Solargraph::Position.new(3, 8))).to eq(58) end it 'constructs position from offset' do @@ -33,4 +34,16 @@ Solargraph::Position.normalize('0, 1') }.to raise_error(ArgumentError) end + + it 'avoids fencepost errors' do + text = " class Foo\n def bar baz, boo = 'boo'\n end\n end\n " + offset = Solargraph::Position.to_offset(text, Solargraph::Position.new(3, 6)) + expect(offset).to eq(67) + end + + it 'avoids fencepost errors with multiple blank lines' do + text = " class Foo\n def bar baz, boo = 'boo'\n\n end\n end\n " + offset = Solargraph::Position.to_offset(text, Solargraph::Position.new(4, 6)) + expect(offset).to eq(68) + end end From be2c6f506512158237751eedf3ea533e8dfbd8d5 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sun, 18 Jan 2026 11:46:28 -0500 Subject: [PATCH 898/930] Refactor RbsMap::Conversions Let's work more directly with type objects and reduce duplication --- lib/solargraph/rbs_map/conversions.rb | 55 ++++++++++++++------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index b958895fb..baeecf598 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -172,8 +172,8 @@ def class_decl_to_pin decl generic_defaults = {} decl.type_params.each do |param| if param.default_type - tag = other_type_to_tag param.default_type - generic_defaults[param.name.to_s] = ComplexType.parse(tag).force_rooted + type = other_type_to_type param.default_type + generic_defaults[param.name.to_s] = type end end class_name = decl.name.relative!.to_s @@ -302,8 +302,8 @@ def module_alias_decl_to_pin decl # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - tag = other_type_to_tag(decl.type) - pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) + type = other_type_to_type(decl.type) + pins.push create_constant(decl.name.relative!.to_s, type.rooted_tag, decl.comment&.string, decl) end # @param decl [RBS::AST::Declarations::Global] @@ -318,7 +318,7 @@ def global_decl_to_pin decl type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -479,7 +479,7 @@ def parts_of_function type, pin if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) return [ [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], - ComplexType.try_parse(method_type_to_tag(type)).force_rooted + method_type_to_type(type) ] end @@ -502,8 +502,7 @@ 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}" - inner_rest_positional_type = - ComplexType.try_parse(other_type_to_tag(type.type.rest_positionals.type)) + inner_rest_positional_type = other_type_to_type(type.type.rest_positionals.type) rest_positional_type = ComplexType::UniqueType.new('Array', [], [inner_rest_positional_type], @@ -540,8 +539,7 @@ def parts_of_function type, pin source: :rbs, type_location: type_location) end - rooted_tag = method_type_to_tag(type) - return_type = ComplexType.try_parse(rooted_tag).force_rooted + return_type = method_type_to_type(type) [parameters, return_type] end @@ -563,7 +561,7 @@ def attr_reader_to_pin(decl, closure, context) visibility: visibility, source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) logger.debug { "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" } pins.push pin @@ -592,12 +590,12 @@ def attr_writer_to_pin(decl, closure, context) pin.parameters << Solargraph::Pin::Parameter.new( name: 'value', - return_type: ComplexType.try_parse(other_type_to_tag(decl.type)).force_rooted, + return_type: other_type_to_type(decl.type), source: :rbs, closure: pin, type_location: type_location ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) pins.push pin end @@ -622,7 +620,7 @@ def ivar_to_pin(decl, closure) comments: decl.comment&.string, source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -639,7 +637,7 @@ def cvar_to_pin(decl, closure) type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -656,7 +654,7 @@ def civar_to_pin(decl, closure) type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -724,12 +722,12 @@ def alias_to_pin decl, closure } # @param type [RBS::MethodType, RBS::Types::Block] - # @return [String] - def method_type_to_tag type + # @return [ComplexType] + def method_type_to_type type if type_aliases.key?(type.type.return_type.to_s) - other_type_to_tag(type_aliases[type.type.return_type.to_s].type) + other_type_to_type(type_aliases[type.type.return_type.to_s].type) else - other_type_to_tag type.type.return_type + other_type_to_type type.type.return_type end end @@ -758,20 +756,21 @@ def type_tag(type_name, type_args = []) # @param type [RBS::Types::Bases::Base,Object] RBS type object. # Note: Generally these extend from RBS::Types::Bases::Base, # but not all. - # @return [String] - def other_type_to_tag type - if type.is_a?(RBS::Types::Optional) - "#{other_type_to_tag(type.type)}, nil" + # + # @return [ComplexType] + def other_type_to_type type + tag = if type.is_a?(RBS::Types::Optional) + "#{other_type_to_type(type.type).rooted_tag}, nil" elsif type.is_a?(RBS::Types::Bases::Any) 'undefined' elsif type.is_a?(RBS::Types::Bases::Bool) 'Boolean' elsif type.is_a?(RBS::Types::Tuple) - "Array(#{type.types.map { |t| other_type_to_tag(t) }.join(', ')})" + "Array(#{type.types.map { |t| other_type_to_type(t).rooted_tag }.join(', ')})" elsif type.is_a?(RBS::Types::Literal) type.literal.inspect elsif type.is_a?(RBS::Types::Union) - type.types.map { |t| other_type_to_tag(t) }.join(', ') + type.types.map { |t| other_type_to_type(t).rooted_tag }.join(', ') elsif type.is_a?(RBS::Types::Record) # @todo Better record support 'Hash' @@ -798,7 +797,8 @@ def other_type_to_tag type # determine dead code 'undefined' elsif type.is_a?(RBS::Types::Intersection) - type.types.map { |member| other_type_to_tag(member) }.join(', ') + type.types.inject(&:intersect) + type.types.map { |member| other_type_to_type(member).rooted_tag }.join(', ') elsif type.is_a?(RBS::Types::Proc) 'Proc' elsif type.is_a?(RBS::Types::Alias) @@ -820,6 +820,7 @@ def other_type_to_tag type Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" 'undefined' end + ComplexType.try_parse(tag).force_rooted end # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Module] From 676da4c4cec040d735a9a54265c91dc91ec462bb Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Mon, 19 Jan 2026 07:26:26 -0500 Subject: [PATCH 899/930] Release 0.58.2 --- CHANGELOG.md | 8 +++++++- lib/solargraph/version.rb | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 315ba0c73..e07381830 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ +## 0.58.2 - January 19, 2026 +- Avoid rbs pollution (#1146) +- Fix 'solargraph pin --references ClassName' private method call (#1150) +- Improve memory efficiency of Position class (#1054) +- Raise InvalidOffsetError for offsets > text (#1155) + ## 0.58.1 - January 2, 2026 -- Normalize line endings to LF (#1142) +- Normalize line endings to LF (#1142) ## 0.58.0 - January 1, 2026 - Faster constant resolution (#1083) diff --git a/lib/solargraph/version.rb b/lib/solargraph/version.rb index 5bb8e52f8..11dd7e1ff 100755 --- a/lib/solargraph/version.rb +++ b/lib/solargraph/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Solargraph - VERSION = '0.58.1' + VERSION = '0.58.2' end From 504ba73bd496bf568d59cbe9440f76b7362f43e0 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 19 Jan 2026 11:04:09 -0500 Subject: [PATCH 900/930] Complete other_type_to_type transition --- lib/solargraph/rbs_map/conversions.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index baeecf598..9ad02408a 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -489,14 +489,14 @@ def parts_of_function type, pin # @sg-ignore RBS generic type understanding issue name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" # @sg-ignore RBS generic type understanding issue - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, source: :rbs, type_location: type_location) + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: other_type_to_type(param.type), source: :rbs, type_location: type_location) end type.type.optional_positionals.each do |param| # @sg-ignore RBS generic type understanding issue name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue - return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + return_type: other_type_to_type(param.type), type_location: type_location, source: :rbs) end @@ -521,7 +521,7 @@ def parts_of_function type, pin name = orig ? orig.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue - return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + return_type: other_type_to_type(param.type), source: :rbs, type_location: type_location) end type.type.optional_keywords.each do |orig, param| @@ -529,7 +529,7 @@ def parts_of_function type, pin name = orig ? orig.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue - return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + return_type: other_type_to_type(param.type), type_location: type_location, source: :rbs) end @@ -736,9 +736,7 @@ def method_type_to_type 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) }.map do |t| - ComplexType.try_parse(t).force_rooted - end + params = type_args.map { |a| other_type_to_type(a) } if base == 'Hash' && params.length == 2 ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) else @@ -797,7 +795,6 @@ def other_type_to_type type # determine dead code 'undefined' elsif type.is_a?(RBS::Types::Intersection) - type.types.inject(&:intersect) type.types.map { |member| other_type_to_type(member).rooted_tag }.join(', ') elsif type.is_a?(RBS::Types::Proc) 'Proc' From 608d64b532992995af5dc97faad38629205c4f9c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 20 Jan 2026 07:12:22 -0500 Subject: [PATCH 901/930] Refactor --- lib/solargraph/rbs_map/conversions.rb | 128 ++++++++++++++------------ 1 file changed, 71 insertions(+), 57 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 9ad02408a..03103fac7 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -23,7 +23,7 @@ def initialize visibility = :public end # @param loader [RBS::EnvironmentLoader] - def initialize(loader:) + def initialize loader: @loader = loader @pins = [] load_environment_to_pins(loader) @@ -43,12 +43,14 @@ def type_aliases end # @param loader [RBS::EnvironmentLoader] + # # @return [void] - def load_environment_to_pins(loader) + def load_environment_to_pins loader environment = RBS::Environment.from_loader(loader).resolve_type_names - cursor = pins.length if environment.declarations.empty? - Solargraph.logger.info "No RBS declarations found in environment for core_root #{loader.core_root.inspect}, libraries #{loader.libs} and directories #{loader.dirs}" + Solargraph.logger.info 'No RBS declarations found in environment for core_root ' \ + "#{loader.core_root.inspect}, libraries #{loader.libs} and " \ + "directories #{loader.dirs}" return end environment.declarations.each { |decl| convert_decl_to_pin(decl, Solargraph::Pin::ROOT_PIN) } @@ -233,7 +235,7 @@ def interface_decl_to_pin decl, closure def module_decl_to_pin decl module_pin = Solargraph::Pin::Namespace.new( type: :module, - name: decl.name.relative!.to_s, + name: decl.name.relative!.to_s, # TODO type_location: location_decl_to_pin_location(decl.location), closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, @@ -294,7 +296,8 @@ def class_alias_decl_to_pin decl def module_alias_decl_to_pin decl # See https://www.rubydoc.info/gems/rbs/3.4.3/RBS/AST/Declarations/ModuleAlias new_name = decl.new_name.relative!.to_s - old_name = decl.old_name.relative!.to_s + old_name = decl.old_name.relative!.to_s # TODO + old_type = ComplexType.parse(old_name).force_rooted # TODO pins.push create_constant(new_name, old_name, decl.comment&.string, decl, 'Module') end @@ -310,7 +313,7 @@ def constant_decl_to_pin decl # @return [void] def global_decl_to_pin decl closure = Solargraph::Pin::ROOT_PIN - name = decl.name.to_s + name = decl.name.to_s # TODO pin = Solargraph::Pin::GlobalVariable.new( name: name, closure: closure, @@ -318,12 +321,11 @@ def global_decl_to_pin decl type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags # TODO pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end - # Visibility overrides that will allow the Solargraph project # and plugins to pass typechecking using SOLARGRAPH_ASSERTS=on, # so that we can detect any regressions/issues elsewhere in the @@ -339,41 +341,45 @@ def global_decl_to_pin decl # allow that to be extended via .solargraph.yml # @type [Hash{Array(String, Symbol, String) => Symbol} VISIBILITY_OVERRIDE = { - ["Rails::Engine", :instance, "run_tasks_blocks"] => :protected, + ['Rails::Engine', :instance, 'run_tasks_blocks'] => :protected, # Should have been marked as both instance and class method in module -e.g., 'module_function' - ["Kernel", :instance, "pretty_inspect"] => :private, + ['Kernel', :instance, 'pretty_inspect'] => :private, # marked incorrectly in RBS - ["WEBrick::HTTPUtils::FormData", :instance, "next_data"] => :protected, - ["Rails::Command", :class, "command_type"] => :private, - ["Rails::Command", :class, "lookup_paths"] => :private, - ["Rails::Command", :class, "file_lookup_paths"] => :private, - ["Rails::Railtie", :instance, "run_console_blocks"] => :protected, - ["Rails::Railtie", :instance, "run_generators_blocks"] => :protected, - ["Rails::Railtie", :instance, "run_runner_blocks"] => :protected, - ["Rails::Railtie", :instance, "run_tasks_blocks"] => :protected, - ["ActionController::Base", :instance, "_protected_ivars"] => :private, - ["ActionView::Template", :instance, "method_name"] => :public, - ["Module", :instance, "ruby2_keywords"] => :private, - ["Nokogiri::XML::Node", :instance, "coerce"] => :protected, - ["Nokogiri::XML::Document", :class, "empty_doc?"] => :private, - ["Nokogiri::Decorators::Slop", :instance, "respond_to_missing?"] => :public, - ["RuboCop::Cop::RangeHelp", :instance, "source_range"] => :private, - ["AST::Node", :instance, "original_dup"] => :private, - ["Rainbow::Presenter", :instance, "wrap_with_sgr"] => :private, - } - - # @param decl [RBS::AST::Members::MethodDefinition, RBS::AST::Members::AttrReader, RBS::AST::Members::AttrWriter, RBS::AST::Members::AttrAccessor] + ['WEBrick::HTTPUtils::FormData', :instance, 'next_data'] => :protected, + ['Rails::Command', :class, 'command_type'] => :private, + ['Rails::Command', :class, 'lookup_paths'] => :private, + ['Rails::Command', :class, 'file_lookup_paths'] => :private, + ['Rails::Railtie', :instance, 'run_console_blocks'] => :protected, + ['Rails::Railtie', :instance, 'run_generators_blocks'] => :protected, + ['Rails::Railtie', :instance, 'run_runner_blocks'] => :protected, + ['Rails::Railtie', :instance, 'run_tasks_blocks'] => :protected, + ['ActionController::Base', :instance, '_protected_ivars'] => :private, + ['ActionView::Template', :instance, 'method_name'] => :public, + ['Module', :instance, 'ruby2_keywords'] => :private, + ['Nokogiri::XML::Node', :instance, 'coerce'] => :protected, + ['Nokogiri::XML::Document', :class, 'empty_doc?'] => :private, + ['Nokogiri::Decorators::Slop', :instance, 'respond_to_missing?'] => :public, + ['RuboCop::Cop::RangeHelp', :instance, 'source_range'] => :private, + ['AST::Node', :instance, 'original_dup'] => :private, + ['Rainbow::Presenter', :instance, 'wrap_with_sgr'] => :private + }.freeze + private_constant :VISIBILITY_OVERRIDE + + # @param decl [RBS::AST::Members::MethodDefinition, RBS::AST::Members::AttrReader, + # RBS::AST::Members::AttrWriter, RBS::AST::Members::AttrAccessor] # @param closure [Pin::Closure] # @param context [Context] # @param scope [Symbol] :instance or :class # @param name [String] The name of the method # @return [Symbol] - def calculate_method_visibility(decl, context, closure, scope, name) + def calculate_method_visibility decl, context, closure, scope, name override_key = [closure.path, scope, name] visibility = VISIBILITY_OVERRIDE[override_key] simple_override_key = [closure.path, scope] visibility ||= VISIBILITY_OVERRIDE[simple_override_key] - visibility ||= :private if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(decl.name) + if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(decl.name) # TODO + visibility ||= :private + end if decl.kind == :singleton_instance # this is a 'module function' visibility ||= :private @@ -393,10 +399,10 @@ def method_def_to_pin decl, closure, context # having different type params / orders - we may need to match # this data model and have generics live in signatures to # handle those correctly - generics = decl.overloads.map(&:method_type).flat_map(&:type_params).map(&:name).map(&:to_s).uniq + generics = decl.overloads.map(&:method_type).flat_map(&:type_params).map(&:name).map(&:to_s).uniq # TODO if decl.instance? - name = decl.name.to_s + name = decl.name.to_s # TODO final_scope = :instance visibility = calculate_method_visibility(decl, context, closure, final_scope, name) pin = Solargraph::Pin::Method.new( @@ -449,17 +455,19 @@ def method_def_to_sigs decl, pin rbs_block = overload.method_type.block block = if rbs_block block_parameters, block_return_type = parts_of_function(rbs_block, pin) - Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, + Pin::Signature.new(generics: generics, parameters: block_parameters, + return_type: block_return_type, source: :rbs, type_location: type_location, closure: pin) end - Pin::Signature.new(generics: generics, parameters: signature_parameters, return_type: signature_return_type, block: block, source: :rbs, + Pin::Signature.new(generics: generics, parameters: signature_parameters, + return_type: signature_return_type, block: block, source: :rbs, type_location: type_location, closure: pin) end end # @param location [RBS::Location, nil] # @return [Solargraph::Location, nil] - def location_decl_to_pin_location(location) + def location_decl_to_pin_location location return nil if location&.name.nil? # @sg-ignore flow sensitive typing should handle return nil if location&.name.nil? @@ -468,17 +476,18 @@ def location_decl_to_pin_location(location) end_pos = Position.new(location.end_line - 1, location.end_column) range = Range.new(start_pos, end_pos) # @sg-ignore flow sensitve typing should handle return nil if location&.name.nil? - Location.new(location.name.to_s, range) + Location.new(location.name.to_s, range) # TODO end - # @param type [RBS::MethodType,RBS::Types::Block] + # @param type [RBS::MethodType, RBS::Types::Block] # @param pin [Pin::Method] # @return [Array(Array, ComplexType)] def parts_of_function type, pin type_location = pin.type_location if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) return [ - [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], + [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, + type_location: type_location)], method_type_to_type(type) ] end @@ -487,13 +496,14 @@ def parts_of_function type, pin arg_num = -1 type.type.required_positionals.each do |param| # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - # @sg-ignore RBS generic type understanding issue - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: other_type_to_type(param.type), source: :rbs, type_location: type_location) + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" # TODO + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, + return_type: other_type_to_type(param.type), + source: :rbs, type_location: type_location) end type.type.optional_positionals.each do |param| # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" # TODO parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue return_type: other_type_to_type(param.type), @@ -501,7 +511,7 @@ def parts_of_function type, pin source: :rbs) end if type.type.rest_positionals - name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" + name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" # TODO inner_rest_positional_type = other_type_to_type(type.type.rest_positionals.type) rest_positional_type = ComplexType::UniqueType.new('Array', [], @@ -509,12 +519,13 @@ def parts_of_function type, pin 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,) + return_type: rest_positional_type) end type.type.trailing_positionals.each do |param| # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, type_location: type_location) + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" # TODO + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, + type_location: type_location) end type.type.required_keywords.each do |orig, param| # @sg-ignore RBS generic type understanding issue @@ -534,8 +545,9 @@ def parts_of_function type, pin source: :rbs) end if type.type.rest_keywords - name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, name: type.type.rest_keywords.name.to_s, closure: pin, + name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" # TODO + parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, + name: type.type.rest_keywords.name.to_s, closure: pin, # TODO source: :rbs, type_location: type_location) end @@ -547,8 +559,8 @@ def parts_of_function type, pin # @param closure [Pin::Namespace] # @param context [Context] # @return [void] - def attr_reader_to_pin(decl, closure, context) - name = decl.name.to_s + def attr_reader_to_pin decl, closure, context + name = decl.name.to_s # TODO final_scope = decl.kind == :instance ? :instance : :class visibility = calculate_method_visibility(decl, context, closure, final_scope, name) pin = Solargraph::Pin::Method.new( @@ -561,9 +573,11 @@ def attr_reader_to_pin(decl, closure, context) visibility: visibility, source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags # TODO pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) - logger.debug { "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" } + logger.debug do + "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" + end pins.push pin end @@ -571,9 +585,9 @@ def attr_reader_to_pin(decl, closure, context) # @param closure [Pin::Namespace] # @param context [Context] # @return [void] - def attr_writer_to_pin(decl, closure, context) + def attr_writer_to_pin decl, closure, context final_scope = decl.kind == :instance ? :instance : :class - name = "#{decl.name.to_s}=" + name = "#{decl.name}=" visibility = calculate_method_visibility(decl, context, closure, final_scope, name) type_location = location_decl_to_pin_location(decl.location) pin = Solargraph::Pin::Method.new( From 9b15cfb4c102c282088fb15d871cf9f78f475883 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 20 Jan 2026 07:47:37 -0500 Subject: [PATCH 902/930] Refactor --- lib/solargraph/rbs_map/conversions.rb | 48 +++++++++++++-------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 03103fac7..04c2b8ab3 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -299,7 +299,7 @@ def module_alias_decl_to_pin decl old_name = decl.old_name.relative!.to_s # TODO old_type = ComplexType.parse(old_name).force_rooted # TODO - pins.push create_constant(new_name, old_name, decl.comment&.string, decl, 'Module') + pins.push create_constant(new_name, old_type, decl.comment&.string, decl, 'Module') end # @param decl [RBS::AST::Declarations::Constant] @@ -450,7 +450,7 @@ def method_def_to_sigs decl, pin # @param overload [RBS::AST::Members::MethodDefinition::Overload] decl.overloads.map do |overload| type_location = location_decl_to_pin_location(overload.method_type.location) - generics = overload.method_type.type_params.map(&:name).map(&:to_s) + generics = overload.method_type.type_params.map(&:name).map(&:to_s) # TODO signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin) rbs_block = overload.method_type.block block = if rbs_block @@ -609,8 +609,8 @@ def attr_writer_to_pin decl, closure, context closure: pin, type_location: type_location ) - rooted_tag = other_type_to_type(decl.type).rooted_tags - pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) + rooted_tags = other_type_to_type(decl.type).rooted_tags + pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tags)) pins.push pin end @@ -618,7 +618,7 @@ def attr_writer_to_pin decl, closure, context # @param closure [Pin::Namespace] # @param context [Context] # @return [void] - def attr_accessor_to_pin(decl, closure, context) + def attr_accessor_to_pin decl, closure, context attr_reader_to_pin(decl, closure, context) attr_writer_to_pin(decl, closure, context) end @@ -626,15 +626,15 @@ def attr_accessor_to_pin(decl, closure, context) # @param decl [RBS::AST::Members::InstanceVariable] # @param closure [Pin::Namespace] # @return [void] - def ivar_to_pin(decl, closure) + def ivar_to_pin decl, closure pin = Solargraph::Pin::InstanceVariable.new( - name: decl.name.to_s, + name: decl.name.to_s, # TODO closure: closure, type_location: location_decl_to_pin_location(decl.location), comments: decl.comment&.string, source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags # TODO pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -642,8 +642,8 @@ def ivar_to_pin(decl, closure) # @param decl [RBS::AST::Members::ClassVariable] # @param closure [Pin::Namespace] # @return [void] - def cvar_to_pin(decl, closure) - name = decl.name.to_s + def cvar_to_pin decl, closure + name = decl.name.to_s # TODO pin = Solargraph::Pin::ClassVariable.new( name: name, closure: closure, @@ -651,7 +651,7 @@ def cvar_to_pin(decl, closure) type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags # TODO pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -659,8 +659,8 @@ def cvar_to_pin(decl, closure) # @param decl [RBS::AST::Members::ClassInstanceVariable] # @param closure [Pin::Namespace] # @return [void] - def civar_to_pin(decl, closure) - name = decl.name.to_s + def civar_to_pin decl, closure + name = decl.name.to_s # TODO pin = Solargraph::Pin::InstanceVariable.new( name: name, closure: closure, @@ -668,7 +668,7 @@ def civar_to_pin(decl, closure) type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags # TODO pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -680,7 +680,7 @@ def include_to_pin decl, closure type = build_type(decl.name, decl.args) generic_values = type.all_params.map(&:to_s) pins.push Solargraph::Pin::Reference::Include.new( - name: decl.name.relative!.to_s, + name: decl.name.relative!.to_s, # TODO type_location: location_decl_to_pin_location(decl.location), generic_values: generic_values, closure: closure, @@ -693,7 +693,7 @@ def include_to_pin decl, closure # @return [void] def prepend_to_pin decl, closure pins.push Solargraph::Pin::Reference::Prepend.new( - name: decl.name.relative!.to_s, + name: decl.name.relative!.to_s, # TODO type_location: location_decl_to_pin_location(decl.location), closure: closure, source: :rbs @@ -705,7 +705,7 @@ def prepend_to_pin decl, closure # @return [void] def extend_to_pin decl, closure pins.push Solargraph::Pin::Reference::Extend.new( - name: decl.name.relative!.to_s, + name: decl.name.relative!.to_s, # TODO type_location: location_decl_to_pin_location(decl.location), closure: closure, source: :rbs @@ -718,12 +718,12 @@ def extend_to_pin decl, closure def alias_to_pin decl, closure final_scope = decl.singleton? ? :class : :instance pins.push Solargraph::Pin::MethodAlias.new( - name: decl.new_name.to_s, + name: decl.new_name.to_s, # TODO type_location: location_decl_to_pin_location(decl.location), - original: decl.old_name.to_s, + original: decl.old_name.to_s, # TODO closure: closure, scope: final_scope, - source: :rbs, + source: :rbs ) end @@ -738,8 +738,8 @@ def alias_to_pin decl, closure # @param type [RBS::MethodType, RBS::Types::Block] # @return [ComplexType] def method_type_to_type type - if type_aliases.key?(type.type.return_type.to_s) - other_type_to_type(type_aliases[type.type.return_type.to_s].type) + if type_aliases.key?(type.type.return_type.to_s) # TODO + other_type_to_type(type_aliases[type.type.return_type.to_s].type) # TODO else other_type_to_type type.type.return_type end @@ -769,7 +769,7 @@ def type_tag(type_name, type_args = []) # Note: Generally these extend from RBS::Types::Bases::Base, # but not all. # - # @return [ComplexType] + # @return [ComplexType, ComplexType::UniqueType] def other_type_to_type type tag = if type.is_a?(RBS::Types::Optional) "#{other_type_to_type(type.type).rooted_tag}, nil" @@ -845,7 +845,7 @@ def add_mixins decl, namespace type = build_type(mixin.name, mixin.args) generic_values = type.all_params.map(&:to_s) pins.push klass.new( - name: mixin.name.relative!.to_s, + name: mixin.name.relative!.to_s, # TODO type_location: location_decl_to_pin_location(mixin.location), generic_values: generic_values, closure: namespace, From 5d9f761e82b42566b9554482af2839c47cf60eda Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 20 Jan 2026 07:55:16 -0500 Subject: [PATCH 903/930] Refactor --- lib/solargraph/rbs_map/conversions.rb | 53 ++++++++++++++++++--------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 04c2b8ab3..f72f44386 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -88,9 +88,18 @@ def convert_decl_to_pin decl, closure # @param module_pin [Pin::Namespace] # @return [void] def convert_self_types_to_pins decl, module_pin - decl.self_types.each { |self_type| context = convert_self_type_to_pins(self_type, module_pin) } + decl.self_types.each { |self_type| convert_self_type_to_pins(self_type, module_pin) } end + RBS_TO_YARD_TYPE = { + 'bool' => 'Boolean', + 'string' => 'String', + 'int' => 'Integer', + 'untyped' => '', + 'NilClass' => 'nil' + }.freeze + private_constant :RBS_TO_YARD_TYPE + # @param decl [RBS::AST::Declarations::Module::Self] # @param closure [Pin::Namespace] # @return [void] @@ -167,10 +176,22 @@ def convert_member_to_pin member, closure, context context end + # Pull the name of type variables for a generic - not the + # values, the names (e.g., T, U, V). As such, "rooting" isn't a + # thing, these are all in the global namespace. + # + # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Interface, + # RBS::AST::Declarations::Module] + # + # @return [Array] + def type_parameter_names decl + decl.type_params.map(&:name).map(&:to_s) + end + # @param decl [RBS::AST::Declarations::Class] # @return [void] def class_decl_to_pin decl - generics = decl.type_params.map(&:name).map(&:to_s) + # @type [Hash{String => ComplexType}] generic_defaults = {} decl.type_params.each do |param| if param.default_type @@ -178,7 +199,11 @@ def class_decl_to_pin decl generic_defaults[param.name.to_s] = type end end - class_name = decl.name.relative!.to_s + + class_name = decl.name.relative!.to_s # TODO + + generics = type_parameter_names(decl) + class_pin = Solargraph::Pin::Namespace.new( type: :class, name: class_name, @@ -196,7 +221,7 @@ 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 + superclass_name = decl.super_class.name.to_s # TODO pins.push Solargraph::Pin::Reference::Superclass.new( type_location: location_decl_to_pin_location(decl.super_class.location), closure: class_pin, @@ -216,10 +241,10 @@ def interface_decl_to_pin decl, closure class_pin = Solargraph::Pin::Namespace.new( type: :module, type_location: location_decl_to_pin_location(decl.location), - name: decl.name.relative!.to_s, - closure: Solargraph::Pin::ROOT_PIN, + name: decl.name.relative!.to_s, # TODO + closure: Solargraph::Pin::ROOT_PIN, # TODO should this use a closure??? comments: decl.comment&.string, - generics: decl.type_params.map(&:name).map(&:to_s), + generics: type_parameter_names(decl), # HACK: Using :hidden to keep interfaces from appearing in # autocompletion visibility: :hidden, @@ -239,7 +264,7 @@ def module_decl_to_pin decl type_location: location_decl_to_pin_location(decl.location), closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, - generics: decl.type_params.map(&:name).map(&:to_s), + generics: type_parameter_names(decl), source: :rbs ) pins.push module_pin @@ -299,7 +324,7 @@ def module_alias_decl_to_pin decl old_name = decl.old_name.relative!.to_s # TODO old_type = ComplexType.parse(old_name).force_rooted # TODO - pins.push create_constant(new_name, old_type, decl.comment&.string, decl, 'Module') + pins.push create_constant(new_name, old_name, decl.comment&.string, decl, 'Module') end # @param decl [RBS::AST::Declarations::Constant] @@ -727,16 +752,8 @@ def alias_to_pin decl, closure ) end - RBS_TO_YARD_TYPE = { - 'bool' => 'Boolean', - 'string' => 'String', - 'int' => 'Integer', - 'untyped' => '', - 'NilClass' => 'nil' - } - # @param type [RBS::MethodType, RBS::Types::Block] - # @return [ComplexType] + # @return [ComplexType, ComplexType::UniqueType] def method_type_to_type type if type_aliases.key?(type.type.return_type.to_s) # TODO other_type_to_type(type_aliases[type.type.return_type.to_s].type) # TODO From 46f8063e3af79c2a169882265432a077c3c6a72b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 20 Jan 2026 08:18:53 -0500 Subject: [PATCH 904/930] Refactor --- lib/solargraph/rbs_map/conversions.rb | 35 +++++++++++++-------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index f72f44386..a708c4191 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -100,6 +100,21 @@ def convert_self_types_to_pins decl, module_pin }.freeze private_constant :RBS_TO_YARD_TYPE + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [ComplexType::UniqueType] + def build_type type_name, type_args = [] + rooted_rbs_name = type_name.relative!.to_s + base = RBS_TO_YARD_TYPE.fetch(rooted_rbs_name, rooted_rbs_name) + params = type_args.map { |a| other_type_to_type(a) } + # tuples have their own class and are handled in other_type_to_type + 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.reject(&:undefined?), rooted: true, parameters_type: :list) + end + end + # @param decl [RBS::AST::Declarations::Module::Self] # @param closure [Pin::Namespace] # @return [void] @@ -191,13 +206,10 @@ def type_parameter_names decl # @param decl [RBS::AST::Declarations::Class] # @return [void] def class_decl_to_pin decl - # @type [Hash{String => ComplexType}] + # @type [Hash{String => ComplexType, ComplexType::UniqueType}] generic_defaults = {} decl.type_params.each do |param| - if param.default_type - type = other_type_to_type param.default_type - generic_defaults[param.name.to_s] = type - end + generic_defaults[param.name.to_s] = other_type_to_type param.default_type if param.default_type end class_name = decl.name.relative!.to_s # TODO @@ -762,19 +774,6 @@ def method_type_to_type type end end - # @param type_name [RBS::TypeName] - # @param type_args [Enumerable] - # @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_type(a) } - 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.reject(&:undefined?), rooted: true, parameters_type: :list) - end - end - # @param type_name [RBS::TypeName] # @param type_args [Enumerable] # @return [String] From 5d8ffc84429e8fe705245f5b6069cb04de5078c6 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 20 Jan 2026 08:32:36 -0500 Subject: [PATCH 905/930] Refactor --- lib/solargraph/rbs_map/conversions.rb | 33 +++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index a708c4191..f005f699f 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -247,9 +247,9 @@ def class_decl_to_pin decl end # @param decl [RBS::AST::Declarations::Interface] - # @param closure [Pin::Closure] # @return [void] - def interface_decl_to_pin decl, closure + # @param [Object] _closure + def interface_decl_to_pin decl, _closure class_pin = Solargraph::Pin::Namespace.new( type: :module, type_location: location_decl_to_pin_location(decl.location), @@ -289,13 +289,16 @@ def module_decl_to_pin decl end # @param name [String] - # @param tag [String] + # @param type [ComplexType, ComplexType::UniqueType] # @param comments [String, nil] - # @param decl [RBS::AST::Declarations::ClassAlias, RBS::AST::Declarations::Constant, RBS::AST::Declarations::ModuleAlias] - # @param base [String, nil] Optional conversion of tag to base + # @param decl [RBS::AST::Declarations::ClassAlias, + # RBS::AST::Declarations::Constant, + # RBS::AST::Declarations::ModuleAlias] + # @param base [String, nil] Optional conversion of tag to + # base - valid values are Class and Module # # @return [Solargraph::Pin::Constant] - def create_constant(name, tag, comments, decl, base = nil) + def create_constant name, type, comments, decl, base = nil parts = name.split('::') if parts.length > 1 name = parts.last @@ -312,8 +315,10 @@ 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 + rooted_tag = type.rooted_tags # TODO + if base + rooted_tag = "#{base}<#{rooted_tag}>" # TODO + end constant_pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) constant_pin end @@ -322,10 +327,10 @@ def create_constant(name, tag, comments, decl, base = nil) # @return [void] def class_alias_decl_to_pin decl # See https://www.rubydoc.info/gems/rbs/3.4.3/RBS/AST/Declarations/ClassAlias - new_name = decl.new_name.relative!.to_s - old_name = decl.old_name.relative!.to_s - - pins.push create_constant(new_name, old_name, decl.comment&.string, decl, 'Class') + new_name = decl.new_name.relative!.to_s # TODO + old_name = decl.old_name.relative!.to_s # TODO + old_type = ComplexType.parse(old_name).force_rooted # TODO + pins.push create_constant(new_name, old_type, decl.comment&.string, decl, 'Class') end # @param decl [RBS::AST::Declarations::ModuleAlias] @@ -336,14 +341,14 @@ def module_alias_decl_to_pin decl old_name = decl.old_name.relative!.to_s # TODO old_type = ComplexType.parse(old_name).force_rooted # TODO - pins.push create_constant(new_name, old_name, decl.comment&.string, decl, 'Module') + pins.push create_constant(new_name, old_type, decl.comment&.string, decl, 'Module') end # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl type = other_type_to_type(decl.type) - pins.push create_constant(decl.name.relative!.to_s, type.rooted_tag, decl.comment&.string, decl) + pins.push create_constant(decl.name.relative!.to_s, type, decl.comment&.string, decl) # TODO end # @param decl [RBS::AST::Declarations::Global] From f20d95dfcaf112e2c6e241911db6cb638cc72f6a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 20 Jan 2026 09:03:38 -0500 Subject: [PATCH 906/930] Refactor --- lib/solargraph/rbs_map/conversions.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index f005f699f..399b797c0 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -330,7 +330,7 @@ def class_alias_decl_to_pin decl new_name = decl.new_name.relative!.to_s # TODO old_name = decl.old_name.relative!.to_s # TODO old_type = ComplexType.parse(old_name).force_rooted # TODO - pins.push create_constant(new_name, old_type, decl.comment&.string, decl, 'Class') + pins.push create_constant(new_name, old_type, decl.comment&.string, decl, '::Class') end # @param decl [RBS::AST::Declarations::ModuleAlias] @@ -341,7 +341,7 @@ def module_alias_decl_to_pin decl old_name = decl.old_name.relative!.to_s # TODO old_type = ComplexType.parse(old_name).force_rooted # TODO - pins.push create_constant(new_name, old_type, decl.comment&.string, decl, 'Module') + pins.push create_constant(new_name, old_type, decl.comment&.string, decl, '::Module') end # @param decl [RBS::AST::Declarations::Constant] From 8f1bf0f4f14c505425c401cded8217c12b153ed4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 20 Jan 2026 18:38:07 -0500 Subject: [PATCH 907/930] Refactor --- lib/solargraph/rbs_map/conversions.rb | 130 +++++++++++++------------- 1 file changed, 63 insertions(+), 67 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 399b797c0..8a8939f8b 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -779,79 +779,75 @@ def method_type_to_type type end end - # @param type_name [RBS::TypeName] - # @param type_args [Enumerable] - # @return [String] - def type_tag(type_name, type_args = []) - build_type(type_name, type_args).tags - end - # @param type [RBS::Types::Bases::Base,Object] RBS type object. # Note: Generally these extend from RBS::Types::Bases::Base, # but not all. # # @return [ComplexType, ComplexType::UniqueType] def other_type_to_type type - tag = if type.is_a?(RBS::Types::Optional) - "#{other_type_to_type(type.type).rooted_tag}, nil" - elsif type.is_a?(RBS::Types::Bases::Any) - 'undefined' - elsif type.is_a?(RBS::Types::Bases::Bool) - 'Boolean' - elsif type.is_a?(RBS::Types::Tuple) - "Array(#{type.types.map { |t| other_type_to_type(t).rooted_tag }.join(', ')})" - elsif type.is_a?(RBS::Types::Literal) - type.literal.inspect - elsif type.is_a?(RBS::Types::Union) - type.types.map { |t| other_type_to_type(t).rooted_tag }.join(', ') - elsif type.is_a?(RBS::Types::Record) - # @todo Better record support - 'Hash' - elsif type.is_a?(RBS::Types::Bases::Nil) - 'nil' - elsif type.is_a?(RBS::Types::Bases::Self) - 'self' - elsif type.is_a?(RBS::Types::Bases::Void) - 'void' - elsif type.is_a?(RBS::Types::Variable) - "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" - elsif type.is_a?(RBS::Types::ClassInstance) #&& !type.args.empty? - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Bases::Instance) - 'self' - elsif type.is_a?(RBS::Types::Bases::Top) - # top is the most super superclass - 'BasicObject' - elsif type.is_a?(RBS::Types::Bases::Bottom) - # bottom is used in contexts where nothing will ever return - # - e.g., it could be the return type of 'exit()' or 'raise' - # - # @todo define a specific bottom type and use it to - # determine dead code - 'undefined' - elsif type.is_a?(RBS::Types::Intersection) - type.types.map { |member| other_type_to_type(member).rooted_tag }.join(', ') - elsif type.is_a?(RBS::Types::Proc) - 'Proc' - elsif type.is_a?(RBS::Types::Alias) - # type-level alias use - e.g., 'bool' in "type bool = true | false" - # @todo ensure these get resolved after processing all aliases - # @todo handle recursive aliases - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Interface) - # represents a mix-in module which can be considered a - # subtype of a consumer of it - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::ClassSingleton) - # e.g., singleton(String) - type_tag(type.name) - else - # RBS doesn't provide a common base class for its type AST nodes - # - # @sg-ignore all types should include location - Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" - 'undefined' - end + tag = case type + when RBS::Types::Optional + return ComplexType.new([other_type_to_type(type.type), + ComplexType::UniqueType::NIL]) + when RBS::Types::Bases::Any + return ComplexType::UNDEFINED + when RBS::Types::Bases::Bool + return ComplexType::BOOLEAN + when RBS::Types::Tuple + tuple_types = type.types.map { |t| other_type_to_type(t) } + return ComplexType::UniqueType.new('Tuple', [], tuple_types, rooted: true, parameters_type: :list) + when RBS::Types::Literal + type.literal.inspect + when RBS::Types::Union + return ComplexType.new(type.types.map { |t| other_type_to_type(t) }) + when RBS::Types::Record + # @todo Better record support + 'Hash' + when RBS::Types::Bases::Nil + return ComplexType::NIL + when RBS::Types::Bases::Self + return ComplexType::SELF + when RBS::Types::Bases::Void + return ComplexType::VOID + when RBS::Types::Variable + "generic<#{type.name}>" # TODO + when RBS::Types::ClassInstance # && !type.args.empty? + return build_type(type.name, type.args) + when RBS::Types::Bases::Instance + return ComplexType::SELF + when RBS::Types::Bases::Top + # top is the most super superclass + 'BasicObject' + when RBS::Types::Bases::Bottom + # bottom is used in contexts where nothing will ever return + # - e.g., it could be the return type of 'exit()' or 'raise' + # + # @todo define a specific bottom type and use it to + # determine dead code + return ComplexType::UNDEFINED + when RBS::Types::Intersection + return ComplexType.new(type.types.map { |member| other_type_to_type(member) }) + when RBS::Types::Proc + 'Proc' + when RBS::Types::Alias + # type-level alias use - e.g., 'bool' in "type bool = true | false" + # @todo ensure these get resolved after processing all aliases + # @todo handle recursive aliases + return build_type(type.name, type.args) + when RBS::Types::Interface + # represents a mix-in module which can be considered a + # subtype of a consumer of it + return build_type(type.name, type.args) + when RBS::Types::ClassSingleton + # e.g., singleton(String) + return build_type(type.name) + else + # RBS doesn't provide a common base class for its type AST nodes + # + # @sg-ignore all types should include location + Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" + return ComplexType::UNDEFINED + end ComplexType.try_parse(tag).force_rooted end From 55f9fc40cb420b59a1191ad089a55ac578c60e39 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 20 Jan 2026 18:57:03 -0500 Subject: [PATCH 908/930] Refactor --- lib/solargraph/rbs_map/conversions.rb | 129 +++++++++++++------------- 1 file changed, 64 insertions(+), 65 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 8a8939f8b..9efa62261 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -254,7 +254,7 @@ def interface_decl_to_pin decl, _closure type: :module, type_location: location_decl_to_pin_location(decl.location), name: decl.name.relative!.to_s, # TODO - closure: Solargraph::Pin::ROOT_PIN, # TODO should this use a closure??? + closure: Solargraph::Pin::ROOT_PIN, # TODO: should this use a closure??? comments: decl.comment&.string, generics: type_parameter_names(decl), # HACK: Using :hidden to keep interfaces from appearing in @@ -785,70 +785,69 @@ def method_type_to_type type # # @return [ComplexType, ComplexType::UniqueType] def other_type_to_type type - tag = case type - when RBS::Types::Optional - return ComplexType.new([other_type_to_type(type.type), - ComplexType::UniqueType::NIL]) - when RBS::Types::Bases::Any - return ComplexType::UNDEFINED - when RBS::Types::Bases::Bool - return ComplexType::BOOLEAN - when RBS::Types::Tuple - tuple_types = type.types.map { |t| other_type_to_type(t) } - return ComplexType::UniqueType.new('Tuple', [], tuple_types, rooted: true, parameters_type: :list) - when RBS::Types::Literal - type.literal.inspect - when RBS::Types::Union - return ComplexType.new(type.types.map { |t| other_type_to_type(t) }) - when RBS::Types::Record - # @todo Better record support - 'Hash' - when RBS::Types::Bases::Nil - return ComplexType::NIL - when RBS::Types::Bases::Self - return ComplexType::SELF - when RBS::Types::Bases::Void - return ComplexType::VOID - when RBS::Types::Variable - "generic<#{type.name}>" # TODO - when RBS::Types::ClassInstance # && !type.args.empty? - return build_type(type.name, type.args) - when RBS::Types::Bases::Instance - return ComplexType::SELF - when RBS::Types::Bases::Top - # top is the most super superclass - 'BasicObject' - when RBS::Types::Bases::Bottom - # bottom is used in contexts where nothing will ever return - # - e.g., it could be the return type of 'exit()' or 'raise' - # - # @todo define a specific bottom type and use it to - # determine dead code - return ComplexType::UNDEFINED - when RBS::Types::Intersection - return ComplexType.new(type.types.map { |member| other_type_to_type(member) }) - when RBS::Types::Proc - 'Proc' - when RBS::Types::Alias - # type-level alias use - e.g., 'bool' in "type bool = true | false" - # @todo ensure these get resolved after processing all aliases - # @todo handle recursive aliases - return build_type(type.name, type.args) - when RBS::Types::Interface - # represents a mix-in module which can be considered a - # subtype of a consumer of it - return build_type(type.name, type.args) - when RBS::Types::ClassSingleton - # e.g., singleton(String) - return build_type(type.name) - else - # RBS doesn't provide a common base class for its type AST nodes - # - # @sg-ignore all types should include location - Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" - return ComplexType::UNDEFINED - end - ComplexType.try_parse(tag).force_rooted + case type + when RBS::Types::Optional + ComplexType.new([other_type_to_type(type.type), + ComplexType::UniqueType::NIL]) + when RBS::Types::Bases::Any + ComplexType::UNDEFINED + when RBS::Types::Bases::Bool + ComplexType::BOOLEAN + when RBS::Types::Tuple + tuple_types = type.types.map { |t| other_type_to_type(t) } + ComplexType::UniqueType.new('Tuple', [], tuple_types, rooted: true, parameters_type: :list) + when RBS::Types::Literal + ComplexType.try_parse(type.literal.inspect).force_rooted + when RBS::Types::Union + ComplexType.new(type.types.map { |t| other_type_to_type(t) }) + when RBS::Types::Record + # @todo Better record support + ComplexType::UniqueType.new('Hash', rooted: true) + when RBS::Types::Bases::Nil + ComplexType::NIL + when RBS::Types::Bases::Self + ComplexType::SELF + when RBS::Types::Bases::Void + ComplexType::VOID + when RBS::Types::Variable + ComplexType.parse("generic<#{type.name}>").force_rooted # TODO + when RBS::Types::ClassInstance # && !type.args.empty? + build_type(type.name, type.args) + when RBS::Types::Bases::Instance + ComplexType::SELF + when RBS::Types::Bases::Top + # top is the most super superclass + ComplexType::UniqueType.new('BasicObject', rooted: true) + when RBS::Types::Bases::Bottom + # bottom is used in contexts where nothing will ever return + # - e.g., it could be the return type of 'exit()' or 'raise' + # + # @todo define a specific bottom type and use it to + # determine dead code + ComplexType::UNDEFINED + when RBS::Types::Intersection + ComplexType.new(type.types.map { |member| other_type_to_type(member) }) + when RBS::Types::Proc + ComplexType::UniqueType.new('Proc', rooted: true) + when RBS::Types::Alias + # type-level alias use - e.g., 'bool' in "type bool = true | false" + # @todo ensure these get resolved after processing all aliases + # @todo handle recursive aliases + build_type(type.name, type.args) + when RBS::Types::Interface + # represents a mix-in module which can be considered a + # subtype of a consumer of it + build_type(type.name, type.args) + when RBS::Types::ClassSingleton + # e.g., singleton(String) + build_type(type.name) + else + # RBS doesn't provide a common base class for its type AST nodes + # + # @sg-ignore all types should include location + Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" + ComplexType::UNDEFINED + end end # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Module] From 468ba978162b77d88eb03f5bab699fb7be7751af Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 20 Jan 2026 18:59:25 -0500 Subject: [PATCH 909/930] Add @sg-ignores --- lib/solargraph/rbs_map/conversions.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 9efa62261..4f2ded323 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -787,6 +787,7 @@ def method_type_to_type type def other_type_to_type type case type when RBS::Types::Optional + # @sg-ignore flow based typing needs to understand case when class pattern ComplexType.new([other_type_to_type(type.type), ComplexType::UniqueType::NIL]) when RBS::Types::Bases::Any @@ -794,11 +795,14 @@ def other_type_to_type type when RBS::Types::Bases::Bool ComplexType::BOOLEAN when RBS::Types::Tuple + # @sg-ignore flow based typing needs to understand case when class pattern tuple_types = type.types.map { |t| other_type_to_type(t) } ComplexType::UniqueType.new('Tuple', [], tuple_types, rooted: true, parameters_type: :list) when RBS::Types::Literal + # @sg-ignore flow based typing needs to understand case when class pattern ComplexType.try_parse(type.literal.inspect).force_rooted when RBS::Types::Union + # @sg-ignore flow based typing needs to understand case when class pattern ComplexType.new(type.types.map { |t| other_type_to_type(t) }) when RBS::Types::Record # @todo Better record support @@ -810,8 +814,10 @@ def other_type_to_type type when RBS::Types::Bases::Void ComplexType::VOID when RBS::Types::Variable + # @sg-ignore flow based typing needs to understand case when class pattern ComplexType.parse("generic<#{type.name}>").force_rooted # TODO when RBS::Types::ClassInstance # && !type.args.empty? + # @sg-ignore flow based typing needs to understand case when class pattern build_type(type.name, type.args) when RBS::Types::Bases::Instance ComplexType::SELF @@ -826,6 +832,7 @@ def other_type_to_type type # determine dead code ComplexType::UNDEFINED when RBS::Types::Intersection + # @sg-ignore flow based typing needs to understand case when class pattern ComplexType.new(type.types.map { |member| other_type_to_type(member) }) when RBS::Types::Proc ComplexType::UniqueType.new('Proc', rooted: true) @@ -833,13 +840,16 @@ def other_type_to_type type # type-level alias use - e.g., 'bool' in "type bool = true | false" # @todo ensure these get resolved after processing all aliases # @todo handle recursive aliases + # @sg-ignore flow based typing needs to understand case when class pattern build_type(type.name, type.args) when RBS::Types::Interface # represents a mix-in module which can be considered a # subtype of a consumer of it + # @sg-ignore flow based typing needs to understand case when class pattern build_type(type.name, type.args) when RBS::Types::ClassSingleton # e.g., singleton(String) + # @sg-ignore flow based typing needs to understand case when class pattern build_type(type.name) else # RBS doesn't provide a common base class for its type AST nodes From fc4c5b7c223e123a64f089ad8f47294294a8d35c Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 21 Jan 2026 08:05:39 -0500 Subject: [PATCH 910/930] Fix tuple issue --- lib/solargraph/rbs_map/conversions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 4f2ded323..ecf93b7eb 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -797,7 +797,7 @@ def other_type_to_type type when RBS::Types::Tuple # @sg-ignore flow based typing needs to understand case when class pattern tuple_types = type.types.map { |t| other_type_to_type(t) } - ComplexType::UniqueType.new('Tuple', [], tuple_types, rooted: true, parameters_type: :list) + ComplexType::UniqueType.new('Tuple', [], tuple_types, rooted: false, parameters_type: :fixed) when RBS::Types::Literal # @sg-ignore flow based typing needs to understand case when class pattern ComplexType.try_parse(type.literal.inspect).force_rooted From 6ec089cc53c6c16b4938a713deb551510c5a4a60 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 21 Jan 2026 08:12:20 -0500 Subject: [PATCH 911/930] Refactor --- lib/solargraph/rbs_map/conversions.rb | 43 +++++++++++++-------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index ecf93b7eb..605372bdf 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -120,7 +120,7 @@ def build_type type_name, type_args = [] # @return [void] def convert_self_type_to_pins decl, closure type = build_type(decl.name, decl.args) - generic_values = type.all_params.map(&:to_s) + generic_values = type.all_params.map(&:rooted_tags) include_pin = Solargraph::Pin::Reference::Include.new( name: type.rooted_name, type_location: location_decl_to_pin_location(decl.location), @@ -232,7 +232,7 @@ def class_decl_to_pin decl pins.push class_pin if decl.super_class type = build_type(decl.super_class.name, decl.super_class.args) - generic_values = type.all_params.map(&:to_s) + generic_values = type.all_params.map(&:to_s) # TODO superclass_name = decl.super_class.name.to_s # TODO pins.push Solargraph::Pin::Reference::Superclass.new( type_location: location_decl_to_pin_location(decl.super_class.location), @@ -337,7 +337,7 @@ def class_alias_decl_to_pin decl # @return [void] def module_alias_decl_to_pin decl # See https://www.rubydoc.info/gems/rbs/3.4.3/RBS/AST/Declarations/ModuleAlias - new_name = decl.new_name.relative!.to_s + new_name = decl.new_name.relative!.to_s # TODO old_name = decl.old_name.relative!.to_s # TODO old_type = ComplexType.parse(old_name).force_rooted # TODO @@ -465,24 +465,23 @@ def method_def_to_pin decl, closure, context pin.instance_variable_set(:@return_type, ComplexType::VOID) end end - if decl.singleton? - final_scope = :class - name = decl.name.to_s - visibility = calculate_method_visibility(decl, context, closure, final_scope, name) - pin = Solargraph::Pin::Method.new( - name: name, - closure: closure, - comments: decl.comment&.string, - type_location: location_decl_to_pin_location(decl.location), - visibility: visibility, - scope: final_scope, - signatures: [], - generics: generics, - source: :rbs - ) - pin.signatures.concat method_def_to_sigs(decl, pin) - pins.push pin - end + return unless decl.singleton? + final_scope = :class + name = decl.name.to_s # TODO + visibility = calculate_method_visibility(decl, context, closure, final_scope, name) + pin = Solargraph::Pin::Method.new( + name: name, + closure: closure, + comments: decl.comment&.string, + type_location: location_decl_to_pin_location(decl.location), + visibility: visibility, + scope: final_scope, + signatures: [], + generics: generics, + source: :rbs + ) + pin.signatures.concat method_def_to_sigs(decl, pin) + pins.push pin end # @param decl [RBS::AST::Members::MethodDefinition] @@ -797,7 +796,7 @@ def other_type_to_type type when RBS::Types::Tuple # @sg-ignore flow based typing needs to understand case when class pattern tuple_types = type.types.map { |t| other_type_to_type(t) } - ComplexType::UniqueType.new('Tuple', [], tuple_types, rooted: false, parameters_type: :fixed) + ComplexType::UniqueType.new('Tuple', [], tuple_types, rooted: true, parameters_type: :fixed) when RBS::Types::Literal # @sg-ignore flow based typing needs to understand case when class pattern ComplexType.try_parse(type.literal.inspect).force_rooted From 19329ef035c7be019b86ed6dc831c4ecda590113 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 21 Jan 2026 19:50:42 -0500 Subject: [PATCH 912/930] Tuple -> Array() --- lib/solargraph/rbs_map/conversions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 605372bdf..315bad26b 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -796,7 +796,7 @@ def other_type_to_type type when RBS::Types::Tuple # @sg-ignore flow based typing needs to understand case when class pattern tuple_types = type.types.map { |t| other_type_to_type(t) } - ComplexType::UniqueType.new('Tuple', [], tuple_types, rooted: true, parameters_type: :fixed) + ComplexType::UniqueType.new('Array', [], tuple_types, rooted: true, parameters_type: :fixed) when RBS::Types::Literal # @sg-ignore flow based typing needs to understand case when class pattern ComplexType.try_parse(type.literal.inspect).force_rooted From c8079e609316922add73fadafb9dde1847005905 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 21 Jan 2026 21:29:59 -0500 Subject: [PATCH 913/930] Add @sg-ignore --- lib/solargraph/rbs_map/conversions.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 315bad26b..6069ef1f3 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -539,6 +539,7 @@ def parts_of_function type, pin # @sg-ignore RBS generic type understanding issue name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" # TODO parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue return_type: other_type_to_type(param.type), source: :rbs, type_location: type_location) end From 7e2970635e4825a86493e752abf689a7aac23e5d Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Wed, 21 Jan 2026 21:43:06 -0500 Subject: [PATCH 914/930] Use rooted names, clarify intent --- lib/solargraph/rbs_map/conversions.rb | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 6069ef1f3..c12066fdb 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -100,11 +100,18 @@ def convert_self_types_to_pins decl, module_pin }.freeze private_constant :RBS_TO_YARD_TYPE + # @param [RBS::TypeName] + # + # @return [String] + def raw_rooted_name(type_name) + type_name.relative!.to_s + end + # @param type_name [RBS::TypeName] # @param type_args [Enumerable] # @return [ComplexType::UniqueType] def build_type type_name, type_args = [] - rooted_rbs_name = type_name.relative!.to_s + rooted_rbs_name = raw_rooted_name(type_name) base = RBS_TO_YARD_TYPE.fetch(rooted_rbs_name, rooted_rbs_name) params = type_args.map { |a| other_type_to_type(a) } # tuples have their own class and are handled in other_type_to_type @@ -212,7 +219,7 @@ def class_decl_to_pin decl generic_defaults[param.name.to_s] = other_type_to_type param.default_type if param.default_type end - class_name = decl.name.relative!.to_s # TODO + class_name = raw_rooted_name(decl.name) generics = type_parameter_names(decl) @@ -232,7 +239,7 @@ def class_decl_to_pin decl pins.push class_pin if decl.super_class type = build_type(decl.super_class.name, decl.super_class.args) - generic_values = type.all_params.map(&:to_s) # TODO + generic_values = type.all_params.map(&:rooted_tags) superclass_name = decl.super_class.name.to_s # TODO pins.push Solargraph::Pin::Reference::Superclass.new( type_location: location_decl_to_pin_location(decl.super_class.location), @@ -253,7 +260,7 @@ def interface_decl_to_pin decl, _closure class_pin = Solargraph::Pin::Namespace.new( type: :module, type_location: location_decl_to_pin_location(decl.location), - name: decl.name.relative!.to_s, # TODO + name: raw_rooted_name(decl.name), closure: Solargraph::Pin::ROOT_PIN, # TODO: should this use a closure??? comments: decl.comment&.string, generics: type_parameter_names(decl), @@ -272,7 +279,7 @@ def interface_decl_to_pin decl, _closure def module_decl_to_pin decl module_pin = Solargraph::Pin::Namespace.new( type: :module, - name: decl.name.relative!.to_s, # TODO + name: raw_rooted_name(decl.name), type_location: location_decl_to_pin_location(decl.location), closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, @@ -327,9 +334,8 @@ def create_constant name, type, comments, decl, base = nil # @return [void] def class_alias_decl_to_pin decl # See https://www.rubydoc.info/gems/rbs/3.4.3/RBS/AST/Declarations/ClassAlias - new_name = decl.new_name.relative!.to_s # TODO - old_name = decl.old_name.relative!.to_s # TODO - old_type = ComplexType.parse(old_name).force_rooted # TODO + new_name = raw_rooted_name(decl.new_name) + old_type = build_type(decl.old_name) pins.push create_constant(new_name, old_type, decl.comment&.string, decl, '::Class') end @@ -720,7 +726,7 @@ def civar_to_pin decl, closure # @return [void] def include_to_pin decl, closure type = build_type(decl.name, decl.args) - generic_values = type.all_params.map(&:to_s) + generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Include.new( name: decl.name.relative!.to_s, # TODO type_location: location_decl_to_pin_location(decl.location), @@ -869,7 +875,7 @@ def add_mixins decl, namespace # @todo are we handling prepend correctly? klass = mixin.is_a?(RBS::AST::Members::Include) ? Pin::Reference::Include : Pin::Reference::Extend type = build_type(mixin.name, mixin.args) - generic_values = type.all_params.map(&:to_s) + generic_values = type.all_params.map(&:rooted_tags) pins.push klass.new( name: mixin.name.relative!.to_s, # TODO type_location: location_decl_to_pin_location(mixin.location), From 769bd5043cfa2599afa467e7177a8386b0a476ec Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Fri, 23 Jan 2026 07:07:56 -0500 Subject: [PATCH 915/930] Refactor --- lib/solargraph/pin/reference.rb | 16 +++ lib/solargraph/rbs_map/conversions.rb | 136 +++++++++++++++----------- spec/rbs_map/core_map_spec.rb | 12 +-- 3 files changed, 98 insertions(+), 66 deletions(-) diff --git a/lib/solargraph/pin/reference.rb b/lib/solargraph/pin/reference.rb index 13e603d6e..4ad64fcb8 100644 --- a/lib/solargraph/pin/reference.rb +++ b/lib/solargraph/pin/reference.rb @@ -12,6 +12,22 @@ class Reference < Base attr_reader :generic_values + # A Reference is a pin that associates a type with another type. + # The existing type is marked as the closure. The name of the + # type we're associating with it is the 'name' field, and + # subtypes are in the 'generic_values' field. + # + # These pins are a little different - the name is a rooted name, + # which may be relative or absolute, preceded with ::, not a + # fully qualified namespace, which is implicitly in the root + # namespace and is never preceded by ::. + # + # @todo can the above be represented in a less subtle way? + # @todo consider refactoring so that we can replicate more + # complex types like Hash{String => Integer} and has both key + # types and subtypes. + # + # @param name [String] rooted name of the referenced type # @param generic_values [Array] def initialize generic_values: [], **splat super(**splat) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index c12066fdb..90eb8722e 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -91,34 +91,53 @@ def convert_self_types_to_pins decl, module_pin decl.self_types.each { |self_type| convert_self_type_to_pins(self_type, module_pin) } end - RBS_TO_YARD_TYPE = { + # @type [Hash{String => String}] + RBS_TO_CLASS = { 'bool' => 'Boolean', 'string' => 'String', 'int' => 'Integer', - 'untyped' => '', - 'NilClass' => 'nil' }.freeze - private_constant :RBS_TO_YARD_TYPE + private_constant :RBS_TO_CLASS + # rooted names (namespaces) use the prefix of :: when they are + # relative to the root namespace, or not if they are relative to + # the current namespace. + # + # @param type_name [RBS::TypeName] + # + # @return [String] + def rooted_name(type_name) + name = type_name.to_s + RBS_TO_CLASS.fetch(name, name) + end + + # fqns names are implicitly fully qualified - they are relative + # to the root namespace and are not prefixed with :: + # # @param [RBS::TypeName] # # @return [String] - def raw_rooted_name(type_name) - type_name.relative!.to_s + def fqns(type_name) + raise "Received unexpected unqualified type name: #{type_name}" unless type_name.absolute? + ns = type_name.relative!.to_s + RBS_TO_CLASS.fetch(ns, ns) end # @param type_name [RBS::TypeName] # @param type_args [Enumerable] # @return [ComplexType::UniqueType] def build_type type_name, type_args = [] - rooted_rbs_name = raw_rooted_name(type_name) - base = RBS_TO_YARD_TYPE.fetch(rooted_rbs_name, rooted_rbs_name) + # we use .absolute? below to tell the type object what to + # expect + rbs_name = type_name.relative!.to_s + base = RBS_TO_CLASS.fetch(rbs_name, rbs_name) + params = type_args.map { |a| other_type_to_type(a) } # tuples have their own class and are handled in other_type_to_type if base == 'Hash' && params.length == 2 - ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: type_name.absolute?, parameters_type: :hash) else - ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: type_name.absolute?, parameters_type: :list) end end @@ -129,7 +148,7 @@ def convert_self_type_to_pins decl, closure type = build_type(decl.name, decl.args) generic_values = type.all_params.map(&:rooted_tags) include_pin = Solargraph::Pin::Reference::Include.new( - name: type.rooted_name, + name: type.name, type_location: location_decl_to_pin_location(decl.location), generic_values: generic_values, closure: closure, @@ -203,7 +222,7 @@ def convert_member_to_pin member, closure, context # thing, these are all in the global namespace. # # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Interface, - # RBS::AST::Declarations::Module] + # RBS::AST::Declarations::Module, RBS::MethodType] # # @return [Array] def type_parameter_names decl @@ -219,7 +238,7 @@ def class_decl_to_pin decl generic_defaults[param.name.to_s] = other_type_to_type param.default_type if param.default_type end - class_name = raw_rooted_name(decl.name) + class_name = fqns(decl.name) generics = type_parameter_names(decl) @@ -240,12 +259,11 @@ 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(&:rooted_tags) - superclass_name = decl.super_class.name.to_s # TODO 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: superclass_name, + name: type.rooted_name, # reference pins use rooted names source: :rbs ) end @@ -260,7 +278,7 @@ def interface_decl_to_pin decl, _closure class_pin = Solargraph::Pin::Namespace.new( type: :module, type_location: location_decl_to_pin_location(decl.location), - name: raw_rooted_name(decl.name), + name: fqns(decl.name), closure: Solargraph::Pin::ROOT_PIN, # TODO: should this use a closure??? comments: decl.comment&.string, generics: type_parameter_names(decl), @@ -279,7 +297,7 @@ def interface_decl_to_pin decl, _closure def module_decl_to_pin decl module_pin = Solargraph::Pin::Namespace.new( type: :module, - name: raw_rooted_name(decl.name), + name: fqns(decl.name), type_location: location_decl_to_pin_location(decl.location), closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, @@ -295,7 +313,7 @@ def module_decl_to_pin decl add_mixins decl, module_pin.closure end - # @param name [String] + # @param fqns [String] # @param type [ComplexType, ComplexType::UniqueType] # @param comments [String, nil] # @param decl [RBS::AST::Declarations::ClassAlias, @@ -305,26 +323,26 @@ def module_decl_to_pin decl # base - valid values are Class and Module # # @return [Solargraph::Pin::Constant] - def create_constant name, type, comments, decl, base = nil - parts = name.split('::') + def create_constant fqns, type, comments, decl, base = nil + parts = fqns.split('::') if parts.length > 1 - name = parts.last + fqns = parts.last # @sg-ignore Need to add nil check here closure = pins.select { |pin| pin && pin.path == parts[0..-2].join('::') }.first else - name = parts.first + fqns = parts.first closure = Solargraph::Pin::ROOT_PIN end constant_pin = Solargraph::Pin::Constant.new( - name: name, + name: fqns, closure: closure, type_location: location_decl_to_pin_location(decl.location), comments: comments, source: :rbs ) - rooted_tag = type.rooted_tags # TODO + rooted_tag = type.rooted_tags if base - rooted_tag = "#{base}<#{rooted_tag}>" # TODO + rooted_tag = "#{base}<#{rooted_tag}>" end constant_pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) constant_pin @@ -334,7 +352,7 @@ def create_constant name, type, comments, decl, base = nil # @return [void] def class_alias_decl_to_pin decl # See https://www.rubydoc.info/gems/rbs/3.4.3/RBS/AST/Declarations/ClassAlias - new_name = raw_rooted_name(decl.new_name) + new_name = fqns(decl.new_name) old_type = build_type(decl.old_name) pins.push create_constant(new_name, old_type, decl.comment&.string, decl, '::Class') end @@ -343,9 +361,8 @@ def class_alias_decl_to_pin decl # @return [void] def module_alias_decl_to_pin decl # See https://www.rubydoc.info/gems/rbs/3.4.3/RBS/AST/Declarations/ModuleAlias - new_name = decl.new_name.relative!.to_s # TODO - old_name = decl.old_name.relative!.to_s # TODO - old_type = ComplexType.parse(old_name).force_rooted # TODO + new_name = fqns(decl.new_name) + old_type = build_type(decl.old_name) pins.push create_constant(new_name, old_type, decl.comment&.string, decl, '::Module') end @@ -353,15 +370,16 @@ def module_alias_decl_to_pin decl # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - type = other_type_to_type(decl.type) - pins.push create_constant(decl.name.relative!.to_s, type, decl.comment&.string, decl) # TODO + target_type = other_type_to_type(decl.type) + constant_name = fqns(decl.name) + pins.push create_constant(constant_name, target_type, decl.comment&.string, decl) end # @param decl [RBS::AST::Declarations::Global] # @return [void] def global_decl_to_pin decl closure = Solargraph::Pin::ROOT_PIN - name = decl.name.to_s # TODO + name = decl.name.to_s pin = Solargraph::Pin::GlobalVariable.new( name: name, closure: closure, @@ -369,7 +387,7 @@ def global_decl_to_pin decl type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags # TODO + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -425,7 +443,7 @@ def calculate_method_visibility decl, context, closure, scope, name visibility = VISIBILITY_OVERRIDE[override_key] simple_override_key = [closure.path, scope] visibility ||= VISIBILITY_OVERRIDE[simple_override_key] - if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(decl.name) # TODO + if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(decl.name) visibility ||= :private end if decl.kind == :singleton_instance @@ -447,10 +465,12 @@ def method_def_to_pin decl, closure, context # having different type params / orders - we may need to match # this data model and have generics live in signatures to # handle those correctly - generics = decl.overloads.map(&:method_type).flat_map(&:type_params).map(&:name).map(&:to_s).uniq # TODO + generics = decl.overloads.map(&:method_type).map do |method_type| + type_parameter_names method_type + end if decl.instance? - name = decl.name.to_s # TODO + name = decl.name.to_s final_scope = :instance visibility = calculate_method_visibility(decl, context, closure, final_scope, name) pin = Solargraph::Pin::Method.new( @@ -473,7 +493,7 @@ def method_def_to_pin decl, closure, context end return unless decl.singleton? final_scope = :class - name = decl.name.to_s # TODO + name = decl.name.to_s visibility = calculate_method_visibility(decl, context, closure, final_scope, name) pin = Solargraph::Pin::Method.new( name: name, @@ -497,7 +517,7 @@ def method_def_to_sigs decl, pin # @param overload [RBS::AST::Members::MethodDefinition::Overload] decl.overloads.map do |overload| type_location = location_decl_to_pin_location(overload.method_type.location) - generics = overload.method_type.type_params.map(&:name).map(&:to_s) # TODO + generics = type_parameter_names(overload.method_type) signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin) rbs_block = overload.method_type.block block = if rbs_block @@ -523,7 +543,7 @@ def location_decl_to_pin_location location end_pos = Position.new(location.end_line - 1, location.end_column) range = Range.new(start_pos, end_pos) # @sg-ignore flow sensitve typing should handle return nil if location&.name.nil? - Location.new(location.name.to_s, range) # TODO + Location.new(location.name.to_s, range) end # @param type [RBS::MethodType, RBS::Types::Block] @@ -621,7 +641,7 @@ def attr_reader_to_pin decl, closure, context visibility: visibility, source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags # TODO + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) logger.debug do "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" @@ -676,13 +696,13 @@ def attr_accessor_to_pin decl, closure, context # @return [void] def ivar_to_pin decl, closure pin = Solargraph::Pin::InstanceVariable.new( - name: decl.name.to_s, # TODO + name: decl.name.to_s, closure: closure, type_location: location_decl_to_pin_location(decl.location), comments: decl.comment&.string, source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags # TODO + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -691,7 +711,7 @@ def ivar_to_pin decl, closure # @param closure [Pin::Namespace] # @return [void] def cvar_to_pin decl, closure - name = decl.name.to_s # TODO + name = decl.name.to_s pin = Solargraph::Pin::ClassVariable.new( name: name, closure: closure, @@ -699,7 +719,7 @@ def cvar_to_pin decl, closure type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags # TODO + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -708,7 +728,7 @@ def cvar_to_pin decl, closure # @param closure [Pin::Namespace] # @return [void] def civar_to_pin decl, closure - name = decl.name.to_s # TODO + name = decl.name.to_s pin = Solargraph::Pin::InstanceVariable.new( name: name, closure: closure, @@ -716,7 +736,7 @@ def civar_to_pin decl, closure type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags # TODO + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -728,7 +748,7 @@ def include_to_pin decl, closure type = build_type(decl.name, decl.args) generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Include.new( - name: decl.name.relative!.to_s, # TODO + name: type.rooted_name, # reference pins use rooted names type_location: location_decl_to_pin_location(decl.location), generic_values: generic_values, closure: closure, @@ -740,9 +760,12 @@ def include_to_pin decl, closure # @param closure [Pin::Namespace] # @return [void] def prepend_to_pin decl, closure + type = build_type(decl.name, decl.args) + generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Prepend.new( - name: decl.name.relative!.to_s, # TODO + name: type.rooted_name, # reference pins use rooted names type_location: location_decl_to_pin_location(decl.location), + generic_values: generic_values, closure: closure, source: :rbs ) @@ -752,9 +775,12 @@ def prepend_to_pin decl, closure # @param closure [Pin::Namespace] # @return [void] def extend_to_pin decl, closure + type = build_type(decl.name, decl.args) + generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Extend.new( - name: decl.name.relative!.to_s, # TODO + name: type.rooted_name, # reference pins use rooted names type_location: location_decl_to_pin_location(decl.location), + generic_values: generic_values, closure: closure, source: :rbs ) @@ -766,9 +792,9 @@ def extend_to_pin decl, closure def alias_to_pin decl, closure final_scope = decl.singleton? ? :class : :instance pins.push Solargraph::Pin::MethodAlias.new( - name: decl.new_name.to_s, # TODO + name: decl.new_name.to_s, type_location: location_decl_to_pin_location(decl.location), - original: decl.old_name.to_s, # TODO + original: decl.old_name.to_s, closure: closure, scope: final_scope, source: :rbs @@ -778,8 +804,8 @@ def alias_to_pin decl, closure # @param type [RBS::MethodType, RBS::Types::Block] # @return [ComplexType, ComplexType::UniqueType] def method_type_to_type type - if type_aliases.key?(type.type.return_type.to_s) # TODO - other_type_to_type(type_aliases[type.type.return_type.to_s].type) # TODO + if type_aliases.key?(type.type.return_type.to_s) + other_type_to_type(type_aliases[type.type.return_type.to_s].type) else other_type_to_type type.type.return_type end @@ -821,7 +847,7 @@ def other_type_to_type type ComplexType::VOID when RBS::Types::Variable # @sg-ignore flow based typing needs to understand case when class pattern - ComplexType.parse("generic<#{type.name}>").force_rooted # TODO + ComplexType.parse("generic<#{type.name}>").force_rooted when RBS::Types::ClassInstance # && !type.args.empty? # @sg-ignore flow based typing needs to understand case when class pattern build_type(type.name, type.args) @@ -877,7 +903,7 @@ def add_mixins decl, namespace type = build_type(mixin.name, mixin.args) generic_values = type.all_params.map(&:rooted_tags) pins.push klass.new( - name: mixin.name.relative!.to_s, # TODO + name: type.rooted_name, # reference pins use rooted names type_location: location_decl_to_pin_location(mixin.location), generic_values: generic_values, closure: namespace, diff --git a/spec/rbs_map/core_map_spec.rb b/spec/rbs_map/core_map_spec.rb index cada2754c..e2d9b41d5 100644 --- a/spec/rbs_map/core_map_spec.rb +++ b/spec/rbs_map/core_map_spec.rb @@ -80,7 +80,7 @@ # correctly. It would be better to test RbsMap or RbsMap::Conversions # with an RBS fixture. core_map = Solargraph::RbsMap::CoreMap.new - pins = core_map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.name == 'Enumerable' } + pins = core_map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.name == '::Enumerable' } expect(pins.map(&:closure).map(&:namespace)).to include('Enumerator') end @@ -97,16 +97,6 @@ class Foo expect(clip.infer.to_s).to eq('Foo') end - it "generates rooted pins from RBS for core" do - map = Solargraph::RbsMap::CoreMap.new - map.pins.each do |pin| - expect(pin).to be_all_rooted - unless pin.is_a?(Solargraph::Pin::Keyword) - expect(pin.closure).to_not be_nil, ->(){ "Pin #{pin.inspect} (#{pin.path}) has no closure" } - end - end - end - it 'renders string literals from RBS in a useful way' do source = Solargraph::Source.load_string(%( foo = nil From f087eed3f9f307e1b59af83dabd605dc0435d89a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Mon, 26 Jan 2026 07:39:06 -0500 Subject: [PATCH 916/930] Remove TODOs, add asserts --- lib/solargraph/rbs_map/conversions.rb | 57 +++++++++++++++++++++------ 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 90eb8722e..0182a11ea 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -62,22 +62,54 @@ def load_environment_to_pins loader def convert_decl_to_pin decl, closure case decl when RBS::AST::Declarations::Class + # @sg-ignore flow sensitive typing should support case/when + unless closure.name == '' || decl.name.absolute? + Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on class #{decl.inspect}") + end class_decl_to_pin decl when RBS::AST::Declarations::Interface + # @sg-ignore flow sensitive typing should support case/when + unless closure.name == '' || decl.name.absolute? + Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on interface #{decl.inspect}") + end # STDERR.puts "Skipping interface #{decl.name.relative!}" - interface_decl_to_pin decl, closure + interface_decl_to_pin decl when RBS::AST::Declarations::TypeAlias + # @sg-ignore flow sensitive typing should support case/when + unless closure.name == '' || decl.name.absolute? + # @sg-ignore flow sensitive typing should support case/when + Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on alias type name #{decl.name.to_s}") + end # @sg-ignore flow sensitive typing should support case/when type_aliases[decl.name.to_s] = decl when RBS::AST::Declarations::Module + # @sg-ignore flow sensitive typing should support case/when + unless closure.name == '' || decl.name.absolute? + # @sg-ignore flow sensitive typing should support case/when + Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on alias type name #{decl.name.to_s}") + end module_decl_to_pin decl when RBS::AST::Declarations::Constant + # @sg-ignore flow sensitive typing should support case/when + unless closure.name == '' || decl.name.absolute? + Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on constant #{decl.inspect}") + end constant_decl_to_pin decl when RBS::AST::Declarations::ClassAlias + # @sg-ignore flow sensitive typing should support case/when + unless closure.name == '' || decl.new_name.absolute? + Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on class alias #{decl.inspect}") + end class_alias_decl_to_pin decl when RBS::AST::Declarations::ModuleAlias + unless closure.name == '' + Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on module alias #{decl.inspect}") + end module_alias_decl_to_pin decl when RBS::AST::Declarations::Global + unless closure.name == '' + Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on global decl #{decl.inspect}") + end global_decl_to_pin decl else Solargraph.logger.warn "Skipping declaration #{decl.class}" @@ -118,7 +150,9 @@ def rooted_name(type_name) # # @return [String] def fqns(type_name) - raise "Received unexpected unqualified type name: #{type_name}" unless type_name.absolute? + unless type_name.absolute? + Solargraph.assert_or_log(:rbs_fqns, "Received unexpected unqualified type name: #{type_name}") + end ns = type_name.relative!.to_s RBS_TO_CLASS.fetch(ns, ns) end @@ -273,13 +307,12 @@ def class_decl_to_pin decl # @param decl [RBS::AST::Declarations::Interface] # @return [void] - # @param [Object] _closure - def interface_decl_to_pin decl, _closure + def interface_decl_to_pin decl class_pin = Solargraph::Pin::Namespace.new( type: :module, type_location: location_decl_to_pin_location(decl.location), name: fqns(decl.name), - closure: Solargraph::Pin::ROOT_PIN, # TODO: should this use a closure??? + closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, generics: type_parameter_names(decl), # HACK: Using :hidden to keep interfaces from appearing in @@ -563,7 +596,7 @@ def parts_of_function type, pin arg_num = -1 type.type.required_positionals.each do |param| # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" # TODO + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue return_type: other_type_to_type(param.type), @@ -571,7 +604,7 @@ def parts_of_function type, pin end type.type.optional_positionals.each do |param| # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" # TODO + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue return_type: other_type_to_type(param.type), @@ -579,7 +612,7 @@ def parts_of_function type, pin source: :rbs) end if type.type.rest_positionals - name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" # TODO + name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" inner_rest_positional_type = other_type_to_type(type.type.rest_positionals.type) rest_positional_type = ComplexType::UniqueType.new('Array', [], @@ -591,7 +624,7 @@ def parts_of_function type, pin end type.type.trailing_positionals.each do |param| # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" # TODO + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, type_location: type_location) end @@ -613,9 +646,9 @@ def parts_of_function type, pin source: :rbs) end if type.type.rest_keywords - name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" # TODO + name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, - name: type.type.rest_keywords.name.to_s, closure: pin, # TODO + name: type.type.rest_keywords.name.to_s, closure: pin, source: :rbs, type_location: type_location) end @@ -628,7 +661,7 @@ def parts_of_function type, pin # @param context [Context] # @return [void] def attr_reader_to_pin decl, closure, context - name = decl.name.to_s # TODO + name = decl.name.to_s final_scope = decl.kind == :instance ? :instance : :class visibility = calculate_method_visibility(decl, context, closure, final_scope, name) pin = Solargraph::Pin::Method.new( From 6979cb71e3ff3ed8c76cd8c7927f565665f8efd7 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 27 Jan 2026 08:31:04 -0500 Subject: [PATCH 917/930] Fix solargraph-rspec spec failure --- .github/workflows/plugins.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index c7ad72cb4..8c4258875 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -118,10 +118,10 @@ jobs: cd .. # git clone https://github.com/lekemula/solargraph-rspec.git - # pending https://github.com/lekemula/solargraph-rspec/pull/30 + # pending https://github.com/lekemula/solargraph-rspec/pull/31 git clone https://github.com/apiology/solargraph-rspec.git cd solargraph-rspec - git checkout reset_closures + git checkout test_solargraph_prereleases - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From 197c2db0c0f04066af27e2a14705762208c8111b Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 27 Jan 2026 09:28:12 -0500 Subject: [PATCH 918/930] Fix merge --- .github/workflows/plugins.yml | 71 +++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 8c4258875..3de7288eb 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 @@ -23,7 +23,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.4 + ruby-version: 3.4 # keep same as typecheck.yml bundler-cache: true - uses: awalsh128/cache-apt-pkgs-action@latest with: @@ -34,7 +34,7 @@ jobs: echo 'gem "solargraph-rails"' > .Gemfile echo 'gem "solargraph-rspec"' >> .Gemfile bundle install - bundle update rbs + bundle update --pre rbs - name: Configure to use plugins run: | bundle exec solargraph config @@ -43,7 +43,7 @@ jobs: - name: Install gem types run: bundle exec rbs collection update - name: Ensure typechecking still works - run: bundle exec solargraph typecheck --level typed + run: bundle exec solargraph typecheck --level strong - name: Ensure specs still run run: bundle exec rake spec rails: @@ -54,7 +54,9 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.4 + ruby-version: 3.4 # keep same as typecheck.yml + # See https://github.com/castwide/solargraph/actions/runs/19000135777/job/54265647107?pr=1119 + rubygems: latest bundler-cache: false - uses: awalsh128/cache-apt-pkgs-action@latest with: @@ -64,7 +66,7 @@ jobs: run: | echo 'gem "solargraph-rails"' > .Gemfile bundle install - bundle update rbs + bundle update --pre rbs - name: Configure to use plugins run: | bundle exec solargraph config @@ -72,7 +74,7 @@ jobs: - name: Install gem types run: bundle exec rbs collection update - name: Ensure typechecking still works - run: bundle exec solargraph typecheck --level typed + run: bundle exec solargraph typecheck --level strong - name: Ensure specs still run run: bundle exec rake spec rspec: @@ -83,7 +85,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.4 + ruby-version: 3.4 # keep same as typecheck.yml bundler-cache: false - uses: awalsh128/cache-apt-pkgs-action@latest with: @@ -93,7 +95,7 @@ jobs: run: | echo 'gem "solargraph-rspec"' >> .Gemfile bundle install - bundle update rbs + bundle update --pre rbs - name: Configure to use plugins run: | bundle exec solargraph config @@ -101,7 +103,7 @@ jobs: - name: Install gem types run: bundle exec rbs collection update - name: Ensure typechecking still works - run: bundle exec solargraph typecheck --level typed + run: bundle exec solargraph typecheck --level strong - name: Ensure specs still run run: bundle exec rake spec @@ -125,38 +127,41 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.1' + ruby-version: 3.4 rubygems: latest bundler-cache: false - name: Install gems run: | - set -x + set -x - cd ../solargraph-rspec - echo "gem 'solargraph', path: '../solargraph'" >> Gemfile - bundle config path ${{ env.BUNDLE_PATH }} - bundle install --jobs 4 --retry 3 - bundle exec appraisal install - # @todo some kind of appraisal/bundle conflict? - # https://github.com/castwide/solargraph/actions/runs/19038710934/job/54369767122?pr=1116 - # /home/runner/work/solargraph/solargraph-rspec/vendor/bundle/ruby/3.1.0/gems/bundler-2.6.9/lib/bundler/runtime.rb:317:in - # `check_for_activated_spec!': You have already activated date - # 3.5.0, but your Gemfile requires date 3.4.1. Prepending - # `bundle exec` to your command may solve - # this. (Gem::LoadError) - bundle exec appraisal update date - # For some reason on ruby 3.1 it defaults to an old version: 1.3.2 - # https://github.com/lekemula/solargraph-rspec/actions/runs/17814581205/job/50645370316?pr=22 - # We update manually to the latest - bundle exec appraisal update rspec-rails + cd ../solargraph-rspec + echo "gem 'solargraph', path: '../solargraph'" >> Gemfile + bundle config path ${{ env.BUNDLE_PATH }} + bundle install --jobs 4 --retry 3 + bundle exec appraisal install + # @todo some kind of appraisal/bundle conflict? + # https://github.com/castwide/solargraph/actions/runs/19038710934/job/54369767122?pr=1116 + # /home/runner/work/solargraph/solargraph-rspec/vendor/bundle/ruby/3.1.0/gems/bundler-2.6.9/lib/bundler/runtime.rb:317:in + # `check_for_activated_spec!': You have already activated date + # 3.5.0, but your Gemfile requires date 3.4.1. Prepending + # `bundle exec` to your command may solve + # this. (Gem::LoadError) + bundle exec appraisal update date + # For some reason on ruby 3.1 it defaults to an old version: 1.3.2 + # https://github.com/lekemula/solargraph-rspec/actions/runs/17814581205/job/50645370316?pr=22 + # We update manually to the latest + bundle exec appraisal update rspec-rails - name: Configure .solargraph.yml run: | cd ../solargraph-rspec cp .solargraph.yml.example .solargraph.yml - - name: Solargraph generate RSpec gems YARD and RBS pins + - name: Solargraph generate RSpec gems YARD pins run: | cd ../solargraph-rspec - bundle exec appraisal rbs collection update + # solargraph-rspec's specs don't pass a workspace, so it + # doesn't know where to look for the RBS collection - let's + # not load one so that the solargraph gems command below works + rspec_gems=$(bundle exec appraisal ruby -r './lib/solargraph-rspec' -e 'puts Solargraph::Rspec::Gems.gem_names.join(" ")' 2>/dev/null | tail -n1) bundle exec appraisal solargraph gems $rspec_gems - name: Run specs @@ -180,6 +185,8 @@ jobs: # solargraph-rails supports Ruby 3.0+ ruby-version: '3.0' bundler-cache: false + # https://github.com/apiology/solargraph/actions/runs/19400815835/job/55508092473?pr=17 + rubygems: latest bundler: latest env: MATRIX_RAILS_VERSION: "7.0" @@ -191,7 +198,7 @@ jobs: cd ../solargraph-rails echo "gem 'solargraph', path: '${GITHUB_WORKSPACE:?}'" >> Gemfile bundle install - bundle update rbs + bundle update --pre rbs RAILS_DIR="$(pwd)/spec/rails7" export RAILS_DIR cd ${RAILS_DIR} From b6cea8ce7595e26d05daa3be9cd88ddaf83effb4 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Tue, 27 Jan 2026 10:55:05 -0500 Subject: [PATCH 919/930] Adjust rubocop todo --- .rubocop_todo.yml | 1 + lib/solargraph/shell.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 01d15c52f..0f9ab6e48 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -410,6 +410,7 @@ Metrics/MethodLength: - 'lib/solargraph/complex_type.rb' - 'lib/solargraph/convention/struct_definition.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' + - 'lib/solargraph/shell.rb' - 'lib/solargraph/source_map/mapper.rb' # Configuration parameters: CountComments, CountAsOne. diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 96645666e..072f79f85 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -362,7 +362,7 @@ def pin path 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 + def profile(file = nil) begin require 'vernier' rescue LoadError From cbaf4af058908d2fc037323893d0976cb0ca8b7e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Thu, 29 Jan 2026 07:22:28 -0500 Subject: [PATCH 920/930] Debug logging fixes --- spec/logging_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/logging_spec.rb b/spec/logging_spec.rb index eee59e606..7d38c087e 100644 --- a/spec/logging_spec.rb +++ b/spec/logging_spec.rb @@ -9,7 +9,7 @@ msg = file.read file.close file.unlink - Solargraph::Logging.logger.reopen STDERR + Solargraph::Logging.logger.reopen File::NULL expect(msg).to include('WARN') end end From 92a677fde005d2f3c882abba32d1e6a840f8073a Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 31 Jan 2026 11:46:21 -0500 Subject: [PATCH 921/930] Reproduce build problem with RBS pre-release --- .github/workflows/rspec.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index ad0d5c20b..2c9db01a0 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -1,3 +1,5 @@ +# https://github.com/ruby/rbs/pull/2833 + # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support From 3725fa92747b6a9d10169400c4475ffce816e353 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 31 Jan 2026 11:55:40 -0500 Subject: [PATCH 922/930] Stop hard-coding bundler version --- .github/workflows/rspec.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 2c9db01a0..7e0b3e44f 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -1,5 +1,3 @@ -# https://github.com/ruby/rbs/pull/2833 - # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support @@ -72,10 +70,6 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} - # see https://github.com/castwide/solargraph/actions/runs/19391419903/job/55485410493?pr=1119 - # - # match version in Gemfile.lock and use same version below - bundler: 2.5.23 bundler-cache: false - name: Set rbs version run: echo "gem 'rbs', '${{ matrix.rbs-version }}'" >> .Gemfile @@ -87,7 +81,7 @@ jobs: run: echo "gem 'tsort'" >> .Gemfile - name: Install gems run: | - bundle _2.5.23_ install + bundle install bundle update rbs # use latest available for this Ruby version - name: Update types run: bundle exec rbs collection update @@ -105,10 +99,6 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: '3.4' - # see https://github.com/castwide/solargraph/actions/runs/19391419903/job/55485410493?pr=1119 - # - # match version in Gemfile.lock and use same version below - bundler: 2.5.23 bundler-cache: false - name: Install gems run: bundle install From d68c9063ba72f2d7e8991c3c5a8d17d7dbc5698f Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 31 Jan 2026 12:15:58 -0500 Subject: [PATCH 923/930] Use bundler preferred by setup-ruby step --- .github/workflows/rspec.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 7e0b3e44f..174a1a6e3 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -70,7 +70,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} - bundler-cache: false + bundler-cache: true - 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: @@ -79,9 +79,8 @@ jobs: # starting from Ruby 3.6.0 - name: Work around legacy rbs deprecation on ruby > 3.4 run: echo "gem 'tsort'" >> .Gemfile - - name: Install gems + - name: Update gems run: | - bundle install bundle update rbs # use latest available for this Ruby version - name: Update types run: bundle exec rbs collection update @@ -99,9 +98,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: '3.4' - bundler-cache: false - - name: Install gems - run: bundle install + bundler-cache: true - name: Update types run: bundle exec rbs collection update - name: Run tests From 4512d019a543e9857df181ae12206f2cc7f96396 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 31 Jan 2026 12:36:00 -0500 Subject: [PATCH 924/930] Fix merge --- lib/solargraph/position.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 368b7d812..332ffa527 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -62,15 +62,18 @@ def self.to_offset text, position line = -1 last_line_index = 0 + # @sg-ignore flow sensitive typing should be able to handle redefinition while (newline_index = text.index("\n", newline_index + 1)) && line <= position.line line += 1 break if line == position.line + # @sg-ignore oflow sensitive typing should be able to handle redefinition line_length = newline_index - last_line_index last_line_index = newline_index end last_line_index += 1 if position.line > 0 + # @sg-ignore flow sensitive typing should be able to handle redefinition last_line_index + position.character end @@ -100,8 +103,10 @@ def self.from_offset text, offset character = offset newline_index = -1 + # @sg-ignore flow sensitive typing should be able to handle redefinition while (newline_index = text.index("\n", newline_index + 1)) && newline_index < offset line += 1 + # @sg-ignore flow sensitive typing should be able to handle redefinition character = offset - newline_index - 1 end character = 0 if character.nil? and (cursor - offset).between?(0, 1) From ce2c3b626cc169c1e42d0ed2379e51835eda2639 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 31 Jan 2026 13:45:08 -0500 Subject: [PATCH 925/930] rubocop -a --- .rubocop_todo.yml | 654 +----------------- Gemfile | 2 +- Rakefile | 46 +- bin/solargraph | 2 +- lib/solargraph.rb | 10 +- lib/solargraph/api_map.rb | 68 +- lib/solargraph/api_map/index.rb | 10 +- lib/solargraph/api_map/source_to_yard.rb | 15 +- lib/solargraph/api_map/store.rb | 40 +- lib/solargraph/bench.rb | 1 - lib/solargraph/complex_type.rb | 61 +- lib/solargraph/complex_type/type_methods.rb | 18 +- lib/solargraph/complex_type/unique_type.rb | 77 ++- lib/solargraph/convention.rb | 4 +- lib/solargraph/convention/data_definition.rb | 2 +- .../data_definition/data_assignment_node.rb | 2 +- .../data_definition/data_definition_node.rb | 6 +- .../convention/struct_definition.rb | 6 +- .../struct_assignment_node.rb | 2 +- .../struct_definition_node.rb | 6 +- lib/solargraph/diagnostics/rubocop.rb | 18 +- lib/solargraph/diagnostics/rubocop_helpers.rb | 4 +- lib/solargraph/diagnostics/type_check.rb | 20 +- lib/solargraph/diagnostics/update_errors.rb | 2 +- lib/solargraph/doc_map.rb | 14 +- lib/solargraph/equality.rb | 6 +- lib/solargraph/gem_pins.rb | 8 +- lib/solargraph/language_server/error_codes.rb | 20 +- lib/solargraph/language_server/host.rb | 33 +- .../language_server/host/diagnoser.rb | 2 +- .../language_server/host/dispatch.rb | 3 +- .../language_server/host/message_worker.rb | 4 +- .../language_server/host/sources.rb | 2 +- .../message/client/register_capability.rb | 4 +- .../message/completion_item/resolve.rb | 10 +- .../message/extended/check_gem_version.rb | 21 +- .../message/extended/document_gems.rb | 14 +- .../message/extended/download_core.rb | 3 +- .../message/extended/search.rb | 2 +- .../language_server/message/initialize.rb | 34 +- .../message/text_document/completion.rb | 16 +- .../message/text_document/definition.rb | 3 +- .../text_document/document_highlight.rb | 3 +- .../message/text_document/formatting.rb | 12 +- .../message/text_document/hover.rb | 8 +- .../message/text_document/prepare_rename.rb | 3 +- .../message/text_document/references.rb | 3 +- .../message/text_document/rename.rb | 9 +- .../message/text_document/signature_help.rb | 4 +- .../workspace/did_change_watched_files.rb | 7 +- .../workspace/did_change_workspace_folders.rb | 2 +- .../language_server/transport/data_reader.rb | 24 +- lib/solargraph/language_server/uri_helpers.rb | 4 +- lib/solargraph/library.rb | 43 +- lib/solargraph/location.rb | 8 +- lib/solargraph/logging.rb | 2 +- lib/solargraph/parser/comment_ripper.rb | 14 +- .../parser/flow_sensitive_typing.rb | 72 +- lib/solargraph/parser/node_processor.rb | 2 +- lib/solargraph/parser/node_processor/base.rb | 8 +- .../parser/parser_gem/class_methods.rb | 8 +- .../parser/parser_gem/flawed_builder.rb | 2 +- .../parser/parser_gem/node_chainer.rb | 33 +- .../parser/parser_gem/node_methods.rb | 105 +-- .../parser_gem/node_processors/args_node.rb | 24 +- .../parser_gem/node_processors/block_node.rb | 2 +- .../parser_gem/node_processors/def_node.rb | 6 +- .../parser_gem/node_processors/defs_node.rb | 18 +- .../parser_gem/node_processors/if_node.rb | 6 +- .../parser_gem/node_processors/ivasgn_node.rb | 3 +- .../parser_gem/node_processors/opasgn_node.rb | 2 +- .../parser_gem/node_processors/sclass_node.rb | 6 +- .../parser_gem/node_processors/send_node.rb | 220 +++--- .../parser_gem/node_processors/until_node.rb | 2 +- .../parser_gem/node_processors/when_node.rb | 2 +- .../parser_gem/node_processors/while_node.rb | 2 +- lib/solargraph/pin/base.rb | 104 +-- lib/solargraph/pin/base_variable.rb | 53 +- lib/solargraph/pin/block.rb | 5 +- lib/solargraph/pin/callable.rb | 41 +- lib/solargraph/pin/closure.rb | 7 +- lib/solargraph/pin/common.rb | 7 +- lib/solargraph/pin/compound_statement.rb | 1 + lib/solargraph/pin/constant.rb | 6 +- lib/solargraph/pin/conversions.rb | 10 +- lib/solargraph/pin/delegated_method.rb | 6 +- lib/solargraph/pin/documenting.rb | 5 +- lib/solargraph/pin/local_variable.rb | 4 +- lib/solargraph/pin/method.rb | 111 +-- lib/solargraph/pin/namespace.rb | 23 +- lib/solargraph/pin/parameter.rb | 42 +- lib/solargraph/pin/proxy_type.rb | 2 + lib/solargraph/pin/reference.rb | 1 + lib/solargraph/pin/search.rb | 2 +- lib/solargraph/pin/signature.rb | 15 +- lib/solargraph/pin/symbol.rb | 1 + lib/solargraph/pin/until.rb | 1 + lib/solargraph/pin/while.rb | 1 + lib/solargraph/pin_cache.rb | 20 +- lib/solargraph/position.rb | 3 +- lib/solargraph/range.rb | 9 +- lib/solargraph/rbs_map.rb | 6 +- lib/solargraph/rbs_map/conversions.rb | 26 +- lib/solargraph/rbs_map/core_fills.rb | 23 +- lib/solargraph/rbs_map/stdlib_map.rb | 1 - lib/solargraph/server_methods.rb | 2 +- lib/solargraph/shell.rb | 124 ++-- lib/solargraph/source.rb | 51 +- lib/solargraph/source/chain.rb | 28 +- lib/solargraph/source/chain/call.rb | 74 +- lib/solargraph/source/chain/class_variable.rb | 2 +- .../source/chain/global_variable.rb | 2 +- lib/solargraph/source/chain/if.rb | 3 +- .../source/chain/instance_variable.rb | 4 +- lib/solargraph/source/chain/link.rb | 2 +- lib/solargraph/source/chain/literal.rb | 2 +- lib/solargraph/source/chain/variable.rb | 4 +- lib/solargraph/source/chain/z_super.rb | 2 +- lib/solargraph/source/change.rb | 2 +- lib/solargraph/source/cursor.rb | 34 +- lib/solargraph/source/encoding_fixes.rb | 13 +- lib/solargraph/source/source_chainer.rb | 55 +- lib/solargraph/source_map.rb | 10 +- lib/solargraph/source_map/clip.rb | 66 +- lib/solargraph/source_map/mapper.rb | 90 +-- lib/solargraph/type_checker.rb | 175 +++-- lib/solargraph/type_checker/rules.rb | 12 +- lib/solargraph/workspace.rb | 47 +- lib/solargraph/workspace/config.rb | 19 +- lib/solargraph/workspace/gemspecs.rb | 2 +- lib/solargraph/yard_map/helpers.rb | 6 +- lib/solargraph/yard_map/mapper.rb | 12 +- lib/solargraph/yard_map/mapper/to_method.rb | 12 +- .../yard_map/mapper/to_namespace.rb | 2 +- lib/solargraph/yard_tags.rb | 4 +- solargraph.gemspec | 67 +- spec/api_map/cache_spec.rb | 2 +- spec/api_map/config_spec.rb | 44 +- spec/api_map/source_to_yard_spec.rb | 26 +- spec/api_map_spec.rb | 2 +- spec/complex_type_spec.rb | 39 +- spec/convention/activesupport_concern_spec.rb | 6 +- spec/convention/struct_definition_spec.rb | 18 +- spec/diagnostics/base_spec.rb | 2 +- spec/diagnostics/require_not_found_spec.rb | 4 +- spec/diagnostics/rubocop_helpers_spec.rb | 10 +- spec/diagnostics/rubocop_spec.rb | 14 +- spec/diagnostics/type_check_spec.rb | 14 +- spec/diagnostics/update_errors_spec.rb | 12 +- spec/diagnostics_spec.rb | 2 +- spec/doc_map_spec.rb | 22 +- spec/language_server/host/diagnoser_spec.rb | 2 +- spec/language_server/host/dispatch_spec.rb | 8 +- .../host/message_worker_spec.rb | 4 +- spec/language_server/host_spec.rb | 136 ++-- .../message/completion_item/resolve_spec.rb | 12 +- .../extended/check_gem_version_spec.rb | 32 +- .../message/initialize_spec.rb | 116 ++-- .../message/text_document/definition_spec.rb | 45 +- .../message/text_document/formatting_spec.rb | 2 +- .../message/text_document/hover_spec.rb | 40 +- .../message/text_document/rename_spec.rb | 112 +-- .../text_document/type_definition_spec.rb | 20 +- .../did_change_watched_files_spec.rb | 84 +-- spec/language_server/message_spec.rb | 4 +- spec/language_server/protocol_spec.rb | 100 ++- .../language_server/transport/adapter_spec.rb | 12 +- .../transport/data_reader_spec.rb | 8 +- spec/library_spec.rb | 85 ++- spec/logging_spec.rb | 4 +- spec/parser/flow_sensitive_typing_spec.rb | 1 - spec/parser/node_chainer_spec.rb | 22 +- spec/parser/node_methods_spec.rb | 74 +- spec/parser/node_processor_spec.rb | 4 +- spec/parser_spec.rb | 2 +- spec/pin/base_spec.rb | 16 +- spec/pin/base_variable_spec.rb | 2 +- spec/pin/block_spec.rb | 2 +- spec/pin/constant_spec.rb | 8 +- spec/pin/documenting_spec.rb | 4 +- spec/pin/instance_variable_spec.rb | 6 +- spec/pin/keyword_spec.rb | 2 +- spec/pin/local_variable_spec.rb | 33 +- spec/pin/method_spec.rb | 12 +- spec/pin/namespace_spec.rb | 8 +- spec/pin/symbol_spec.rb | 27 +- spec/pin_cache_spec.rb | 3 +- spec/position_spec.rb | 10 +- spec/rbs_map/conversions_spec.rb | 2 +- spec/rbs_map/core_map_spec.rb | 14 +- spec/rbs_map/stdlib_map_spec.rb | 2 +- spec/shell_spec.rb | 6 +- spec/source/chain/array_spec.rb | 2 +- spec/source/chain/call_spec.rb | 16 +- spec/source/chain/class_variable_spec.rb | 4 +- spec/source/chain/constant_spec.rb | 2 +- spec/source/chain/global_variable_spec.rb | 2 +- spec/source/chain/head_spec.rb | 2 +- spec/source/chain/instance_variable_spec.rb | 11 +- spec/source/chain/link_spec.rb | 10 +- spec/source/chain/literal_spec.rb | 2 +- spec/source/chain/z_super_spec.rb | 2 +- spec/source/chain_spec.rb | 49 +- spec/source/change_spec.rb | 18 +- spec/source/cursor_spec.rb | 48 +- spec/source/source_chainer_spec.rb | 134 ++-- spec/source/updater_spec.rb | 6 +- spec/source_map/clip_spec.rb | 8 +- spec/source_map/mapper_spec.rb | 340 ++++----- spec/source_map_spec.rb | 18 +- spec/source_spec.rb | 68 +- spec/spec_helper.rb | 10 +- spec/type_checker/levels/alpha_spec.rb | 3 +- spec/type_checker/levels/normal_spec.rb | 2 +- spec/type_checker/levels/strict_spec.rb | 17 +- spec/type_checker/levels/strong_spec.rb | 5 +- spec/type_checker/levels/typed_spec.rb | 2 +- spec/type_checker_spec.rb | 6 +- spec/workspace/config_spec.rb | 17 +- spec/workspace_spec.rb | 53 +- spec/yard_map/mapper/to_method_spec.rb | 40 +- 221 files changed, 2570 insertions(+), 3038 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0f9ab6e48..1ad3d1116 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,24 +6,12 @@ # 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). -Gemspec/AddRuntimeDependency: - Exclude: - - 'solargraph.gemspec' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: Severity. Gemspec/DeprecatedAttributeAssignment: Exclude: - - 'solargraph.gemspec' - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation. -Gemspec/OrderedDependencies: - Exclude: - - 'solargraph.gemspec' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: Severity. Gemspec/RequireMFA: @@ -31,234 +19,6 @@ Gemspec/RequireMFA: - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' - 'spec/fixtures/rubocop-custom-version/specifications/rubocop-0.0.0.gemspec' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: with_first_argument, with_fixed_indentation -Layout/ArgumentAlignment: - Exclude: - - 'lib/solargraph/pin/callable.rb' - - 'spec/source/source_chainer_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleAlignWith. -# SupportedStylesAlignWith: either, start_of_block, start_of_line -Layout/BlockAlignment: - Exclude: - - 'spec/source_map/mapper_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -Layout/ClosingHeredocIndentation: - Exclude: - - 'spec/diagnostics/rubocop_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowForAlignment. -Layout/CommentIndentation: - Exclude: - - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/source_map/mapper.rb' - -# This cop supports safe autocorrection (--autocorrect). -Layout/ElseAlignment: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines. -Layout/EmptyLineBetweenDefs: - Exclude: - - 'lib/solargraph/language_server/message/initialize.rb' - - 'lib/solargraph/pin/delegated_method.rb' - -# This cop supports safe autocorrection (--autocorrect). -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 -Layout/EmptyLinesAroundModuleBody: - Exclude: - - 'lib/solargraph/api_map/source_to_yard.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleAlignWith, Severity. -# SupportedStylesAlignWith: keyword, variable, start_of_line -Layout/EndAlignment: - Enabled: false - -# 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/rbs_map/conversions.rb' - - 'lib/solargraph/type_checker.rb' - - 'spec/spec_helper.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: consistent, consistent_relative_to_receiver, special_for_inner_method_call, special_for_inner_method_call_in_parentheses -Layout/FirstArgumentIndentation: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_processors/args_node.rb' - - 'spec/source/source_chainer_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: special_inside_parentheses, consistent, align_brackets -Layout/FirstArrayElementIndentation: - Exclude: - - 'lib/solargraph/source.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: special_inside_parentheses, consistent, align_braces -Layout/FirstHashElementIndentation: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. -# SupportedHashRocketStyles: key, separator, table -# SupportedColonStyles: key, separator, table -# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit -Layout/HashAlignment: - Exclude: - - 'lib/solargraph/workspace/config.rb' - -# This cop supports safe autocorrection (--autocorrect). -Layout/HeredocIndentation: - Exclude: - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Width, AllowedPatterns. -Layout/IndentationWidth: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowDoxygenCommentStyle, AllowGemfileRubyComment, AllowRBSInlineAnnotation, AllowSteepAnnotation. -Layout/LeadingCommentSpace: - Exclude: - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source_map/clip.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, no_space -Layout/LineContinuationSpacing: - Exclude: - - 'lib/solargraph/diagnostics/rubocop_helpers.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: symmetrical, new_line, same_line -Layout/MultilineMethodCallBraceLayout: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'spec/source/source_chainer_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: aligned, indented, indented_relative_to_receiver -Layout/MultilineMethodCallIndentation: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: aligned, indented -Layout/MultilineOperationIndentation: - Exclude: - - 'lib/solargraph/language_server/host/dispatch.rb' - - 'lib/solargraph/source.rb' - -# This cop supports safe autocorrection (--autocorrect). -Layout/SpaceAfterComma: - Exclude: - - 'spec/source/cursor_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, no_space -Layout/SpaceAroundEqualsInParameterDefault: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator, EnforcedStyleForRationalLiterals. -# SupportedStylesForExponentOperator: space, no_space -# SupportedStylesForRationalLiterals: space, no_space -Layout/SpaceAroundOperators: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. -# SupportedStyles: space, no_space -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceBeforeBlockBraces: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -Layout/SpaceBeforeComma: - Exclude: - - 'spec/source/cursor_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. -# SupportedStyles: space, no_space -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceInsideBlockBraces: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. -# SupportedStyles: space, no_space, compact -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceInsideHashLiteralBraces: - Exclude: - - 'lib/solargraph/language_server/message/extended/search.rb' - - 'lib/solargraph/language_server/message/initialize.rb' - - 'lib/solargraph/workspace/config.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, compact, no_space -Layout/SpaceInsideParens: - Exclude: - - 'lib/solargraph/pin/namespace.rb' - - 'lib/solargraph/source_map.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowInHeredoc. -Layout/TrailingWhitespace: - Exclude: - - 'lib/solargraph/language_server/message/client/register_capability.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedMethods, AllowedPatterns. -Lint/AmbiguousBlockAssociation: - Exclude: - - 'lib/solargraph/language_server/host.rb' - -# This cop supports safe autocorrection (--autocorrect). -Lint/AmbiguousOperator: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -Lint/AmbiguousOperatorPrecedence: - Exclude: - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/source.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: RequireParenthesesForMethodChains. Lint/AmbiguousRange: @@ -278,7 +38,6 @@ Lint/BinaryOperatorWithIdenticalOperands: Lint/BooleanSymbol: Exclude: - 'lib/solargraph/convention/struct_definition/struct_definition_node.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - 'lib/solargraph/source/chain/literal.rb' # Configuration parameters: AllowedMethods. @@ -307,18 +66,6 @@ Lint/NonAtomicFileOperation: Exclude: - 'spec/diagnostics/rubocop_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -Lint/ParenthesesAsGroupedExpression: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/source_map/clip_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -Lint/RedundantRequireStatement: - Exclude: - - 'spec/language_server/protocol_spec.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowedMethods, InferNonNilReceiver, AdditionalNilMethods. # AllowedMethods: instance_of?, kind_of?, is_a?, eql?, respond_to?, equal? @@ -328,35 +75,11 @@ Lint/RedundantSafeNavigation: - 'lib/solargraph/api_map/source_to_yard.rb' - 'lib/solargraph/rbs_map.rb' -# This cop supports safe autocorrection (--autocorrect). -Lint/RedundantStringCoercion: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/pin/conversions.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/namespace.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: strict, consistent -Lint/SymbolConversion: - Exclude: - - 'lib/solargraph/pin/base.rb' - # Configuration parameters: AllowKeywordBlockArguments. Lint/UnderscorePrefixedVariableName: Exclude: - 'lib/solargraph/library.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. -Lint/UnusedBlockArgument: - Exclude: - - 'lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb' - - 'lib/solargraph/logging.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions. # NotImplementedExceptions: NotImplementedError @@ -365,7 +88,11 @@ Lint/UnusedMethodArgument: # This cop supports safe autocorrection (--autocorrect). Lint/UselessAssignment: - Enabled: false + Exclude: + - 'lib/solargraph/pin/block.rb' + - 'spec/fixtures/long_squiggly_heredoc.rb' + - 'spec/fixtures/rubocop-unused-variable-error/app.rb' + - 'spec/fixtures/unicode.rb' # This cop supports unsafe autocorrection (--autocorrect-all). Lint/UselessMethodDefinition: @@ -384,7 +111,7 @@ Metrics/AbcSize: # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. # AllowedMethods: refine Metrics/BlockLength: - Max: 57 + Max: 61 # Configuration parameters: CountBlocks, CountModifierForms. Metrics/BlockNesting: @@ -406,16 +133,11 @@ Metrics/CyclomaticComplexity: # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: - Exclude: - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/convention/struct_definition.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source_map/mapper.rb' + Enabled: false # Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: - Max: 169 + Max: 167 # Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters. Metrics/ParameterLists: @@ -484,14 +206,6 @@ RSpec/BeEq: - 'spec/complex_type_spec.rb' - 'spec/pin/method_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: be, be_nil -RSpec/BeNil: - Exclude: - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/language_server/host_spec.rb' - RSpec/BeforeAfterAll: Exclude: - '**/spec/spec_helper.rb' @@ -517,24 +231,6 @@ RSpec/DescribeClass: RSpec/DescribedClass: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -RSpec/EmptyLineAfterFinalLet: - Exclude: - - 'spec/workspace/config_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: CustomTransform, IgnoredWords, DisallowedExamples. -# DisallowedExamples: works -RSpec/ExampleWording: - Exclude: - - 'spec/pin/base_spec.rb' - - 'spec/pin/method_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -RSpec/ExcessiveDocstringSpacing: - Exclude: - - 'spec/source/chain/call_spec.rb' - # This cop supports safe autocorrection (--autocorrect). RSpec/ExpectActual: Exclude: @@ -542,12 +238,6 @@ RSpec/ExpectActual: - 'spec/rbs_map/stdlib_map_spec.rb' - 'spec/source_map/mapper_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, each, example -RSpec/HookArgument: - Enabled: false - # Configuration parameters: AssignmentOnly. RSpec/InstanceVariable: Enabled: false @@ -556,11 +246,6 @@ RSpec/LeakyConstantDeclaration: Exclude: - 'spec/complex_type_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -RSpec/LetBeforeExamples: - Exclude: - - 'spec/complex_type_spec.rb' - RSpec/MultipleExpectations: Max: 14 @@ -568,14 +253,6 @@ RSpec/MultipleExpectations: RSpec/NestedGroups: Max: 4 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: not_to, to_not -RSpec/NotToNot: - Exclude: - - 'spec/api_map_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Strict, EnforcedStyle, AllowedExplicitMatchers. # SupportedStyles: inflected, explicit @@ -593,11 +270,6 @@ RSpec/RemoveConst: Exclude: - 'spec/diagnostics/rubocop_helpers_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -RSpec/ScatteredLet: - Exclude: - - 'spec/complex_type_spec.rb' - Security/MarshalLoad: Exclude: - 'lib/solargraph/pin_cache.rb' @@ -608,16 +280,6 @@ Security/MarshalLoad: Style/AccessModifierDeclarations: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: separated, grouped -Style/AccessorGrouping: - Exclude: - - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/rbs_map.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: always, conditionals @@ -633,19 +295,14 @@ Style/ArgumentsForwarding: Exclude: - 'lib/solargraph/complex_type.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. -# SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces -# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object -# FunctionalMethods: let, let!, subject, watch -# AllowedMethods: lambda, proc, it -Style/BlockDelimiters: - Enabled: false - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: MinBranchesCount. Style/CaseLikeIf: - Enabled: false + Exclude: + - 'lib/solargraph/language_server/message/workspace/did_change_watched_files.rb' + - 'lib/solargraph/pin/parameter.rb' + - 'lib/solargraph/source/source_chainer.rb' + - 'lib/solargraph/yard_map/mapper.rb' # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle, EnforcedStyleForClasses, EnforcedStyleForModules. @@ -669,11 +326,6 @@ Style/CollectionCompact: Exclude: - 'lib/solargraph/pin/constant.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/ColonMethodCall: - Exclude: - - 'spec/type_checker_spec.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/CombinableLoops: Exclude: @@ -690,49 +342,20 @@ Style/ConcatArrayLiterals: 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: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -Style/EmptyLambdaParameter: - Exclude: - - 'spec/rbs_map/core_map_spec.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: compact, expanded Style/EmptyMethod: Exclude: - - 'lib/solargraph/language_server/message/client/register_capability.rb' - 'spec/fixtures/formattable.rb' - 'spec/fixtures/rdoc-lib/lib/example.rb' - 'spec/fixtures/workspace-with-gemfile/lib/thing.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: trailing_conditional, ternary -Style/EmptyStringInsideInterpolation: - Exclude: - - 'lib/solargraph/library.rb' - - 'lib/solargraph/pin/documenting.rb' - - 'lib/solargraph/shell.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/ExpandPathArguments: - Exclude: - - 'solargraph.gemspec' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedVars, DefaultToNil. -Style/FetchEnvVar: - Exclude: - - 'spec/api_map/config_spec.rb' - - 'spec/spec_helper.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: left_coerce, right_coerce, single_coerce, fdiv @@ -751,14 +374,13 @@ Style/FrozenStringLiteralComment: Style/GlobalStdStream: Exclude: - 'lib/solargraph/logging.rb' - - 'lib/solargraph/pin/base.rb' - 'lib/solargraph/shell.rb' - - 'spec/logging_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: - Enabled: false + Exclude: + - 'lib/solargraph/source_map/clip.rb' # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowSplatArgument. @@ -774,37 +396,21 @@ Style/HashEachMethods: - 'lib/solargraph/library.rb' - 'lib/solargraph/source.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, EnforcedShorthandSyntax, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. -# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys -# SupportedShorthandSyntax: always, never, either, consistent, either_consistent -Style/HashSyntax: - Exclude: - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/IdenticalConditionalBranches: Exclude: - 'lib/solargraph/library.rb' - 'lib/solargraph/type_checker.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowIfModifier. -Style/IfInsideElse: - Enabled: false - # This cop supports safe autocorrection (--autocorrect). Style/IfUnlessModifier: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: call, braces -Style/LambdaCall: +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: InverseMethods, InverseBlocks. +Style/InverseMethods: Exclude: - - 'lib/solargraph/library.rb' + - 'lib/solargraph/source.rb' # This cop supports unsafe autocorrection (--autocorrect-all). Style/MapIntoArray: @@ -827,69 +433,19 @@ Style/MapToSet: # Configuration parameters: EnforcedStyle. # SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline Style/MethodDefParentheses: - Enabled: false + Exclude: + - 'spec/fixtures/rdoc-lib/lib/example.rb' Style/MultilineBlockChain: Exclude: - 'lib/solargraph/pin/search.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/MultilineIfModifier: - Exclude: - - 'lib/solargraph/pin/callable.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/MultilineTernaryOperator: - Exclude: - - 'lib/solargraph/language_server/host.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowMethodComparison, ComparisonsThreshold. -Style/MultipleComparison: - Enabled: false - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: literals, strict Style/MutableConstant: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: both, prefix, postfix -Style/NegatedIf: - Exclude: - - 'lib/solargraph/language_server/host/diagnoser.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/NegatedIfElseCondition: - Exclude: - - 'lib/solargraph/diagnostics/rubocop.rb' - - 'lib/solargraph/language_server/message/extended/document_gems.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/type_checker.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/NestedTernaryOperator: - Exclude: - - 'lib/solargraph/pin/conversions.rb' - - 'lib/solargraph/pin/method.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, MinBodyLength, AllowConsecutiveConditionals. -# SupportedStyles: skip_modifier_ifs, always -Style/Next: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin/signature.rb' - - 'lib/solargraph/source_map/clip.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Strict, AllowedNumbers, AllowedPatterns. -Style/NumericLiterals: - MinDigits: 6 - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. # SupportedStyles: predicate, comparison @@ -905,13 +461,6 @@ Style/OpenStructUse: Style/OptionalBooleanParameter: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowSafeAssignment, AllowInMultilineConditions. -Style/ParenthesesAroundCondition: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/type_checker.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: short, verbose @@ -925,82 +474,12 @@ Style/RedundantArgument: Exclude: - 'lib/solargraph/source_map/mapper.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantAssignment: - Exclude: - - 'lib/solargraph/language_server/host/dispatch.rb' - - 'lib/solargraph/workspace/config.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantBegin: - Exclude: - - 'lib/solargraph/language_server/transport/data_reader.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/encoding_fixes.rb' - - 'lib/solargraph/workspace.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantException: - Exclude: - - 'spec/language_server/host_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantFreeze: - Exclude: - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/source_map/mapper.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/source_map/mapper.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantParentheses: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantRegexpArgument: - Exclude: - - 'lib/solargraph/api_map/index.rb' - - 'lib/solargraph/workspace/config.rb' - - 'spec/diagnostics/rubocop_helpers_spec.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/language_server/host_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantRegexpEscape: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowMultipleReturnValues. -Style/RedundantReturn: - Exclude: - - 'lib/solargraph/complex_type/type_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/source/chain/z_super.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantSelf: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, AllowInnerSlashes. -# SupportedStyles: slashes, percent_r, mixed -Style/RegexpLiteral: - Exclude: - - 'lib/solargraph/language_server/uri_helpers.rb' - - 'lib/solargraph/workspace/config.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, explicit -Style/RescueStandardError: - Exclude: - - 'lib/solargraph/pin/base.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # AllowedMethods: present?, blank?, presence, try, try! @@ -1016,22 +495,6 @@ Style/SafeNavigationChainLength: Style/SlicingWithRange: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowModifier. -Style/SoleNestedConditional: - Exclude: - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/type_checker.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/StderrPuts: - Exclude: - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/shell.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Mode. Style/StringConcatenation: @@ -1041,7 +504,8 @@ Style/StringConcatenation: # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes Style/StringLiterals: - Enabled: false + Exclude: + - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' # This cop supports safe autocorrection (--autocorrect). Style/SuperArguments: @@ -1050,71 +514,12 @@ Style/SuperArguments: - 'lib/solargraph/pin/method.rb' - 'lib/solargraph/pin/signature.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, MinSize. -# SupportedStyles: percent, brackets -Style/SymbolArray: - Enabled: false - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments. # AllowedMethods: define_method Style/SymbolProc: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, AllowSafeAssignment. -# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex -Style/TernaryParentheses: - Exclude: - - 'lib/solargraph/source_map/mapper.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInArguments: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma -Style/TrailingCommaInArrayLiteral: - Exclude: - - 'lib/solargraph/language_server/message/text_document/formatting.rb' - - 'lib/solargraph/rbs_map/core_fills.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma -Style/TrailingCommaInHashLiteral: - Exclude: - - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/closure.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, AllowedMethods. -# AllowedMethods: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym -Style/TrivialAccessors: - Exclude: - - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/WhileUntilModifier: - Exclude: - - 'lib/solargraph/complex_type.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, MinSize, WordRegex. -# SupportedStyles: percent, brackets -Style/WordArray: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -Style/YAMLFileRead: - Exclude: - - 'lib/solargraph/workspace/config.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/ZeroLengthPredicate: Exclude: @@ -1123,18 +528,13 @@ Style/ZeroLengthPredicate: - 'lib/solargraph/source/chain/array.rb' - 'spec/language_server/protocol_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: long, short -YARD/CollectionType: - Exclude: - - 'lib/solargraph/range.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStylePrototypeName. # SupportedStylesPrototypeName: before, after YARD/MismatchName: - Enabled: false + Exclude: + - 'lib/solargraph/pin/reference.rb' + - 'lib/solargraph/rbs_map/conversions.rb' YARD/TagTypeSyntax: Enabled: false @@ -1143,4 +543,4 @@ YARD/TagTypeSyntax: # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. # URISchemes: http, https Layout/LineLength: - Max: 244 + Max: 224 diff --git a/Gemfile b/Gemfile index 8de7ad88e..6849d21b5 100755 --- a/Gemfile +++ b/Gemfile @@ -3,5 +3,5 @@ source 'https://rubygems.org' gemspec name: 'solargraph' # Local gemfile for development tools, etc. -local_gemfile = File.expand_path(".Gemfile", __dir__) +local_gemfile = File.expand_path('.Gemfile', __dir__) instance_eval File.read local_gemfile if File.exist? local_gemfile diff --git a/Rakefile b/Rakefile index 146aeac45..584322291 100755 --- a/Rakefile +++ b/Rakefile @@ -3,40 +3,40 @@ require 'bundler/gem_tasks' require 'fileutils' require 'open3' -desc "Open a Pry session preloaded with this library" +desc 'Open a Pry session preloaded with this library' task :console do - sh "pry -I lib -r solargraph.rb" + sh 'pry -I lib -r solargraph.rb' end -desc "Run the type checker" +desc 'Run the type checker' task typecheck: [:typecheck_strong] -desc "Run the type checker at typed level - return code issues provable without annotations being correct" +desc 'Run the type checker at typed level - return code issues provable without annotations being correct' task :typecheck_typed do - sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level typed" + sh 'SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level typed' end -desc "Run the type checker at strict level - report issues using type annotations" +desc 'Run the type checker at strict level - report issues using type annotations' task :typecheck_strict do - sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strict" + sh 'SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strict' end -desc "Run the type checker at strong level - enforce that type annotations exist" +desc 'Run the type checker at strong level - enforce that type annotations exist' task :typecheck_strong do - sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strong" + sh 'SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strong' end -desc "Run the type checker at alpha level - run high-false-alarm checks" +desc 'Run the type checker at alpha level - run high-false-alarm checks' task :typecheck_alpha do - sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level alpha" + sh 'SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level alpha' end -desc "Run RSpec tests, starting with the ones that failed last time" +desc 'Run RSpec tests, starting with the ones that failed last time' task spec: %i[spec_failed undercover_no_fail full_spec] do undercover end -desc "Run all RSpec tests" +desc 'Run all RSpec tests' task :full_spec do warn 'starting spec' sh 'TEST_COVERAGE_COMMAND_NAME=full-new bundle exec rspec' # --profile' @@ -66,17 +66,17 @@ rescue StandardError => e # @sg-ignore Need to add nil check here warn "Backtrace:\n#{e.backtrace.join("\n")}" warn "output: #{output}" - puts "Flushing" + puts 'Flushing' $stdout.flush raise end -desc "Check PR coverage" +desc 'Check PR coverage' task :undercover do - raise "Undercover failed" unless undercover.success? + raise 'Undercover failed' unless undercover.success? end -desc "Branch-focused fast-feedback quality/spec/coverage checks" +desc 'Branch-focused fast-feedback quality/spec/coverage checks' task test: %i[overcommit spec typecheck] do # do these in order Rake::Task['typecheck_strict'].invoke @@ -84,18 +84,18 @@ task test: %i[overcommit spec typecheck] do Rake::Task['typecheck_alpha'].invoke end -desc "Re-run failed specs. Add --fail-fast in your .rspec-local file if desired." +desc 'Re-run failed specs. Add --fail-fast in your .rspec-local file if desired.' task :spec_failed do # allow user to check out any persistent failures while looking for # more in the whole test suite sh 'TEST_COVERAGE_COMMAND_NAME=next-failure bundle exec rspec --only-failures || true' end -desc "Run undercover and show output without failing the task if it fails" +desc 'Run undercover and show output without failing the task if it fails' task :undercover_no_fail do undercover rescue StandardError - puts "Undercover failed, but continuing with other tasks." + puts 'Undercover failed, but continuing with other tasks.' end # @return [void] @@ -104,7 +104,7 @@ def simplecov_collate require 'simplecov-lcov' require 'undercover/simplecov_formatter' - SimpleCov.collate(Dir["coverage/{next-failure,full,ad-hoc}/.resultset.json"]) do + SimpleCov.collate(Dir['coverage/{next-failure,full,ad-hoc}/.resultset.json']) do cname = 'combined' command_name cname new_dir = File.join('coverage', cname) @@ -119,7 +119,7 @@ def simplecov_collate ]) SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true end - puts "Simplecov collated results into coverage/combined/.resultset.json" + puts 'Simplecov collated results into coverage/combined/.resultset.json' rescue StandardError => e puts "Simplecov collate failed: #{e.message}" ensure @@ -131,7 +131,7 @@ task :simplecov_collate do simplecov_collate end -desc "Show quality checks on this development branch so far, including any staged files" +desc 'Show quality checks on this development branch so far, including any staged files' task :overcommit do # OVERCOMMIT_DEBUG=1 will show more detail sh 'SOLARGRAPH_ASSERTS=on bundle exec overcommit --run --diff origin/master' diff --git a/bin/solargraph b/bin/solargraph index 248dc42fd..8f47bbda6 100755 --- a/bin/solargraph +++ b/bin/solargraph @@ -1,7 +1,7 @@ #!/usr/bin/env ruby # turn off warning diagnostics from Ruby -$VERBOSE=nil +$VERBOSE = nil require 'solargraph' diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 27a79e4ad..544a4e2e2 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -71,7 +71,7 @@ def self.asserts_on? # @param msg [String, nil] An optional message to log # @param block [Proc] A block that returns a message to log # @return [void] - def self.assert_or_log(type, msg = nil, &block) + def self.assert_or_log type, msg = nil, &block if asserts_on? # @type [String, nil] msg ||= block.call @@ -113,10 +113,10 @@ def self.logger # @return [generic] def self.with_clean_env &block meth = if Bundler.respond_to?(:with_original_env) - :with_original_env - else - :with_clean_env - end + :with_original_env + else + :with_clean_env + end Bundler.send meth, &block end end diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 904edff8e..87b96dba4 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -51,15 +51,15 @@ def self.reset_core out: nil # # @param other [Object] - def eql?(other) + def eql? other self.class == other.class && # @sg-ignore flow sensitive typing needs to handle self.class == other.class equality_fields == other.equality_fields end # @param other [Object] - def ==(other) - self.eql?(other) + def == other + eql?(other) end # @return [Integer] @@ -212,7 +212,7 @@ def cache_all_for_doc_map! out: $stderr, rebuild: false # @param rebuild [Boolean] # @param out [StringIO, IO, nil] # @return [void] - def cache_gem(gemspec, rebuild: false, out: nil) + def cache_gem gemspec, rebuild: false, out: nil doc_map.cache(gemspec, rebuild: rebuild, out: out) end @@ -317,19 +317,19 @@ def resolve name, *gates # # @param pin [Pin::Reference] # @return [String, nil] - def dereference(pin) + def dereference pin store.constants.dereference(pin) end # @param fqns [String] # @return [Array] - def get_extends(fqns) + def get_extends fqns store.get_extends(fqns) end # @param fqns [String] # @return [Array] - def get_includes(fqns) + def get_includes fqns store.get_includes(fqns) end @@ -341,7 +341,7 @@ def get_includes(fqns) # @return [Array] def get_instance_variable_pins namespace, scope = :instance result = [] - used = [namespace] + [namespace] result.concat store.get_instance_variables(namespace, scope) sc_fqns = namespace while (sc = store.get_superclass(sc_fqns)) @@ -364,12 +364,12 @@ def get_instance_variable_pins namespace, scope = :instance # @param location [Location] # # @return [Pin::BaseVariable, nil] - def var_at_location(candidates, name, closure, location) - with_correct_name = candidates.select { |pin| pin.name == name} + def var_at_location candidates, name, closure, location + with_correct_name = candidates.select { |pin| pin.name == name } vars_at_location = with_correct_name.reject do |pin| # visible_at? excludes the starting position, but we want to # include it for this purpose - (!pin.visible_at?(closure, location) && !pin.starts_at?(location)) + !pin.visible_at?(closure, location) && !pin.starts_at?(location) end vars_at_location.inject(&:combine_with) @@ -446,7 +446,7 @@ def get_methods rooted_tag, scope: :instance, visibility: [:public], deep: true comments: init_pin.comments, closure: init_pin.closure, source: init_pin.source, - type_location: init_pin.type_location, + type_location: init_pin.type_location ) new_pin.parameters = init_pin.parameters.map do |init_param| param = init_param.clone @@ -536,7 +536,8 @@ def get_complex_type_methods complex_type, context = '', internal = false # @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 + def get_method_stack rooted_tag, name, scope: :instance, visibility: %i[private protected public], + preserve_generics: false rooted_type = ComplexType.parse(rooted_tag) fqns = rooted_type.namespace namespace_pin = store.get_path_pins(fqns).first @@ -661,7 +662,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 sup = ComplexType.try_parse(sup) sub = ComplexType.try_parse(sub) # @todo If two literals are different values of the same type, it would @@ -693,14 +694,14 @@ 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| inc_tag.type.name }.include?(module_ns) end # @param pins [Enumerable] # @param visibility [Enumerable] # @return [Array] - def resolve_method_aliases pins, visibility = [:public, :private, :protected] + def resolve_method_aliases pins, visibility = %i[public private protected] with_resolved_aliases = pins.map do |pin| next pin unless pin.is_a?(Pin::MethodAlias) resolved = resolve_method_alias(pin) @@ -730,7 +731,7 @@ def workspace # @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 @@ -786,7 +787,7 @@ def store 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 - fqns_generic_params = rooted_type.all_params + rooted_type.all_params namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first return [] if no_core && fqns =~ /^(Object|BasicObject|Class|Module)$/ reqstr = "#{fqns}|#{scope}|#{visibility.sort}|#{deep}" @@ -797,7 +798,9 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false # ensure we start out with any immediate methods in this # namespace so we roughly match the same ordering of get_methods # and obey the 'deep' instruction - direct_convention_methods, convention_methods_by_reference = environ.pins.partition { |p| p.namespace == rooted_tag } + direct_convention_methods, convention_methods_by_reference = environ.pins.partition do |p| + p.namespace == rooted_tag + end result.concat direct_convention_methods if deep && scope == :instance @@ -809,8 +812,10 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false # Store#get_methods doesn't know about full tags, just # namespaces; resolving the generics in the method pins is this # class' responsibility - methods = store.get_methods(fqns, scope: scope, visibility: visibility).sort{ |a, b| a.name <=> b.name } - logger.info { "ApiMap#inner_get_methods(rooted_tag=#{rooted_tag.inspect}, scope=#{scope.inspect}, visibility=#{visibility.inspect}, deep=#{deep.inspect}, skip=#{skip.inspect}, fqns=#{fqns}) - added from store: #{methods}" } + methods = store.get_methods(fqns, scope: scope, visibility: visibility).sort { |a, b| a.name <=> b.name } + logger.info do + "ApiMap#inner_get_methods(rooted_tag=#{rooted_tag.inspect}, scope=#{scope.inspect}, visibility=#{visibility.inspect}, deep=#{deep.inspect}, skip=#{skip.inspect}, fqns=#{fqns}) - added from store: #{methods}" + end result.concat methods if deep result.concat convention_methods_by_reference @@ -819,7 +824,8 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false store.get_includes(fqns).reverse.each do |ref| in_tag = dereference(ref) # @sg-ignore Need to add nil check here - result.concat inner_get_methods_from_reference(in_tag, namespace_pin, rooted_type, scope, visibility, deep, skip, true) + 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? @@ -827,7 +833,9 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false visibility, true, skip, no_core) end else - logger.info { "ApiMap#inner_get_methods(#{fqns}, #{scope}, #{visibility}, #{deep}, #{skip}) - looking for get_extends() from #{fqns}" } + logger.info do + "ApiMap#inner_get_methods(#{fqns}, #{scope}, #{visibility}, #{deep}, #{skip}) - looking for get_extends() from #{fqns}" + end store.get_extends(fqns).reverse.each do |em| fqem = dereference(em) result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil? @@ -863,7 +871,7 @@ def path_macros 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 = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first return nil if pin.nil? pin.type end @@ -889,7 +897,7 @@ def prefer_non_nil_variables pins # @param alias_pin [Pin::MethodAlias] # @return [Pin::Method, nil] - def resolve_method_alias(alias_pin) + def resolve_method_alias alias_pin ancestors = store.get_ancestors(alias_pin.full_context.reduce_class_type.tag) # @type [Pin::Method, nil] original = nil @@ -920,7 +928,9 @@ def resolve_method_alias(alias_pin) end if original.nil? # :nocov: - Solargraph.assert_or_log(:alias_target_missing) { "Rejecting alias - target is missing while looking for #{alias_pin.full_context.tag} #{alias_pin.original} in #{alias_pin.scope} scope = #{alias_pin.inspect}" } + Solargraph.assert_or_log(:alias_target_missing) do + "Rejecting alias - target is missing while looking for #{alias_pin.full_context.tag} #{alias_pin.original} in #{alias_pin.scope} scope = #{alias_pin.inspect}" + end return nil # :nocov: end @@ -933,7 +943,7 @@ def resolve_method_alias(alias_pin) # @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) + 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: alias_pin.location, @@ -949,7 +959,7 @@ def create_resolved_alias_pin(alias_pin, original) return_type: original.return_type, source: :resolve_method_alias } - resolved_pin = Pin::Method.new **args + resolved_pin = Pin::Method.new(**args) # Clone signatures and parameters resolved_pin.signatures.each do |sig| @@ -987,7 +997,7 @@ def should_erase_generics_when_done? namespace_pin, rooted_type end # @param namespace_pin [Pin::Namespace, Pin::Constant] - def has_generics?(namespace_pin) + def has_generics? namespace_pin namespace_pin.is_a?(Pin::Namespace) && !namespace_pin.generics.empty? end diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 48cf05706..3d7405e0f 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -118,15 +118,15 @@ def catalog new_pins # @param k [String] # @param v [Set] set.classify(&:class) - .map { |k, v| pin_class_hash[k].concat v.to_a } + .map { |k, v| pin_class_hash[k].concat v.to_a } # @param k [String] # @param v [Set] set.classify(&:namespace) - .map { |k, v| namespace_hash[k].concat v.to_a } + .map { |k, v| namespace_hash[k].concat v.to_a } # @param k [String] # @param v [Set] set.classify(&:path) - .map { |k, v| path_pin_hash[k].concat v.to_a } + .map { |k, v| path_pin_hash[k].concat v.to_a } @namespaces = path_pin_hash.keys.compact.to_set map_references Pin::Reference::Include, include_references map_references Pin::Reference::Prepend, prepend_references @@ -157,9 +157,7 @@ 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| - new_pin = if pin.path.end_with?('#initialize') - path_pin_hash[pin.path.sub(/#initialize/, '.new')].first - end + new_pin = (path_pin_hash[pin.path.sub('#initialize', '.new')].first if pin.path.end_with?('#initialize')) (ovr.tags.map(&:tag_name) + ovr.delete).uniq.each do |tag| # @sg-ignore Wrong argument type for # YARD::Docstring#delete_tags: name expected String, diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index 05010c636..f971d285d 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -3,7 +3,6 @@ module Solargraph class ApiMap module SourceToYard - # Get the YARD CodeObject at the specified path. # # @sg-ignore Declared return type generic, nil does not match @@ -36,20 +35,20 @@ def rake_yard store end if pin.type == :class # @param obj [YARD::CodeObjects::RootObject] - code_object_map[pin.path] ||= YARD::CodeObjects::ClassObject.new(root_code_object, pin.path) { |obj| + code_object_map[pin.path] ||= YARD::CodeObjects::ClassObject.new(root_code_object, pin.path) do |obj| # @sg-ignore flow sensitive typing needs to handle attrs next if pin.location.nil? || pin.location.filename.nil? # @sg-ignore flow sensitive typing needs to handle attrs obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) - } + end else # @param obj [YARD::CodeObjects::RootObject] - code_object_map[pin.path] ||= YARD::CodeObjects::ModuleObject.new(root_code_object, pin.path) { |obj| + code_object_map[pin.path] ||= YARD::CodeObjects::ModuleObject.new(root_code_object, pin.path) do |obj| # @sg-ignore flow sensitive typing needs to handle attrs next if pin.location.nil? || pin.location.filename.nil? # @sg-ignore flow sensitive typing needs to handle attrs obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) - } + end end code_object_map[pin.path].docstring = pin.docstring store.get_includes(pin.path).each do |ref| @@ -75,12 +74,14 @@ def rake_yard store # @sg-ignore Need to add nil check here # @param obj [YARD::CodeObjects::RootObject] - code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope) { |obj| + code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new( + code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope + ) do |obj| # @sg-ignore flow sensitive typing needs to handle attrs next if pin.location.nil? || pin.location.filename.nil? # @sg-ignore flow sensitive typing needs to handle attrs obj.add_file pin.location.filename, pin.location.range.start.line - } + end method_object = code_object_at(pin.path, YARD::CodeObjects::MethodObject) # @sg-ignore Need to add nil check here method_object.docstring = pin.docstring diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index bb371d173..cca195337 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -38,10 +38,10 @@ def update *pinsets pinsets[changed..].each_with_index do |pins, idx| @pinsets[changed + idx] = pins @indexes[changed + idx] = if pins.empty? - @indexes[changed + idx - 1] - else - @indexes[changed + idx - 1].merge(pins) - end + @indexes[changed + idx - 1] + else + @indexes[changed + idx - 1].merge(pins) + end end constants.clear cached_qualify_superclass.clear @@ -60,10 +60,10 @@ def inspect # @param visibility [Array] # @return [Enumerable] def get_constants fqns, visibility = [:public] - namespace_children(fqns).select { |pin| + namespace_children(fqns).select do |pin| # @sg-ignore flow sensitive typing not smart enough to handle this case !pin.name.empty? && (pin.is_a?(Pin::Namespace) || pin.is_a?(Pin::Constant)) && visibility.include?(pin.visibility) - } + end end # @param fqns [String] @@ -77,8 +77,10 @@ def get_methods fqns, scope: :instance, visibility: [:public] GemPins.combine_method_pins_by_path(all_pins) end - BOOLEAN_SUPERCLASS_PIN = Pin::Reference::Superclass.new(name: 'Boolean', closure: Pin::ROOT_PIN, source: :solargraph) - OBJECT_SUPERCLASS_PIN = Pin::Reference::Superclass.new(name: 'Object', closure: Pin::ROOT_PIN, source: :solargraph) + BOOLEAN_SUPERCLASS_PIN = Pin::Reference::Superclass.new(name: 'Boolean', closure: Pin::ROOT_PIN, + source: :solargraph) + OBJECT_SUPERCLASS_PIN = Pin::Reference::Superclass.new(name: 'Object', closure: Pin::ROOT_PIN, + source: :solargraph) # @param fqns [String, nil] # @return [Pin::Reference::Superclass, nil] @@ -129,17 +131,17 @@ def get_path_pins path # @param fqns [String, nil] # @param scope [Symbol] :class or :instance # @return [Enumerable] - def get_instance_variables(fqns, scope = :instance) - all_instance_variables.select { |pin| + def get_instance_variables fqns, scope = :instance + all_instance_variables.select do |pin| pin.binder.namespace == fqns && pin.binder.scope == scope - } + end end # @param fqns [String] # # @return [Enumerable] - def get_class_variables(fqns) - namespace_children(fqns).select { |pin| pin.is_a?(Pin::ClassVariable)} + def get_class_variables fqns + namespace_children(fqns).select { |pin| pin.is_a?(Pin::ClassVariable) } end # @return [Enumerable] @@ -149,7 +151,7 @@ def get_symbols # @param fqns [String] # @return [Boolean] - def namespace_exists?(fqns) + def namespace_exists? fqns fqns_pins(fqns).any? end @@ -165,7 +167,7 @@ def method_pins # @param fqns [String] # @return [Array] - def domains(fqns) + def domains fqns result = [] fqns_pins(fqns).each do |nspin| result.concat nspin.domains @@ -178,7 +180,7 @@ def named_macros @named_macros ||= begin result = {} pins.each do |pin| - pin.macros.select{|m| m.tag.tag_name == 'macro' && !m.tag.text.empty? }.each do |macro| + pin.macros.select { |m| m.tag.tag_name == 'macro' && !m.tag.text.empty? }.each do |macro| next if macro.tag.name.nil? || macro.tag.name.empty? result[macro.tag.name] = macro end @@ -217,7 +219,7 @@ def fqns_pins fqns # 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) + def get_ancestors fqns return [] if fqns.nil? || fqns.empty? ancestors = [fqns] @@ -260,7 +262,7 @@ def get_ancestors(fqns) # @param fqns [String] # # @return [Array] - def get_ancestor_references(fqns) + def get_ancestor_references fqns (get_prepends(fqns) + get_includes(fqns) + [get_superclass(fqns)]).compact end @@ -350,7 +352,7 @@ def all_instance_variables # @param fqns [String] # @return [Pin::Reference::Superclass, nil] - def try_special_superclasses(fqns) + def try_special_superclasses fqns return OBJECT_SUPERCLASS_PIN if fqns == 'Boolean' return OBJECT_SUPERCLASS_PIN if !%w[BasicObject Object].include?(fqns) && namespace_exists?(fqns) diff --git a/lib/solargraph/bench.rb b/lib/solargraph/bench.rb index e6180c933..ed72c5a82 100644 --- a/lib/solargraph/bench.rb +++ b/lib/solargraph/bench.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true - module Solargraph # A container of source maps and workspace data to be cataloged in an ApiMap. # diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index 0b5618d2c..b0f834318 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -4,7 +4,7 @@ module Solargraph # A container for type data based on YARD type tags. # class ComplexType - GENERIC_TAG_NAME = 'generic'.freeze + GENERIC_TAG_NAME = 'generic' # @!parse # include TypeMethods include Equality @@ -19,7 +19,7 @@ def initialize types = [UniqueType::UNDEFINED] # @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.delete_if { |i| %w[false true].include?(i.name) } items.unshift(UniqueType::BOOLEAN) end # @type [Array] @@ -40,7 +40,7 @@ def initialize types = [UniqueType::UNDEFINED] def qualify api_map, *gates red = reduce_object types = red.items.map do |t| - next t if ['nil', 'void', 'undefined'].include?(t.name) + next t if %w[nil void undefined].include?(t.name) next t if ['::Boolean'].include?(t.rooted_name) t.qualify api_map, *gates end @@ -54,7 +54,10 @@ def qualify api_map, *gates def resolve_generics_from_context generics_to_resolve, context_type, resolved_generic_values: {} return self unless generic? - ComplexType.new(@items.map { |i| i.resolve_generics_from_context(generics_to_resolve, context_type, resolved_generic_values: resolved_generic_values) }) + ComplexType.new(@items.map do |i| + i.resolve_generics_from_context(generics_to_resolve, context_type, + resolved_generic_values: resolved_generic_values) + end) end # @return [UniqueType] @@ -85,14 +88,14 @@ def self_to_type dst # @sg-ignore Declared return type # ::Array<::Solargraph::ComplexType::UniqueType> does not match # inferred type ::Array<::Proc> for Solargraph::ComplexType#map - def map(&block) + def map &block @items.map(&block) end # @yieldparam [UniqueType] # @return [Enumerable] def each &block - @items.each &block + @items.each(&block) end # @yieldparam [UniqueType] @@ -103,7 +106,7 @@ def each_unique_type &block return enum_for(__method__) unless block_given? @items.each do |item| - item.each_unique_type &block + item.each_unique_type(&block) end end @@ -113,7 +116,7 @@ def each_unique_type &block # @param make_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) + 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, @@ -134,13 +137,13 @@ def to_a # @param index [Integer] # @return [UniqueType] - def [](index) + def [] index @items[index] end # @return [Array] def select &block - @items.select &block + @items.select(&block) end # @return [String] @@ -157,6 +160,7 @@ def namespaces # @param name [Symbol] # # @return [Object, nil] + # @param [Array] args def method_missing name, *args, &block return if @items.first.nil? return @items.first.send(name, *args, &block) if respond_to_missing?(name) @@ -165,7 +169,7 @@ def method_missing name, *args, &block # @param name [Symbol] # @param include_private [Boolean] - def respond_to_missing?(name, include_private = false) + def respond_to_missing? name, include_private = false TypeMethods.public_instance_methods.include?(name) || super end @@ -219,10 +223,10 @@ def desc # # @param variance [:invariant, :covariant, :contravariant] # @return [Boolean] - def conforms_to?(api_map, expected, + def conforms_to? api_map, expected, situation, rules = [], - variance: erased_variance(situation)) + variance: erased_variance(situation) expected = expected.downcast_to_literal_if_possible inferred = downcast_to_literal_if_possible @@ -263,14 +267,14 @@ def rooted_tags # @yieldparam [UniqueType] def all? &block - @items.all? &block + @items.all?(&block) end # @yieldparam [UniqueType] # @yieldreturn [Boolean] # @return [Boolean] def any? &block - @items.compact.any? &block + @items.compact.any?(&block) end def selfy? @@ -290,8 +294,10 @@ def simplify_literals # @yieldparam t [UniqueType] # @yieldreturn [UniqueType] # @return [ComplexType] - def transform(new_name = nil, &transform_type) - raise "Please remove leading :: and set rooted with recreate() instead - #{new_name}" if new_name&.start_with?('::') + def transform new_name = nil, &transform_type + if new_name&.start_with?('::') + raise "Please remove leading :: and set rooted with recreate() instead - #{new_name}" + end ComplexType.new(map { |ut| ut.transform(new_name, &transform_type) }) end @@ -329,7 +335,7 @@ def all_params # @return [ComplexType] def reduce_class_type new_items = items.flat_map do |type| - next type unless ['Module', 'Class'].include?(type.name) + next type unless %w[Module Class].include?(type.name) next type if type.all_params.empty? type.all_params @@ -344,7 +350,7 @@ def all_rooted? end # @param other [ComplexType, UniqueType] - def erased_version_of?(other) + def erased_version_of? other return false if items.length != 1 || other.items.length != 1 @items.first.erased_version_of?(other.items.first) @@ -447,14 +453,14 @@ def parse *strings, partial: false # @param char [String] type_string&.each_char do |char| if char == '=' - #raise ComplexTypeError, "Invalid = in type #{type_string}" unless curly_stack > 0 + # raise ComplexTypeError, "Invalid = in type #{type_string}" unless curly_stack > 0 elsif char == '<' point_stack += 1 elsif char == '>' if subtype_string.end_with?('=') && curly_stack > 0 subtype_string += char elsif base.end_with?('=') - raise ComplexTypeError, "Invalid hash thing" unless key_types.nil? + raise ComplexTypeError, 'Invalid hash thing' unless key_types.nil? # types.push ComplexType.new([UniqueType.new(base[0..-2].strip)]) # @sg-ignore Need to add nil check here types.push UniqueType.parse(base[0..-2].strip, subtype_string) @@ -499,12 +505,15 @@ def parse *strings, partial: false subtype_string.concat char end end - raise ComplexTypeError, "Unclosed subtype in #{type_string}" if point_stack != 0 || curly_stack != 0 || paren_stack != 0 + if point_stack != 0 || curly_stack != 0 || paren_stack != 0 + raise ComplexTypeError, + "Unclosed subtype in #{type_string}" + end # types.push ComplexType.new([UniqueType.new(base, subtype_string)]) types.push UniqueType.parse(base.strip, subtype_string.strip) end unless key_types.nil? - raise ComplexTypeError, "Invalid use of key/value parameters" unless partial + raise ComplexTypeError, 'Invalid use of key/value parameters' unless partial return key_types if types.empty? return [key_types, types] end @@ -516,7 +525,7 @@ def parse *strings, partial: false # @param strings [Array] # @return [ComplexType] def try_parse *strings - parse *strings + parse(*strings) rescue ComplexTypeError => e Solargraph.logger.info "Error parsing complex type `#{strings.join(', ')}`: #{e.message}" ComplexType::UNDEFINED @@ -541,9 +550,7 @@ def try_parse *strings # @param dst [String] # @return [String] def reduce_class dst - while dst =~ /^(Class|Module)\<(.*?)\>$/ - dst = dst.sub(/^(Class|Module)\$/, '') - end + dst = dst.sub(/^(Class|Module)$/, '') while dst =~ /^(Class|Module)<(.*?)>$/ dst end end diff --git a/lib/solargraph/complex_type/type_methods.rb b/lib/solargraph/complex_type/type_methods.rb index 9ba833c36..afd73050e 100644 --- a/lib/solargraph/complex_type/type_methods.rb +++ b/lib/solargraph/complex_type/type_methods.rb @@ -87,7 +87,7 @@ def erased_variance situation = :method_call # @param generics_to_erase [Enumerable] # @return [self] - def erase_generics(generics_to_erase) + def erase_generics generics_to_erase transform do |type| if type.name == ComplexType::GENERIC_TAG_NAME if type.all_params.length == 1 && generics_to_erase.include?(type.all_params.first.to_s) @@ -142,7 +142,7 @@ def namespace @namespace ||= lambda do return 'Object' if duck_type? return 'NilClass' if nil_type? - return (name == 'Class' || name == 'Module') && !subtypes.empty? ? subtypes.first.name : name + %w[Class Module].include?(name) && !subtypes.empty? ? subtypes.first.name : name end.call end @@ -150,7 +150,7 @@ def namespace def namespace_type return ComplexType.parse('::Object') if duck_type? return ComplexType.parse('::NilClass') if nil_type? - return subtypes.first if (name == 'Class' || name == 'Module') && !subtypes.empty? + return subtypes.first if %w[Class Module].include?(name) && !subtypes.empty? self end @@ -177,7 +177,7 @@ def rooted_substring end # @return [String] - def generate_substring_from(&to_str) + def generate_substring_from &to_str key_types_str = key_types.map(&to_str).join(', ') subtypes_str = subtypes.map(&to_str).join(', ') if (key_types.none?(&:defined?) && subtypes.none?(&:defined?)) || @@ -187,19 +187,17 @@ def generate_substring_from(&to_str) "{#{key_types_str} => #{subtypes_str}}" elsif fixed_parameters? "(#{subtypes_str})" + elsif name == 'Hash' + "<#{key_types_str}, #{subtypes_str}>" else - if name == 'Hash' - "<#{key_types_str}, #{subtypes_str}>" - else - "<#{key_types_str}#{subtypes_str}>" - end + "<#{key_types_str}#{subtypes_str}>" end end # @return [::Symbol] :class or :instance def scope @scope ||= :instance if duck_type? || nil_type? - @scope ||= (name == 'Class' || name == 'Module') && !subtypes.empty? ? :class : :instance + @scope ||= %w[Class Module].include?(name) && !subtypes.empty? ? :class : :instance end # @param other [Object] diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index c701d3ac8..201dec4f5 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -25,9 +25,7 @@ class UniqueType # @param make_rooted [Boolean, nil] # @return [UniqueType] def self.parse name, substring = '', make_rooted: nil - if name.start_with?(':::') - raise ComplexTypeError, "Illegal prefix: #{name}" - end + raise ComplexTypeError, "Illegal prefix: #{name}" if name.start_with?(':::') if name.start_with?('::') name = name[2..-1] rooted = true @@ -48,13 +46,17 @@ def self.parse name, substring = '', make_rooted: nil # @sg-ignore Need to add nil check here 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) + unless !subs.is_a?(ComplexType) and subs.length == 2 and !subs[0].is_a?(UniqueType) and !subs[1].is_a?(UniqueType) + raise ComplexTypeError, + "Bad hash type: name=#{name}, substring=#{substring}" + end key_types.concat(subs[0].map { |u| ComplexType.new([u]) }) subtypes.concat(subs[1].map { |u| ComplexType.new([u]) }) elsif parameters_type == :list && name == 'Hash' # Treat Hash as Hash{A => B} if subs.length != 2 - raise ComplexTypeError, "Bad hash type: name=#{name}, substring=#{substring} - must have exactly two parameters" + raise ComplexTypeError, + "Bad hash type: name=#{name}, substring=#{substring} - must have exactly two parameters" end key_types.concat(subs[0].map { |u| ComplexType.new([u]) }) subtypes.concat(subs[1].map { |u| ComplexType.new([u]) }) @@ -71,9 +73,9 @@ def self.parse name, substring = '', make_rooted: nil # @param subtypes [Array] # @param rooted [Boolean] # @param parameters_type [Symbol, nil] - def initialize(name, key_types = [], subtypes = [], rooted:, parameters_type: nil) - if parameters_type.nil? - raise "You must supply parameters_type if you provide parameters" unless key_types.empty? && subtypes.empty? + def initialize name, key_types = [], subtypes = [], rooted:, parameters_type: nil + if parameters_type.nil? && !(key_types.empty? && subtypes.empty?) + raise 'You must supply parameters_type if you provide parameters' end raise "Please remove leading :: and set rooted instead - #{name.inspect}" if name.start_with?('::') @name = name @@ -95,7 +97,7 @@ def implicit_union? # @todo use api_map to establish number of generics in type; # if only one is allowed but multiple are passed in, treat # those as implicit unions - ['Hash', 'Array', 'Set', '_ToAry', 'Enumerable', '_Each'].include?(name) && parameters_type != :fixed + %w[Hash Array Set _ToAry Enumerable _Each].include?(name) && parameters_type != :fixed end def to_s @@ -175,7 +177,7 @@ def determine_non_literal_name # | `false` return name if name.empty? return 'NilClass' if name == 'nil' - return 'Boolean' if ['true', 'false'].include?(name) + return 'Boolean' if %w[true false].include?(name) return 'Symbol' if name[0] == ':' # @sg-ignore Need to add nil check here return 'String' if ['"', "'"].include?(name[0]) @@ -183,7 +185,7 @@ def determine_non_literal_name name end - def eql?(other) + def eql? other self.class == other.class && # @sg-ignore flow sensitive typing should support .class == .class @name == other.name && @@ -199,7 +201,7 @@ def eql?(other) @parameters_type == other.parameters_type end - def ==(other) + def == other eql?(other) end @@ -231,7 +233,7 @@ def parameter_variance _situation, default = :covariant # 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? + if %w[Hash Tuple Array Set Enumerable].include?(name) && fixed_parameters? :covariant else default @@ -244,7 +246,7 @@ def interface? end # @param other [UniqueType] - def erased_version_of?(other) + def erased_version_of? other name == other.name && (all_params.empty? || all_params.all?(&:undefined?)) end @@ -253,8 +255,8 @@ def erased_version_of?(other) # @param situation [:method_call, :assignment, :return_type] # @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: erased_variance(situation)) + 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 @@ -315,9 +317,9 @@ def to_rbs 'nil' elsif name == GENERIC_TAG_NAME all_params.first&.name - elsif ['Class', 'Module'].include?(name) + elsif %w[Class Module].include?(name) rbs_name - elsif ['Tuple', 'Array'].include?(name) && fixed_parameters? + elsif %w[Tuple Array].include?(name) && fixed_parameters? # tuples don't have a name; they're just [foo, bar, baz]. if substring == '()' # but there are no zero element tuples, so we go with an array @@ -342,7 +344,7 @@ def parameters? # @param types [Array] # @return [String] - def rbs_union(types) + def rbs_union types if types.length == 1 types.first.to_rbs else @@ -397,7 +399,8 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge end if new_binding resolved_generic_values.transform_values! do |complex_type| - complex_type.resolve_generics_from_context(generics_to_resolve, nil, resolved_generic_values: resolved_generic_values) + complex_type.resolve_generics_from_context(generics_to_resolve, nil, + resolved_generic_values: resolved_generic_values) end end # @sg-ignore flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) @@ -405,8 +408,10 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge end # @todo typechecking should complain when the method being called has no @yieldparam tag - new_key_types = resolve_param_generics_from_context(generics_to_resolve, context_type, resolved_generic_values, &:key_types) - new_subtypes = resolve_param_generics_from_context(generics_to_resolve, context_type, resolved_generic_values, &:subtypes) + new_key_types = resolve_param_generics_from_context(generics_to_resolve, context_type, resolved_generic_values, + &:key_types) + new_subtypes = resolve_param_generics_from_context(generics_to_resolve, context_type, resolved_generic_values, + &:subtypes) recreate(new_key_types: new_key_types, new_subtypes: new_subtypes) end @@ -415,7 +420,7 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge # @param resolved_generic_values [Hash{String => ComplexType}] # @yieldreturn [Array] # @return [Array] - def resolve_param_generics_from_context(generics_to_resolve, context_type, resolved_generic_values) + def resolve_param_generics_from_context generics_to_resolve, context_type, resolved_generic_values types = yield self types.each_with_index.flat_map do |ct, i| ct.items.flat_map do |ut| @@ -423,10 +428,12 @@ def resolve_param_generics_from_context(generics_to_resolve, context_type, resol if context_params && context_params[i] type_arg = context_params[i] type_arg.map do |new_unique_context_type| - ut.resolve_generics_from_context generics_to_resolve, new_unique_context_type, resolved_generic_values: resolved_generic_values + ut.resolve_generics_from_context generics_to_resolve, new_unique_context_type, + resolved_generic_values: resolved_generic_values end else - ut.resolve_generics_from_context generics_to_resolve, nil, resolved_generic_values: resolved_generic_values + ut.resolve_generics_from_context generics_to_resolve, nil, + resolved_generic_values: resolved_generic_values end end end @@ -482,7 +489,7 @@ def map &block # @yieldreturn [self] # @return [Enumerable] def each &block - [self].each &block + [self].each(&block) end # @return [Array] @@ -496,7 +503,7 @@ def to_a # @param make_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) + 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?('::') new_name ||= name @@ -530,8 +537,10 @@ def force_rooted # @yieldparam t [UniqueType] # @yieldreturn [self] # @return [self] - def transform(new_name = nil, &transform_type) - raise "Please remove leading :: and set rooted with recreate() instead - #{new_name}" if new_name&.start_with?('::') + def transform new_name = nil, &transform_type + if new_name&.start_with?('::') + raise "Please remove leading :: and set rooted with recreate() instead - #{new_name}" + end if name == ComplexType::GENERIC_TAG_NAME # doesn't make sense to manipulate the name of the generic new_key_types = @key_types @@ -540,7 +549,8 @@ def transform(new_name = nil, &transform_type) new_key_types = @key_types.flat_map { |ct| ct.items.map { |ut| ut.transform(&transform_type) } } new_subtypes = @subtypes.flat_map { |ct| ct.items.map { |ut| ut.transform(&transform_type) } } end - new_type = recreate(new_name: new_name || name, new_key_types: new_key_types, new_subtypes: new_subtypes, make_rooted: @rooted) + new_type = recreate(new_name: new_name || name, new_key_types: new_key_types, new_subtypes: new_subtypes, + make_rooted: @rooted) yield new_type end @@ -585,7 +595,7 @@ def any? &block # @return [ComplexType] def reduce_class_type new_items = items.flat_map do |type| - next type unless ['Module', 'Class'].include?(type.name) + next type unless %w[Module Class].include?(type.name) next type if type.all_params.empty? type.all_params @@ -603,12 +613,12 @@ def rooted? end # @param name_to_check [String] - def can_root_name?(name_to_check = name) + def can_root_name? name_to_check = name self.class.can_root_name?(name_to_check) end # @param name [String] - def self.can_root_name?(name) + def self.can_root_name? name # name is not lowercase !name.empty? && name != name.downcase end @@ -625,7 +635,6 @@ def self.can_root_name?(name) '::NilClass' => UniqueType::NIL }.freeze - include Logging end end diff --git a/lib/solargraph/convention.rb b/lib/solargraph/convention.rb index 89eac82b7..5e73eb3bf 100644 --- a/lib/solargraph/convention.rb +++ b/lib/solargraph/convention.rb @@ -30,7 +30,7 @@ def self.unregister convention # @param source_map [SourceMap] # @return [Environ] - def self.for_local(source_map) + def self.for_local source_map result = Environ.new @@conventions.each do |conv| result.merge conv.local(source_map) @@ -40,7 +40,7 @@ def self.for_local(source_map) # @param doc_map [DocMap] # @return [Environ] - def self.for_global(doc_map) + def self.for_global doc_map result = Environ.new @@conventions.each do |conv| result.merge conv.global(doc_map) diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index 193364061..960852caa 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -93,7 +93,7 @@ def data_definition_node # @param attribute_node [Parser::AST::Node] # @param attribute_name [String] # @return [String, nil] - def attribute_comments(attribute_node, attribute_name) + def attribute_comments attribute_node, attribute_name data_comments = comments_for(attribute_node) return if data_comments.nil? || data_comments.empty? diff --git a/lib/solargraph/convention/data_definition/data_assignment_node.rb b/lib/solargraph/convention/data_definition/data_assignment_node.rb index cffe77494..97ef272cf 100644 --- a/lib/solargraph/convention/data_definition/data_assignment_node.rb +++ b/lib/solargraph/convention/data_definition/data_assignment_node.rb @@ -23,7 +23,7 @@ class << self # s(:args), # s(:send, nil, :bar)))) # @param node [::Parser::AST::Node] - def match?(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 49cf210a7..d14817933 100644 --- a/lib/solargraph/convention/data_definition/data_definition_node.rb +++ b/lib/solargraph/convention/data_definition/data_definition_node.rb @@ -27,7 +27,7 @@ class << self # s(:send, nil, :bar))) # # @param node [Parser::AST::Node] - def match?(node) + def match? node return false unless node&.type == :class data_definition_node?(node.children[1]) @@ -37,7 +37,7 @@ def match?(node) # @param data_node [Parser::AST::Node] # @return [Boolean] - def data_definition_node?(data_node) + def data_definition_node? data_node return false unless data_node.is_a?(::Parser::AST::Node) return false unless data_node&.type == :send return false unless data_node.children[0]&.type == :const @@ -49,7 +49,7 @@ def data_definition_node?(data_node) end # @param node [Parser::AST::Node] - def initialize(node) + def initialize node @node = node end diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index 7871dec00..f1d240363 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -142,7 +142,7 @@ def parse_comments # @param tag [YARD::Tags::Tag, nil] The param tag for this attribute.xtract_ # # @return [String] - def tag_string(tag) + def tag_string tag tag&.types&.join(',') || 'undefined' end @@ -150,8 +150,8 @@ def tag_string(tag) # @param for_setter [Boolean] If true, will return a @param tag instead of a @return tag # # @return [String] The formatted comment for the attribute - def attribute_comment(tag, for_setter) - return "" if tag.nil? + def attribute_comment tag, for_setter + return '' if tag.nil? suffix = "[#{tag_string(tag)}] #{tag.text}" diff --git a/lib/solargraph/convention/struct_definition/struct_assignment_node.rb b/lib/solargraph/convention/struct_definition/struct_assignment_node.rb index 2816de6ed..6dcafd068 100644 --- a/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_assignment_node.rb @@ -24,7 +24,7 @@ class << self # s(:send, nil, :bar)))) # # @param node [Parser::AST::Node] - def match?(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 8f03f1fcb..51518a687 100644 --- a/lib/solargraph/convention/struct_definition/struct_definition_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_definition_node.rb @@ -27,7 +27,7 @@ class << self # s(:send, nil, :bar))) # # @param node [Parser::AST::Node] - def match?(node) + def match? node return false unless node&.type == :class struct_definition_node?(node.children[1]) @@ -37,7 +37,7 @@ def match?(node) # @param struct_node [Parser::AST::Node] # @return [Boolean] - def struct_definition_node?(struct_node) + def struct_definition_node? struct_node return false unless struct_node.is_a?(::Parser::AST::Node) return false unless struct_node&.type == :send return false unless struct_node.children[0]&.type == :const @@ -49,7 +49,7 @@ def struct_definition_node?(struct_node) end # @param node [Parser::AST::Node] - def initialize(node) + def initialize node @node = node end diff --git a/lib/solargraph/diagnostics/rubocop.rb b/lib/solargraph/diagnostics/rubocop.rb index 5eade7592..23dbbfe63 100644 --- a/lib/solargraph/diagnostics/rubocop.rb +++ b/lib/solargraph/diagnostics/rubocop.rb @@ -33,7 +33,7 @@ def diagnose source, _api_map # a time - it uses 'chdir' to read config files with ERB, # which can conflict with other chdirs. result = Solargraph::CHDIR_MUTEX.synchronize do - redirect_stdout{ runner.run(paths) } + redirect_stdout { runner.run(paths) } end return [] if result.empty? @@ -77,7 +77,7 @@ def offense_to_diagnostic off severity: SEVERITIES[off['severity']], source: 'rubocop', code: off['cop_name'], - message: off['message'].gsub(/^#{off['cop_name']}\:/, '') + message: off['message'].gsub(/^#{off['cop_name']}:/, '') } end @@ -96,22 +96,22 @@ def offense_start_position off # @param off [Hash{String => Hash{String => Integer}}] # @return [Position] def offense_ending_position off - if off['location']['start_line'] != off['location']['last_line'] - Position.new(off['location']['start_line'], 0) - else + if off['location']['start_line'] == off['location']['last_line'] start_line = off['location']['start_line'] - 1 # @type [Integer] last_column = off['location']['last_column'] line = @source.code.lines[start_line] col_off = if line.nil? || line.empty? - 1 - else - 0 - end + 1 + else + 0 + end Position.new( start_line, last_column - col_off ) + else + Position.new(off['location']['start_line'], 0) end end end diff --git a/lib/solargraph/diagnostics/rubocop_helpers.rb b/lib/solargraph/diagnostics/rubocop_helpers.rb index b306f638a..52a205465 100644 --- a/lib/solargraph/diagnostics/rubocop_helpers.rb +++ b/lib/solargraph/diagnostics/rubocop_helpers.rb @@ -13,7 +13,7 @@ module RubocopHelpers # @param version [String, nil] # @raise [InvalidRubocopVersionError] if _version_ is not installed # @return [void] - def require_rubocop(version = nil) + def require_rubocop version = nil begin # @type [String] gem_path = Gem::Specification.find_by_name('rubocop', version).full_gem_path @@ -24,7 +24,7 @@ def require_rubocop(version = nil) # @type [Array] specs = e.specs raise InvalidRubocopVersionError, - "could not find '#{e.name}' (#{e.requirement}) - "\ + "could not find '#{e.name}' (#{e.requirement}) - " \ "did find: [#{specs.map { |s| s.version.version }.join(', ')}]" end require 'rubocop' diff --git a/lib/solargraph/diagnostics/type_check.rb b/lib/solargraph/diagnostics/type_check.rb index ea833860b..b1333f9d9 100644 --- a/lib/solargraph/diagnostics/type_check.rb +++ b/lib/solargraph/diagnostics/type_check.rb @@ -10,19 +10,19 @@ class TypeCheck < Base def diagnose source, api_map # return [] unless args.include?('always') || api_map.workspaced?(source.filename) severity = Diagnostics::Severities::ERROR - level = (args.reverse.find { |a| ['normal', 'typed', 'strict', 'strong'].include?(a) }) || :normal + level = args.reverse.find { |a| %w[normal typed strict strong].include?(a) } || :normal # @sg-ignore sensitive typing needs to handle || on nil types checker = Solargraph::TypeChecker.new(source.filename, api_map: api_map, level: level.to_sym) checker.problems - .sort { |a, b| a.location.range.start.line <=> b.location.range.start.line } - .map do |problem| - { - range: extract_first_line(problem.location, source), - severity: severity, - source: 'Typecheck', - message: problem.message - } - end + .sort { |a, b| a.location.range.start.line <=> b.location.range.start.line } + .map do |problem| + { + range: extract_first_line(problem.location, source), + severity: severity, + source: 'Typecheck', + message: problem.message + } + end end private diff --git a/lib/solargraph/diagnostics/update_errors.rb b/lib/solargraph/diagnostics/update_errors.rb index b6f9baa89..6da4c5835 100644 --- a/lib/solargraph/diagnostics/update_errors.rb +++ b/lib/solargraph/diagnostics/update_errors.rb @@ -26,7 +26,7 @@ def diagnose source, api_map def combine_ranges code, ranges result = [] lines = [] - ranges.sort{|a, b| a.start.line <=> b.start.line}.each do |rng| + ranges.sort { |a, b| a.start.line <=> b.start.line }.each do |rng| next if rng.nil? || lines.include?(rng.start.line) lines.push rng.start.line next if rng.start.line >= code.lines.length diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 79bb086cc..f3cdfa94a 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -98,12 +98,12 @@ def dependencies out: $stderr @dependencies ||= begin gem_deps = gemspecs - .flat_map { |spec| workspace.fetch_dependencies(spec, out: out) } - .uniq(&:name) + .flat_map { |spec| workspace.fetch_dependencies(spec, out: out) } + .uniq(&:name) stdlib_deps = gemspecs - .flat_map { |spec| workspace.stdlib_dependencies(spec.name) } - .flat_map { |dep_name| workspace.resolve_require(dep_name) } - .compact + .flat_map { |spec| workspace.stdlib_dependencies(spec.name) } + .flat_map { |dep_name| workspace.resolve_require(dep_name) } + .compact existing_gems = gemspecs.map(&:name) (gem_deps + stdlib_deps).reject { |gemspec| existing_gems.include? gemspec.name } end @@ -167,7 +167,7 @@ def load_serialized_gem_pins out: @out end end - existing_pin_count = serialized_pins.length + serialized_pins.length time = Benchmark.measure do gemspecs.each do |gemspec| # only deserializes already-cached gems @@ -179,7 +179,7 @@ def load_serialized_gem_pins out: @out end end end - pins_processed = serialized_pins.length - existing_pin_count + serialized_pins.length 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" diff --git a/lib/solargraph/equality.rb b/lib/solargraph/equality.rb index edef8fa7a..0a266df79 100644 --- a/lib/solargraph/equality.rb +++ b/lib/solargraph/equality.rb @@ -10,7 +10,7 @@ module Equality # @param other [Object] # @return [Boolean] - def eql?(other) + def eql? other self.class.eql?(other.class) && # @sg-ignore flow sensitive typing should support .class == .class equality_fields.eql?(other.equality_fields) @@ -18,8 +18,8 @@ def eql?(other) # @param other [Object] # @return [Boolean] - def ==(other) - self.eql?(other) + def == other + eql?(other) end def hash diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index 790422065..340746a8f 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -13,7 +13,7 @@ class << self # @param pins [Array] # @return [Array] - def self.combine_method_pins_by_path(pins) + 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) by_path.transform_values! do |pins| @@ -47,7 +47,7 @@ def self.combine_method_pins(*pins) # @param rbs_pins [Array] # # @return [Array] - def self.combine(yard_pins, rbs_pins) + def self.combine yard_pins, rbs_pins in_yard = Set.new rbs_store = Solargraph::ApiMap::Store.new(rbs_pins) combined = yard_pins.map do |yard_pin| @@ -57,7 +57,9 @@ def self.combine(yard_pins, rbs_pins) next yard_pin unless rbs_pin && yard_pin.is_a?(Pin::Method) unless rbs_pin - logger.debug { "GemPins.combine: No rbs pin for #{yard_pin.path} - using YARD's '#{yard_pin.inspect} (return_type=#{yard_pin.return_type}; signatures=#{yard_pin.signatures})" } + logger.debug do + "GemPins.combine: No rbs pin for #{yard_pin.path} - using YARD's '#{yard_pin.inspect} (return_type=#{yard_pin.return_type}; signatures=#{yard_pin.signatures})" + end next yard_pin end diff --git a/lib/solargraph/language_server/error_codes.rb b/lib/solargraph/language_server/error_codes.rb index 492ca6462..7df65388a 100644 --- a/lib/solargraph/language_server/error_codes.rb +++ b/lib/solargraph/language_server/error_codes.rb @@ -5,16 +5,16 @@ module LanguageServer # The ErrorCode constants for the language server protocol. # module ErrorCodes - PARSE_ERROR = -32700 - INVALID_REQUEST = -32600 - METHOD_NOT_FOUND = -32601 - INVALID_PARAMS = -32602 - INTERNAL_ERROR = -32603 - SERVER_ERROR_START = -32099 - SERVER_ERROR_END = -32000 - SERVER_NOT_INITIALIZED = -32002 - UNKNOWN_ERROR_CODE = -32001 - REQUEST_CANCELLED = -32800 + PARSE_ERROR = -32_700 + INVALID_REQUEST = -32_600 + METHOD_NOT_FOUND = -32_601 + INVALID_PARAMS = -32_602 + INTERNAL_ERROR = -32_603 + SERVER_ERROR_START = -32_099 + SERVER_ERROR_END = -32_000 + SERVER_NOT_INITIALIZED = -32_002 + UNKNOWN_ERROR_CODE = -32_001 + REQUEST_CANCELLED = -32_800 end end end diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index 5a08d44b6..e8b914767 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -119,7 +119,7 @@ def receive request nil end else - logger.warn "Invalid message received." + logger.warn 'Invalid message received.' logger.debug request nil end @@ -154,7 +154,7 @@ def delete *uris lib.delete(*filenames) end uris.each do |uri| - send_notification "textDocument/publishDiagnostics", { + send_notification 'textDocument/publishDiagnostics', { uri: uri, diagnostics: [] } @@ -209,7 +209,7 @@ def diagnose uri logger.info "Diagnosing #{uri}" begin results = library.diagnose uri_to_file(uri) - send_notification "textDocument/publishDiagnostics", { + send_notification 'textDocument/publishDiagnostics', { uri: uri, diagnostics: results } @@ -354,7 +354,7 @@ def folders # @return [void] def send_notification method, params response = { - jsonrpc: "2.0", + jsonrpc: '2.0', method: method, params: params } @@ -377,7 +377,7 @@ def send_notification method, params def send_request method, params, &block @request_mutex.synchronize do message = { - jsonrpc: "2.0", + jsonrpc: '2.0', method: method, params: params, id: @next_request_id @@ -419,13 +419,13 @@ def register_capabilities methods # @return [void] def unregister_capabilities methods logger.debug "Unregistering capabilities: #{methods}" - unregisterations = methods.select{|m| registered?(m)}.map{ |m| + unregisterations = methods.select { |m| registered?(m) }.map do |m| @registered_capabilities.delete m { id: m, method: m } - } + end return if unregisterations.empty? send_request 'client/unregisterCapability', { unregisterations: unregisterations } end @@ -493,7 +493,7 @@ def locate_pins params params['data']['location']['range']['end']['character'] ) ) - result.concat library.locate_pins(location).select{ |pin| pin.name == params['label'] } + result.concat(library.locate_pins(location).select { |pin| pin.name == params['label'] }) end if params['data']['path'] result.concat library.path_pins(params['data']['path']) @@ -748,9 +748,12 @@ def generate_updater params params['contentChanges'].each do |recvd| chng = check_diff(params['textDocument']['uri'], recvd) changes.push Solargraph::Source::Change.new( - (chng['range'].nil? ? - nil : - Solargraph::Range.from_to(chng['range']['start']['line'], chng['range']['start']['character'], chng['range']['end']['line'], chng['range']['end']['character']) + (if chng['range'].nil? + nil + else + Solargraph::Range.from_to(chng['range']['start']['line'], chng['range']['start']['character'], + chng['range']['end']['line'], chng['range']['end']['character']) + end ), chng['text'] ) @@ -813,10 +816,10 @@ def dynamic_capability_options # documentSymbolProvider: true, # workspaceSymbolProvider: true, # workspace: { - # workspaceFolders: { - # supported: true, - # changeNotifications: true - # } + # workspaceFolders: { + # supported: true, + # changeNotifications: true + # } # } 'textDocument/definition' => { definitionProvider: true diff --git a/lib/solargraph/language_server/host/diagnoser.rb b/lib/solargraph/language_server/host/diagnoser.rb index e69ae16f9..8c259c131 100644 --- a/lib/solargraph/language_server/host/diagnoser.rb +++ b/lib/solargraph/language_server/host/diagnoser.rb @@ -56,7 +56,7 @@ def start # @return [void] def tick return if queue.empty? || host.synchronizing? - if !host.options['diagnostics'] + unless host.options['diagnostics'] mutex.synchronize { queue.clear } return end diff --git a/lib/solargraph/language_server/host/dispatch.rb b/lib/solargraph/language_server/host/dispatch.rb index 4cb135169..b3c78ffca 100644 --- a/lib/solargraph/language_server/host/dispatch.rb +++ b/lib/solargraph/language_server/host/dispatch.rb @@ -45,12 +45,11 @@ def update_libraries uri # @param uri [String] # @return [Library] def library_for uri - result = explicit_library_for(uri) || + explicit_library_for(uri) || implicit_library_for(uri) || generic_library_for(uri) # previous library for already call attach. avoid call twice # result.attach sources.find(uri) if sources.include?(uri) - result end # Find an explicit library match for the given URI. An explicit match diff --git a/lib/solargraph/language_server/host/message_worker.rb b/lib/solargraph/language_server/host/message_worker.rb index b0878b154..440e2c7cc 100644 --- a/lib/solargraph/language_server/host/message_worker.rb +++ b/lib/solargraph/language_server/host/message_worker.rb @@ -20,7 +20,7 @@ class MessageWorker ].freeze # @param host [Host] - def initialize(host) + def initialize host @host = host @mutex = Mutex.new @resource = ConditionVariable.new @@ -44,7 +44,7 @@ def stop # @param message [Hash] The message to handle. Will be forwarded to Host#receive # @return [void] - def queue(message) + def queue message @mutex.synchronize do messages.push(message) @resource.signal diff --git a/lib/solargraph/language_server/host/sources.rb b/lib/solargraph/language_server/host/sources.rb index 766ea634a..72a155d73 100644 --- a/lib/solargraph/language_server/host/sources.rb +++ b/lib/solargraph/language_server/host/sources.rb @@ -13,7 +13,7 @@ class Sources # @param uri [String] # @return [void] - def add_uri(uri) + def add_uri uri queue.push(uri) end diff --git a/lib/solargraph/language_server/message/client/register_capability.rb b/lib/solargraph/language_server/message/client/register_capability.rb index a9af8748e..67469def8 100644 --- a/lib/solargraph/language_server/message/client/register_capability.rb +++ b/lib/solargraph/language_server/message/client/register_capability.rb @@ -5,9 +5,7 @@ module LanguageServer module Message module Client class RegisterCapability < Solargraph::LanguageServer::Message::Base - def process - - end + def process; end end end end diff --git a/lib/solargraph/language_server/message/completion_item/resolve.rb b/lib/solargraph/language_server/message/completion_item/resolve.rb index 0e181460f..171ec6a6c 100644 --- a/lib/solargraph/language_server/message/completion_item/resolve.rb +++ b/lib/solargraph/language_server/message/completion_item/resolve.rb @@ -21,9 +21,9 @@ def merge pins docs = pins .reject { |pin| pin.documentation.empty? && pin.return_type.undefined? } result = params - .transform_keys(&:to_sym) - .merge(pins.first.resolve_completion_item) - .merge(documentation: markup_content(join_docs(docs))) + .transform_keys(&:to_sym) + .merge(pins.first.resolve_completion_item) + .merge(documentation: markup_content(join_docs(docs))) result[:detail] = pins.first.detail result end @@ -45,9 +45,7 @@ def join_docs pins last_link = nil pins.each do |pin| this_link = host.options['enablePages'] ? pin.link_documentation : pin.text_documentation - if this_link && this_link != last_link && this_link != 'undefined' - result.push this_link - end + result.push this_link if this_link && this_link != last_link && this_link != 'undefined' result.push pin.documentation unless result.last && result.last.end_with?(pin.documentation) last_link = this_link end 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 4a6a3df4d..8909406a4 100644 --- a/lib/solargraph/language_server/message/extended/check_gem_version.rb +++ b/lib/solargraph/language_server/message/extended/check_gem_version.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true - module Solargraph module LanguageServer module Message @@ -17,8 +16,8 @@ def self.fetcher # @param obj [Gem::SpecFetcher] # @return [Gem::SpecFetcher] - def self.fetcher= obj - @fetcher = obj + class << self + attr_writer :fetcher end GEM_ZERO = Gem::Version.new('0.0.0') @@ -41,11 +40,11 @@ def process ['Update now'] do |result| next unless result == 'Update now' cmd = if host.options['useBundler'] - 'bundle update solargraph' - else - 'gem update solargraph' - end - o, s = Open3.capture2(cmd) + 'bundle update solargraph' + else + 'gem update solargraph' + end + _, s = Open3.capture2(cmd) if s == 0 host.show_message 'Successfully updated the Solargraph gem.', LanguageServer::MessageTypes::INFO host.send_notification '$/solargraph/restart', {} @@ -62,9 +61,9 @@ def process host.show_message(error, MessageTypes::ERROR) if params['verbose'] end set_result({ - installed: current, - available: available - }) + installed: current, + available: available + }) end private diff --git a/lib/solargraph/language_server/message/extended/document_gems.rb b/lib/solargraph/language_server/message/extended/document_gems.rb index f1cfca0b8..403632390 100644 --- a/lib/solargraph/language_server/message/extended/document_gems.rb +++ b/lib/solargraph/language_server/message/extended/document_gems.rb @@ -13,16 +13,16 @@ class DocumentGems < Base def process cmd = [host.command_path, 'gems'] cmd.push '--rebuild' if params['rebuild'] - o, s = Open3.capture2(*cmd) - if s != 0 - host.show_message "An error occurred while building gem documentation.", LanguageServer::MessageTypes::ERROR + _, s = Open3.capture2(*cmd) + if s == 0 set_result({ - status: 'err' - }) + status: 'ok' + }) else + host.show_message 'An error occurred while building gem documentation.', LanguageServer::MessageTypes::ERROR set_result({ - status: 'ok' - }) + status: 'err' + }) end end end diff --git a/lib/solargraph/language_server/message/extended/download_core.rb b/lib/solargraph/language_server/message/extended/download_core.rb index 7310757a1..4e0775c14 100644 --- a/lib/solargraph/language_server/message/extended/download_core.rb +++ b/lib/solargraph/language_server/message/extended/download_core.rb @@ -10,7 +10,8 @@ module Extended # class DownloadCore < Base def process - host.show_message "Downloading cores is deprecated. Solargraph currently uses RBS for core and stdlib documentation", LanguageServer::MessageTypes::INFO + host.show_message 'Downloading cores is deprecated. Solargraph currently uses RBS for core and stdlib documentation', + LanguageServer::MessageTypes::INFO end end end diff --git a/lib/solargraph/language_server/message/extended/search.rb b/lib/solargraph/language_server/message/extended/search.rb index 1f09a8890..312c048bb 100644 --- a/lib/solargraph/language_server/message/extended/search.rb +++ b/lib/solargraph/language_server/message/extended/search.rb @@ -8,7 +8,7 @@ class Search < Base def process results = host.search(params['query']) page = Solargraph::Page.new(host.options['viewsPath']) - content = page.render('search', layout: true, locals: {query: params['query'], results: results}) + content = page.render('search', layout: true, locals: { query: params['query'], results: results }) set_result( content: content ) diff --git a/lib/solargraph/language_server/message/initialize.rb b/lib/solargraph/language_server/message/initialize.rb index 3f3e1338b..f0f54f22e 100644 --- a/lib/solargraph/language_server/message/initialize.rb +++ b/lib/solargraph/language_server/message/initialize.rb @@ -26,20 +26,27 @@ def process } } # FIXME: lsp default is utf-16, may have different position - result[:capabilities][:positionEncoding] = "utf-32" if params.dig("capabilities", "general", "positionEncodings")&.include?("utf-32") + result[:capabilities][:positionEncoding] = 'utf-32' if params.dig('capabilities', 'general', + 'positionEncodings')&.include?('utf-32') result[:capabilities].merge! static_completion unless dynamic_registration_for?('textDocument', 'completion') - result[:capabilities].merge! static_signature_help unless dynamic_registration_for?('textDocument', 'signatureHelp') + result[:capabilities].merge! static_signature_help unless dynamic_registration_for?('textDocument', + 'signatureHelp') # result[:capabilities].merge! static_on_type_formatting unless dynamic_registration_for?('textDocument', 'onTypeFormatting') result[:capabilities].merge! static_hover unless dynamic_registration_for?('textDocument', 'hover') - result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', 'formatting') - result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', 'documentSymbol') + result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', + 'formatting') + result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', + 'documentSymbol') result[:capabilities].merge! static_definitions unless dynamic_registration_for?('textDocument', 'definition') - result[:capabilities].merge! static_type_definitions unless dynamic_registration_for?('textDocument', 'typeDefinition') + result[:capabilities].merge! static_type_definitions unless dynamic_registration_for?('textDocument', + 'typeDefinition') result[:capabilities].merge! static_rename unless dynamic_registration_for?('textDocument', 'rename') result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references') result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol') - result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', 'foldingRange') - result[:capabilities].merge! static_highlights unless dynamic_registration_for?('textDocument', 'documentHighlight') + result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', + 'foldingRange') + result[:capabilities].merge! static_highlights unless dynamic_registration_for?('textDocument', + 'documentHighlight') # @todo Temporarily disabled # result[:capabilities].merge! static_code_action unless dynamic_registration_for?('textDocument', 'codeAction') set_result result @@ -71,7 +78,7 @@ def static_completion def static_code_action { codeActionProvider: true, - codeActionKinds: ["quickfix"] + codeActionKinds: ['quickfix'] } end @@ -144,11 +151,10 @@ def static_type_definitions # @return [Hash{Symbol => Hash{Symbol => Boolean}}] def static_rename { - renameProvider: {prepareProvider: true} + renameProvider: { prepareProvider: true } } end - # @return [Hash{Symbol => Boolean}] def static_references return {} unless host.options['references'] @@ -178,10 +184,10 @@ def static_highlights # enforce strict true/false-ness # @sg-ignore def dynamic_registration_for? section, capability - result = (params['capabilities'] && - params['capabilities'][section] && - params['capabilities'][section][capability] && - params['capabilities'][section][capability]['dynamicRegistration']) + result = params['capabilities'] && + params['capabilities'][section] && + params['capabilities'][section][capability] && + params['capabilities'][section][capability]['dynamicRegistration'] host.allow_registration("#{section}/#{capability}") if result result end diff --git a/lib/solargraph/language_server/message/text_document/completion.rb b/lib/solargraph/language_server/message/text_document/completion.rb index 550ae40ef..d2f352a3d 100644 --- a/lib/solargraph/language_server/message/text_document/completion.rb +++ b/lib/solargraph/language_server/message/text_document/completion.rb @@ -6,7 +6,7 @@ module Message module TextDocument class Completion < Base def process - return set_error(ErrorCodes::REQUEST_CANCELLED, "cancelled by so many request") if host.pending_completions? + return set_error(ErrorCodes::REQUEST_CANCELLED, 'cancelled by so many request') if host.pending_completions? line = params['position']['line'] col = params['position']['character'] @@ -19,12 +19,12 @@ def process completion.pins.each do |pin| idx += 1 if last_context != pin.context items.push pin.completion_item.merge({ - textEdit: { - range: completion.range.to_hash, - newText: pin.name.sub(/=$/, ' = ').sub(/:$/, ': ') - }, - sortText: "#{idx.to_s.rjust(4, '0')}#{pin.name}" - }) + textEdit: { + range: completion.range.to_hash, + newText: pin.name.sub(/=$/, ' = ').sub(/:$/, ': ') + }, + sortText: "#{idx.to_s.rjust(4, '0')}#{pin.name}" + }) items.last[:data][:uri] = params['textDocument']['uri'] last_context = pin.context end @@ -32,7 +32,7 @@ def process isIncomplete: false, items: items ) - rescue InvalidOffsetError => e + rescue InvalidOffsetError Logging.logger.info "Completion ignored invalid offset: #{params['textDocument']['uri']}, line #{line}, character #{col}" set_result empty_result end diff --git a/lib/solargraph/language_server/message/text_document/definition.rb b/lib/solargraph/language_server/message/text_document/definition.rb index 96c1e988a..f88583f48 100644 --- a/lib/solargraph/language_server/message/text_document/definition.rb +++ b/lib/solargraph/language_server/message/text_document/definition.rb @@ -28,7 +28,8 @@ def code_location def require_location # @todo Terrible hack lib = host.library_for(params['textDocument']['uri']) - rloc = Solargraph::Location.new(uri_to_file(params['textDocument']['uri']), Solargraph::Range.from_to(@line, @column, @line, @column)) + rloc = Solargraph::Location.new(uri_to_file(params['textDocument']['uri']), + Solargraph::Range.from_to(@line, @column, @line, @column)) dloc = lib.locate_ref(rloc) return nil if dloc.nil? [ diff --git a/lib/solargraph/language_server/message/text_document/document_highlight.rb b/lib/solargraph/language_server/message/text_document/document_highlight.rb index 2aded7641..a0541762a 100644 --- a/lib/solargraph/language_server/message/text_document/document_highlight.rb +++ b/lib/solargraph/language_server/message/text_document/document_highlight.rb @@ -3,7 +3,8 @@ module Solargraph::LanguageServer::Message::TextDocument class DocumentHighlight < Base def process - locs = host.references_from(params['textDocument']['uri'], params['position']['line'], params['position']['character'], strip: true, only: true) + locs = host.references_from(params['textDocument']['uri'], params['position']['line'], + params['position']['character'], strip: true, only: true) result = locs.map do |loc| { range: loc.range.to_hash, diff --git a/lib/solargraph/language_server/message/text_document/formatting.rb b/lib/solargraph/language_server/message/text_document/formatting.rb index 7010de741..c6cc3353a 100644 --- a/lib/solargraph/language_server/message/text_document/formatting.rb +++ b/lib/solargraph/language_server/message/text_document/formatting.rb @@ -43,7 +43,7 @@ def process # @param corrections [String] # @return [void] - def log_corrections(corrections) + def log_corrections corrections corrections = corrections&.strip return if corrections&.empty? @@ -56,7 +56,7 @@ def log_corrections(corrections) # @param file_uri [String] # @return [Hash{String => undefined}] - def config_for(file_uri) + def config_for file_uri conf = host.formatter_config(file_uri) return {} unless conf.is_a?(Hash) @@ -71,10 +71,10 @@ def cli_args file_uri, config args = [ config['cops'] == 'all' ? '-A' : '-a', '--cache', 'false', - '--format', formatter_class(config).name, + '--format', formatter_class(config).name ] - ['except', 'only'].each do |arg| + %w[except only].each do |arg| cops = cop_list(config[arg]) args += ["--#{arg}", cops] if cops end @@ -86,7 +86,7 @@ def cli_args file_uri, config # @param config [Hash{String => String}] # @sg-ignore # @return [Class] - def formatter_class(config) + def formatter_class config if self.class.const_defined?('BlankRubocopFormatter') # @sg-ignore BlankRubocopFormatter @@ -100,7 +100,7 @@ def formatter_class(config) # @param value [Array, String] # # @return [String, nil] - def cop_list(value) + def cop_list value # @type [String] # @sg-ignore Translate to something flow sensitive typing understands value = value.join(',') if value.respond_to?(:join) diff --git a/lib/solargraph/language_server/message/text_document/hover.rb b/lib/solargraph/language_server/message/text_document/hover.rb index 57a9161a3..379403258 100644 --- a/lib/solargraph/language_server/message/text_document/hover.rb +++ b/lib/solargraph/language_server/message/text_document/hover.rb @@ -15,9 +15,7 @@ def process suggestions.each do |pin| parts = [] this_link = host.options['enablePages'] ? pin.link_documentation : pin.text_documentation - if !this_link.nil? && this_link != last_link - parts.push this_link - end + parts.push this_link if !this_link.nil? && this_link != last_link parts.push "`#{pin.detail}`" unless pin.is_a?(Pin::Namespace) || pin.detail.nil? parts.push pin.documentation unless pin.documentation.nil? || pin.documentation.empty? unless parts.empty? @@ -43,8 +41,8 @@ def process # @return [Hash{Symbol => Hash{Symbol => String}}, nil] def contents_or_nil contents stripped = contents - .map(&:strip) - .reject { |c| c.empty? } + .map(&:strip) + .reject { |c| c.empty? } return nil if stripped.empty? { contents: { diff --git a/lib/solargraph/language_server/message/text_document/prepare_rename.rb b/lib/solargraph/language_server/message/text_document/prepare_rename.rb index 4a37410dd..770d126ad 100644 --- a/lib/solargraph/language_server/message/text_document/prepare_rename.rb +++ b/lib/solargraph/language_server/message/text_document/prepare_rename.rb @@ -5,7 +5,8 @@ class PrepareRename < Base def process line = params['position']['line'] col = params['position']['character'] - set_result host.sources.find(params['textDocument']['uri']).cursor_at(Solargraph::Position.new(line, col)).range.to_hash + set_result host.sources.find(params['textDocument']['uri']).cursor_at(Solargraph::Position.new(line, + col)).range.to_hash end end end diff --git a/lib/solargraph/language_server/message/text_document/references.rb b/lib/solargraph/language_server/message/text_document/references.rb index 2de266214..08ae35c9d 100644 --- a/lib/solargraph/language_server/message/text_document/references.rb +++ b/lib/solargraph/language_server/message/text_document/references.rb @@ -3,7 +3,8 @@ module Solargraph::LanguageServer::Message::TextDocument class References < Base def process - locs = host.references_from(params['textDocument']['uri'], params['position']['line'], params['position']['character']) + locs = host.references_from(params['textDocument']['uri'], params['position']['line'], + params['position']['character']) result = locs.map do |loc| { uri: file_to_uri(loc.filename), diff --git a/lib/solargraph/language_server/message/text_document/rename.rb b/lib/solargraph/language_server/message/text_document/rename.rb index 997e9595b..66bdce274 100644 --- a/lib/solargraph/language_server/message/text_document/rename.rb +++ b/lib/solargraph/language_server/message/text_document/rename.rb @@ -3,15 +3,16 @@ module Solargraph::LanguageServer::Message::TextDocument class Rename < Base def process - locs = host.references_from(params['textDocument']['uri'], params['position']['line'], params['position']['character'], strip: true) + locs = host.references_from(params['textDocument']['uri'], params['position']['line'], + params['position']['character'], strip: true) changes = {} locs.each do |loc| uri = file_to_uri(loc.filename) changes[uri] ||= [] changes[uri].push({ - range: loc.range.to_hash, - newText: params['newName'] - }) + range: loc.range.to_hash, + newText: params['newName'] + }) end set_result changes: changes end diff --git a/lib/solargraph/language_server/message/text_document/signature_help.rb b/lib/solargraph/language_server/message/text_document/signature_help.rb index a56b56edd..5a460b205 100644 --- a/lib/solargraph/language_server/message/text_document/signature_help.rb +++ b/lib/solargraph/language_server/message/text_document/signature_help.rb @@ -10,8 +10,8 @@ def process col = params['position']['character'] suggestions = host.signatures_at(params['textDocument']['uri'], line, col) set_result({ - signatures: suggestions.flat_map { |pin| pin.signature_help } - }) + signatures: suggestions.flat_map { |pin| pin.signature_help } + }) rescue FileNotFoundError => e Logging.logger.warn "[#{e.class}] #{e.message}" # @sg-ignore Need to add nil check here diff --git a/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb b/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb index 32b2c1c50..133cf883d 100644 --- a/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +++ b/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb @@ -26,12 +26,13 @@ def process to_delete << change['uri'] need_catalog = true else - set_error Solargraph::LanguageServer::ErrorCodes::INVALID_PARAMS, "Unknown change type ##{change['type']} for #{uri_to_file(change['uri'])}" + set_error Solargraph::LanguageServer::ErrorCodes::INVALID_PARAMS, + "Unknown change type ##{change['type']} for #{uri_to_file(change['uri'])}" end end - host.create *to_create - host.delete *to_delete + host.create(*to_create) + host.delete(*to_delete) # Force host to catalog libraries after file changes (see castwide/solargraph#139) host.catalog if need_catalog 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 e1e83fc1e..8f93c02e7 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 @@ -18,7 +18,7 @@ def add_folders # @return [void] def remove_folders return unless params['event'] && params['event']['removed'] - params['event']['removed'].each do |folder| + params['event']['removed'].each do |_folder| host.remove_folders params['event']['removed'] end end diff --git a/lib/solargraph/language_server/transport/data_reader.rb b/lib/solargraph/language_server/transport/data_reader.rb index 3fc3ed311..c24118f03 100644 --- a/lib/solargraph/language_server/transport/data_reader.rb +++ b/lib/solargraph/language_server/transport/data_reader.rb @@ -33,8 +33,8 @@ def receive data @buffer.concat char if @in_header prepare_to_parse_message if @buffer.end_with?("\r\n\r\n") - else - parse_message_from_buffer if @buffer.bytesize == @content_length + elsif @buffer.bytesize == @content_length + parse_message_from_buffer end end end @@ -56,17 +56,15 @@ def prepare_to_parse_message # @return [void] def parse_message_from_buffer - begin - msg = JSON.parse(@buffer) - @message_handler.call msg unless @message_handler.nil? - rescue JSON::ParserError => e - Solargraph::Logging.logger.warn "Failed to parse request: #{e.message}" - Solargraph::Logging.logger.debug "Buffer: #{@buffer}" - ensure - @buffer.clear - @in_header = true - @content_length = 0 - end + msg = JSON.parse(@buffer) + @message_handler.call msg unless @message_handler.nil? + rescue JSON::ParserError => e + Solargraph::Logging.logger.warn "Failed to parse request: #{e.message}" + Solargraph::Logging.logger.debug "Buffer: #{@buffer}" + ensure + @buffer.clear + @in_header = true + @content_length = 0 end end end diff --git a/lib/solargraph/language_server/uri_helpers.rb b/lib/solargraph/language_server/uri_helpers.rb index c7e55afb8..fcf2e0dcb 100644 --- a/lib/solargraph/language_server/uri_helpers.rb +++ b/lib/solargraph/language_server/uri_helpers.rb @@ -14,7 +14,7 @@ module UriHelpers # @param uri [String] # @return [String] def uri_to_file uri - decode(uri).sub(/^file\:(?:\/\/)?/, '').sub(/^\/([a-z]\:)/i, '\1') + decode(uri).sub(%r{^file:(?://)?}, '').sub(%r{^/([a-z]:)}i, '\1') end # Convert a file path to a URI. @@ -22,7 +22,7 @@ def uri_to_file uri # @param file [String] # @return [String] def file_to_uri file - "file://#{encode(file.gsub(/^([a-z]\:)/i, '/\1'))}" + "file://#{encode(file.gsub(/^([a-z]:)/i, '/\1'))}" end # Encode text to be used as a URI path component in LSP. diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 019d8d91c..85d20a465 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -127,8 +127,8 @@ def create filename, text # @return [Boolean] True if at least one file was added to the workspace. def create_from_disk *filenames sources = filenames - .reject { |filename| File.directory?(filename) || !File.exist?(filename) } - .map { |filename| Solargraph::Source.load_string(File.read(filename), filename) } + .reject { |filename| File.directory?(filename) || !File.exist?(filename) } + .map { |filename| Solargraph::Source.load_string(File.read(filename), filename) } result = workspace.merge(*sources) sources.each { |source| maybe_map source } result @@ -195,7 +195,7 @@ def definitions_at filename, line, column offset = Solargraph::Position.to_offset(source.code, Solargraph::Position.new(line, column)) # @sg-ignore Need to add nil check here # @type [MatchData, nil] - lft = source.code[0..offset-1].match(/\[[a-z0-9_:<, ]*?([a-z0-9_:]*)\z/i) + lft = source.code[0..offset - 1].match(/\[[a-z0-9_:<, ]*?([a-z0-9_:]*)\z/i) # @sg-ignore Need to add nil check here # @type [MatchData, nil] rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i) @@ -264,10 +264,10 @@ def references_from filename, line, column, strip: false, only: false return [] unless pin result = [] files = if only - [api_map.source_map(filename)] - else - (workspace.sources + (@current ? [@current] : [])) - end + [api_map.source_map(filename)] + else + (workspace.sources + (@current ? [@current] : [])) + end files.uniq(&:filename).each do |source| found = source.references(pin.name) found.select! do |loc| @@ -329,8 +329,8 @@ def locate_ref location end workspace.require_paths.each do |path| full = File.join path, pin.name - return_if_match.(full) - return_if_match.(full << ".rb") + return_if_match.call(full) + return_if_match.call(full << '.rb') end nil rescue FileNotFoundError @@ -546,12 +546,12 @@ def find_external_requires source_map new_set = source_map.requires.map(&:name).to_set # return if new_set == source_map_external_require_hash[source_map.filename] _filenames = nil - filenames = ->{ _filenames ||= workspace.filenames.to_set } + filenames = -> { _filenames ||= workspace.filenames.to_set } # @sg-ignore Need to add nil check here source_map_external_require_hash[source_map.filename] = new_set.reject do |path| workspace.require_paths.any? do |base| full = File.join(base, path) - filenames[].include?(full) or filenames[].include?(full << ".rb") + filenames[].include?(full) or filenames[].include?(full << '.rb') end end @external_requires = nil @@ -585,12 +585,9 @@ def read filename # @param error [FileNotFoundError] # @return [nil] def handle_file_not_found filename, error - if workspace.source(filename) - Solargraph.logger.debug "#{filename} is not cataloged in the ApiMap" - nil - else - raise error - end + raise error unless workspace.source(filename) + Solargraph.logger.debug "#{filename} is not cataloged in the ApiMap" + nil end # @param source [Source, nil] @@ -679,12 +676,12 @@ def report_cache_progress gem_name, pending finished = @total - pending # @sg-ignore flow sensitive typing needs better handling of ||= on lvars pct = if @total.zero? - 0 - else - # @sg-ignore flow sensitive typing needs better handling of ||= on lvars - ((finished.to_f / @total.to_f) * 100).to_i - end - message = "#{gem_name}#{pending > 0 ? " (+#{pending})" : ''}" + 0 + else + # @sg-ignore flow sensitive typing needs better handling of ||= on lvars + ((finished.to_f / @total.to_f) * 100).to_i + end + message = "#{gem_name}#{" (+#{pending})" if pending > 0}" # " if @cache_progress @cache_progress.report(message, pct) diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index d51458470..a7eaf9e6b 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -17,7 +17,7 @@ class Location # @param filename [String, nil] # @param range [Solargraph::Range] def initialize filename, range - raise "Use nil to represent no-file" if filename&.empty? + raise 'Use nil to represent no-file' if filename&.empty? @filename = filename @range = range @@ -28,7 +28,7 @@ def initialize filename, range end # @param other [self] - def <=>(other) + def <=> other return nil unless other.is_a?(Location) if filename == other.filename range <=> other.range @@ -60,14 +60,14 @@ def to_hash # @param node [Parser::AST::Node, nil] # @return [Location, nil] - def self.from_node(node) + def self.from_node node return nil if node.nil? || node.loc.nil? filename = node.loc.expression.source_buffer.name # @sg-ignore flow sensitive typing needs to create separate ranges for postfix if filename = nil if filename.empty? range = Range.from_node(node) # @sg-ignore Need to add nil check here - self.new(filename, range) + new(filename, range) end # @param other [BasicObject] diff --git a/lib/solargraph/logging.rb b/lib/solargraph/logging.rb index 30bdc06c2..60ca51f17 100644 --- a/lib/solargraph/logging.rb +++ b/lib/solargraph/logging.rb @@ -23,7 +23,7 @@ module Logging end @@logger = Logger.new(STDERR, level: level) # @sg-ignore Fix cvar issue - @@logger.formatter = proc do |severity, datetime, progname, msg| + @@logger.formatter = proc do |severity, _datetime, _progname, msg| "[#{severity}] #{msg}\n" end diff --git a/lib/solargraph/parser/comment_ripper.rb b/lib/solargraph/parser/comment_ripper.rb index 8bfe166f5..41e68e744 100644 --- a/lib/solargraph/parser/comment_ripper.rb +++ b/lib/solargraph/parser/comment_ripper.rb @@ -26,19 +26,25 @@ def on_comment *args # @sg-ignore Need to add nil check here if @buffer_lines[result[2][0]][0..result[2][1]].strip =~ /^#/ chomped = result[1].chomp - if result[2][0] == 0 && chomped.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '').match(/^#\s*frozen_string_literal:/) + if result[2][0] == 0 && chomped.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, + replace: '').match(/^#\s*frozen_string_literal:/) chomped = '#' end - @comments[result[2][0]] = Snippet.new(Range.from_to(result[2][0], result[2][1], result[2][0], result[2][1] + chomped.length), chomped) + @comments[result[2][0]] = + Snippet.new(Range.from_to(result[2][0], result[2][1], result[2][0], result[2][1] + chomped.length), chomped) end result end # @param result [Array(Symbol, String, Array([Integer, nil], [Integer, nil]))] # @return [void] - def create_snippet(result) + def create_snippet result chomped = result[1].chomp - @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) + @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 diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index e73a46cb2..26fb646b4 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -7,7 +7,7 @@ class FlowSensitiveTyping # @param ivars [Array] # @param enclosing_breakable_pin [Solargraph::Pin::Breakable, nil] # @param enclosing_compound_statement_pin [Solargraph::Pin::CompoundStatement, nil] - def initialize(locals, ivars, enclosing_breakable_pin, enclosing_compound_statement_pin) + def initialize locals, ivars, enclosing_breakable_pin, enclosing_compound_statement_pin @locals = locals @ivars = ivars @enclosing_breakable_pin = enclosing_breakable_pin @@ -19,7 +19,7 @@ def initialize(locals, ivars, enclosing_breakable_pin, enclosing_compound_statem # @param false_ranges [Array] # # @return [void] - def process_and(and_node, true_ranges = [], false_ranges = []) + def process_and and_node, true_ranges = [], false_ranges = [] return unless and_node.type == :and # @type [Parser::AST::Node] @@ -45,7 +45,7 @@ def process_and(and_node, true_ranges = [], false_ranges = []) # @param false_ranges [Array] # # @return [void] - def process_or(or_node, true_ranges = [], false_ranges = []) + def process_or or_node, true_ranges = [], false_ranges = [] return unless or_node.type == :or # @type [Parser::AST::Node] @@ -74,7 +74,7 @@ def process_or(or_node, true_ranges = [], false_ranges = []) # @param false_presences [Array] # # @return [void] - def process_calls(node, true_presences, false_presences) + def process_calls node, true_presences, false_presences return unless node.type == :send process_isa(node, true_presences, false_presences) @@ -87,7 +87,7 @@ def process_calls(node, true_presences, false_presences) # @param false_ranges [Array] # # @return [void] - def process_if(if_node, true_ranges = [], false_ranges = []) + def process_if if_node, true_ranges = [], false_ranges = [] return if if_node.type != :if # @@ -111,13 +111,9 @@ def process_if(if_node, true_ranges = [], false_ranges = []) rest_of_breakable_body = Range.new(get_node_end_position(if_node), get_node_end_position(enclosing_breakable_pin.node)) - if always_breaks?(then_clause) - false_ranges << rest_of_breakable_body - end + false_ranges << rest_of_breakable_body if always_breaks?(then_clause) - if always_breaks?(else_clause) - true_ranges << rest_of_breakable_body - end + true_ranges << rest_of_breakable_body if always_breaks?(else_clause) end unless enclosing_compound_statement_pin.node.nil? @@ -129,13 +125,9 @@ def process_if(if_node, true_ranges = [], false_ranges = []) # statement, we can assume things about the rest of the # compound statement # - if always_leaves_compound_statement?(then_clause) - false_ranges << rest_of_returnable_body - end + false_ranges << rest_of_returnable_body if always_leaves_compound_statement?(then_clause) - if always_leaves_compound_statement?(else_clause) - true_ranges << rest_of_returnable_body - end + true_ranges << rest_of_returnable_body if always_leaves_compound_statement?(else_clause) end unless then_clause.nil? @@ -166,7 +158,7 @@ def process_if(if_node, true_ranges = [], false_ranges = []) # @param false_ranges [Array] # # @return [void] - def process_while(while_node, true_ranges = [], false_ranges = []) + def process_while while_node, true_ranges = [], false_ranges = [] return if while_node.type != :while # @@ -210,7 +202,7 @@ class << self # @param downcast_not_type [ComplexType, nil] # # @return [void] - def add_downcast_var(pin, presence:, downcast_type:, downcast_not_type:) + def add_downcast_var pin, presence:, downcast_type:, downcast_not_type: new_pin = pin.downcast(exclude_return_type: downcast_not_type, intersection_return_type: downcast_type, source: :flow_sensitive_typing, @@ -228,7 +220,7 @@ def add_downcast_var(pin, presence:, downcast_type:, downcast_not_type:) # @param presences [Array] # # @return [void] - def process_facts(facts_by_pin, presences) + def process_facts facts_by_pin, presences # # Add specialized vars for the rest of the block # @@ -251,7 +243,7 @@ def process_facts(facts_by_pin, presences) # @param false_ranges [Array] # # @return [void] - def process_expression(expression_node, true_ranges, false_ranges) + def process_expression expression_node, true_ranges, false_ranges process_calls(expression_node, true_ranges, false_ranges) process_and(expression_node, true_ranges, false_ranges) process_or(expression_node, true_ranges, false_ranges) @@ -263,7 +255,7 @@ def process_expression(expression_node, true_ranges, false_ranges) # @return [Array(String, String), nil] Tuple of rgument to # function, then receiver of function if it's a variable, # otherwise nil if no simple variable receiver - def parse_call(call_node, method_name) + def parse_call call_node, method_name return unless call_node&.type == :send && call_node.children[1] == method_name # Check if conditional node follows this pattern: # s(:send, @@ -282,7 +274,7 @@ def parse_call(call_node, method_name) # or like this: # (lvar :repr) # @sg-ignore Need to look at Tuple#include? handling - variable_name = call_receiver.children[0].to_s if [:lvar, :ivar].include?(call_receiver&.type) + variable_name = call_receiver.children[0].to_s if %i[lvar ivar].include?(call_receiver&.type) return unless variable_name [call_arg, variable_name] @@ -290,7 +282,7 @@ def parse_call(call_node, method_name) # @param isa_node [Parser::AST::Node] # @return [Array(String, String), nil] - def parse_isa(isa_node) + def parse_isa isa_node call_type_name, variable_name = parse_call(isa_node, :is_a?) return unless call_type_name @@ -304,7 +296,7 @@ def parse_isa(isa_node) # @sg-ignore Solargraph::Parser::FlowSensitiveTyping#find_var # return type could not be inferred # @return [Solargraph::Pin::LocalVariable, Solargraph::Pin::InstanceVariable, nil] - def find_var(variable_name, position) + def find_var variable_name, position if variable_name.start_with?('@') # @sg-ignore flow sensitive typing needs to handle attrs ivars.find { |ivar| ivar.name == variable_name && (!ivar.presence || ivar.presence.include?(position)) } @@ -319,7 +311,7 @@ def find_var(variable_name, position) # @param false_presences [Array] # # @return [void] - def process_isa(isa_node, true_presences, false_presences) + def process_isa isa_node, true_presences, false_presences isa_type_name, variable_name = parse_isa(isa_node) return if variable_name.nil? || variable_name.empty? # @sg-ignore Need to add nil check here @@ -343,7 +335,7 @@ def process_isa(isa_node, true_presences, false_presences) # @param nilp_node [Parser::AST::Node] # @return [Array(String, String), nil] - def parse_nilp(nilp_node) + def parse_nilp nilp_node parse_call(nilp_node, :nil?) end @@ -352,7 +344,7 @@ def parse_nilp(nilp_node) # @param false_presences [Array] # # @return [void] - def process_nilp(nilp_node, true_presences, false_presences) + def process_nilp nilp_node, true_presences, false_presences nilp_arg, variable_name = parse_nilp(nilp_node) return if variable_name.nil? || variable_name.empty? # if .nil? got an argument, move on, this isn't the situation @@ -380,7 +372,7 @@ def process_nilp(nilp_node, true_presences, false_presences) # @param bang_node [Parser::AST::Node] # @return [Array(String, String), nil] - def parse_bang(bang_node) + def parse_bang bang_node parse_call(bang_node, :!) end @@ -389,7 +381,7 @@ def parse_bang(bang_node) # @param false_presences [Array] # # @return [void] - def process_bang(bang_node, true_presences, false_presences) + def process_bang bang_node, true_presences, false_presences # pry(main)> require 'parser/current'; Parser::CurrentRuby.parse("!2") # => s(:send, # s(:int, 2), :!) @@ -405,7 +397,7 @@ def process_bang(bang_node, true_presences, false_presences) # @param var_node [Parser::AST::Node] # # @return [String, nil] Variable name referenced - def parse_variable(var_node) + def parse_variable var_node return if var_node.children.length != 1 var_node.children[0]&.to_s @@ -415,8 +407,8 @@ def parse_variable(var_node) # @param node [Parser::AST::Node] # @param true_presences [Array] # @param false_presences [Array] - def process_variable(node, true_presences, false_presences) - return unless [:lvar, :ivar, :cvar, :gvar].include?(node.type) + def process_variable node, true_presences, false_presences + return unless %i[lvar ivar cvar gvar].include?(node.type) variable_name = parse_variable(node) return if variable_name.nil? @@ -443,7 +435,7 @@ def process_variable(node, true_presences, false_presences) # @param node [Parser::AST::Node] # # @return [String, nil] - def type_name(node) + def type_name node # e.g., # s(:const, nil, :Baz) return unless node&.type == :const @@ -462,22 +454,18 @@ def type_name(node) # @param clause_node [Parser::AST::Node, nil] # @sg-ignore need boolish support for ? methods - def always_breaks?(clause_node) + def always_breaks? clause_node clause_node&.type == :break end # @param clause_node [Parser::AST::Node, nil] - def always_leaves_compound_statement?(clause_node) + def always_leaves_compound_statement? clause_node # https://docs.ruby-lang.org/en/2.2.0/keywords_rdoc.html # @sg-ignore Need to look at Tuple#include? handling - [:return, :raise, :next, :redo, :retry].include?(clause_node&.type) + %i[return raise next redo retry].include?(clause_node&.type) end - attr_reader :locals - - attr_reader :ivars - - attr_reader :enclosing_breakable_pin, :enclosing_compound_statement_pin + attr_reader :locals, :ivars, :enclosing_breakable_pin, :enclosing_compound_statement_pin end end end diff --git a/lib/solargraph/parser/node_processor.rb b/lib/solargraph/parser/node_processor.rb index a43fb326c..d3579df80 100644 --- a/lib/solargraph/parser/node_processor.rb +++ b/lib/solargraph/parser/node_processor.rb @@ -43,7 +43,7 @@ def self.process node, region = Region.new, pins = [], locals = [], ivars = [] pins.push Pin::Namespace.new( location: region.source.location, name: '', - source: :parser, + source: :parser ) end return [pins, locals, ivars] unless Parser.is_ast_node?(node) diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index e11078e07..48fd070f6 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -58,13 +58,13 @@ def position # @sg-ignore downcast output of Enumerable#select # @return [Solargraph::Pin::Breakable, nil] def enclosing_breakable_pin - pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location&.range&.contain?(position)}.last + pins.select { |pin| pin.is_a?(Pin::Breakable) && pin.location&.range&.contain?(position) }.last end # @todo downcast output of Enumerable#select # @return [Solargraph::Pin::CompoundStatement, nil] def enclosing_compound_statement_pin - pins.select{|pin| pin.is_a?(Pin::CompoundStatement) && pin.location&.range&.contain?(position)}.last + pins.select { |pin| pin.is_a?(Pin::CompoundStatement) && pin.location&.range&.contain?(position) }.last end # @param subregion [Region] @@ -80,14 +80,14 @@ def process_children subregion = region # @param node [Parser::AST::Node] # @return [Solargraph::Location] - def get_node_location(node) + def get_node_location node range = Parser.node_range(node) Location.new(region.filename, range) end # @param node [Parser::AST::Node] # @return [String, nil] - def comments_for(node) + def comments_for node region.source.comments_for(node) end diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index 3f70d14df..dfaf7925a 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -54,7 +54,7 @@ def map source # @param name [String] # @return [Array] def references source, name - if name.end_with?("=") + if name.end_with?('=') reg = /#{Regexp.escape name[0..-2]}\s*=/ # @param code [String] # @param offset [Integer] @@ -98,17 +98,17 @@ def inner_node_references name, top # @return [Source::Chain] def chain *args - NodeChainer.chain *args + NodeChainer.chain(*args) end # @return [Source::Chain] def chain_string *args - NodeChainer.load_string *args + NodeChainer.load_string(*args) end # @return [Array(Array, Array)] def process_node *args - Solargraph::Parser::NodeProcessor.process *args + Solargraph::Parser::NodeProcessor.process(*args) end # @param node [Parser::AST::Node, nil] diff --git a/lib/solargraph/parser/parser_gem/flawed_builder.rb b/lib/solargraph/parser/parser_gem/flawed_builder.rb index acf665e16..341042f22 100644 --- a/lib/solargraph/parser/parser_gem/flawed_builder.rb +++ b/lib/solargraph/parser/parser_gem/flawed_builder.rb @@ -10,7 +10,7 @@ class FlawedBuilder < ::Parser::Builders::Default # @param token [::Parser::AST::Node] # @return [String] # @sg-ignore - def string_value(token) + def string_value token value(token) end end diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index bc04c2855..5803424d0 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -22,7 +22,7 @@ def initialize node, filename = nil, parent = nil # @return [Source::Chain] def chain links = generate_links(@node) - Chain.new(links, @node, (Parser.is_ast_node?(@node) && @node.type == :splat)) + Chain.new(links, @node, Parser.is_ast_node?(@node) && @node.type == :splat) end class << self @@ -39,7 +39,7 @@ def chain node, filename = nil, parent = nil # @param starting_line [Integer] # # @return [Source::Chain] - def load_string(code, filename, starting_line) + def load_string code, filename, starting_line node = Parser.parse(code.sub(/\.$/, ''), filename, starting_line) chain = NodeChainer.new(node).chain chain.links.push(Chain::Link.new) if code.end_with?('.') @@ -94,13 +94,13 @@ def generate_links n elsif n.type == :const const = unpack_name(n) result.push Chain::Constant.new(const) - elsif [:lvar, :lvasgn].include?(n.type) + elsif %i[lvar lvasgn].include?(n.type) result.push Chain::Call.new(n.children[0].to_s, Location.from_node(n)) - elsif [:ivar, :ivasgn].include?(n.type) + elsif %i[ivar ivasgn].include?(n.type) result.push Chain::InstanceVariable.new(n.children[0].to_s, n, Location.from_node(n)) - elsif [:cvar, :cvasgn].include?(n.type) + elsif %i[cvar cvasgn].include?(n.type) result.push Chain::ClassVariable.new(n.children[0].to_s) - elsif [:gvar, :gvasgn].include?(n.type) + elsif %i[gvar gvasgn].include?(n.type) result.push Chain::GlobalVariable.new(n.children[0].to_s) elsif n.type == :or_asgn # @bar ||= 123 translates to: @@ -113,13 +113,14 @@ def generate_links n or_link = Chain::Or.new([lhs_chain, rhs_chain]) # this is just for a call chain, so we don't need to record the assignment result.push(or_link) - elsif [:class, :module, :def, :defs].include?(n.type) + elsif %i[class module def defs].include?(n.type) # @todo Undefined or what? result.push Chain::UNDEFINED_CALL elsif n.type == :and result.concat generate_links(n.children.last) elsif n.type == :or - result.push Chain::Or.new([NodeChainer.chain(n.children[0], @filename), NodeChainer.chain(n.children[1], @filename, n)]) + result.push Chain::Or.new([NodeChainer.chain(n.children[0], @filename), + NodeChainer.chain(n.children[1], @filename, n)]) elsif n.type == :if then_clause = if n.children[1] NodeChainer.chain(n.children[1], @filename, n) @@ -132,7 +133,7 @@ def generate_links n Source::Chain.new([Source::Chain::Literal.new('nil', nil)], n) end result.push Chain::If.new([then_clause, else_clause]) - elsif [:begin, :kwbegin].include?(n.type) + elsif %i[begin kwbegin].include?(n.type) result.concat generate_links(n.children.last) elsif n.type == :block_pass block_variable_name_node = n.children[0] @@ -140,12 +141,10 @@ def generate_links n # anonymous block forwarding (e.g., "&") # added in Ruby 3.1 - https://bugs.ruby-lang.org/issues/11256 result.push Chain::BlockVariable.new(nil) + elsif block_variable_name_node.type == :sym + result.push Chain::BlockSymbol.new("#{block_variable_name_node.children[0]}") else - if block_variable_name_node.type == :sym - result.push Chain::BlockSymbol.new("#{block_variable_name_node.children[0].to_s}") - else - result.push Chain::BlockVariable.new("&#{block_variable_name_node.children[0].to_s}") - end + result.push Chain::BlockVariable.new("&#{block_variable_name_node.children[0]}") end elsif n.type == :hash result.push Chain::Hash.new('::Hash', n, hash_is_splatted?(n)) @@ -154,7 +153,7 @@ def generate_links n result.push Source::Chain::Array.new(chained_children, n) else lit = infer_literal_node_type(n) - result.push (lit ? Chain::Literal.new(lit, n) : Chain::Link.new) + result.push(lit ? Chain::Literal.new(lit, n) : Chain::Link.new) end result end @@ -163,7 +162,9 @@ def generate_links n def hash_is_splatted? node return false unless Parser.is_ast_node?(node) && node.type == :hash return false unless Parser.is_ast_node?(node.children.last) && node.children.last.type == :kwsplat - return false if Parser.is_ast_node?(node.children.last.children[0]) && node.children.last.children[0].type == :hash + if Parser.is_ast_node?(node.children.last.children[0]) && node.children.last.children[0].type == :hash + return false + end true end diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index f8859de76..69249a94e 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -12,17 +12,17 @@ module NodeMethods # @param node [Parser::AST::Node] # @return [String] - def unpack_name(node) - pack_name(node).join("::") + def unpack_name node + pack_name(node).join('::') end # @param node [Parser::AST::Node] # @return [Array] - def pack_name(node) + def pack_name node # @type [Array] parts = [] if node.is_a?(AST::Node) - node.children.each { |n| + node.children.each do |n| if n.is_a?(AST::Node) if n.type == :cbase parts = [''] + pack_name(n) @@ -32,7 +32,7 @@ def pack_name(node) else parts.push n unless n.nil? end - } + end end parts end @@ -41,7 +41,7 @@ def pack_name(node) # @return [String, nil] def infer_literal_node_type node return nil unless node.is_a?(AST::Node) - if node.type == :str || node.type == :dstr + if %i[str dstr].include?(node.type) return '::String' elsif node.type == :array return '::Array' @@ -53,30 +53,30 @@ def infer_literal_node_type node return '::Integer' elsif node.type == :float return '::Float' - elsif node.type == :sym || node.type == :dsym + elsif %i[sym dsym].include?(node.type) return '::Symbol' elsif node.type == :regexp return '::Regexp' elsif node.type == :irange return '::Range' - elsif node.type == :true || node.type == :false + elsif %i[true false].include?(node.type) return '::Boolean' # @todo Support `nil` keyword in types - # elsif node.type == :nil - # return 'NilClass' + # elsif node.type == :nil + # return 'NilClass' end nil end # @param node [Parser::AST::Node] # @return [Position] - def get_node_start_position(node) + def get_node_start_position node Position.new(node.loc.line, node.loc.column) end # @param node [Parser::AST::Node] # @return [Position] - def get_node_end_position(node) + def get_node_end_position node Position.new(node.loc.last_line, node.loc.last_column) end @@ -86,19 +86,15 @@ def get_node_end_position(node) # @return [String] def drill_signature node, signature return signature unless node.is_a?(AST::Node) - if node.type == :const or node.type == :cbase - unless node.children[0].nil? - signature += drill_signature(node.children[0], signature) - end + if %i[const cbase].include?(node.type) + signature += drill_signature(node.children[0], signature) unless node.children[0].nil? signature += '::' unless signature.empty? signature += node.children[1].to_s - elsif node.type == :lvar or node.type == :ivar or node.type == :cvar + elsif %i[lvar ivar cvar].include?(node.type) signature += '.' unless signature.empty? signature += node.children[0].to_s elsif node.type == :send - unless node.children[0].nil? - signature += drill_signature(node.children[0], signature) - end + signature += drill_signature(node.children[0], signature) unless node.children[0].nil? signature += '.' unless signature.empty? signature += node.children[1].to_s end @@ -112,7 +108,9 @@ def convert_hash node # @sg-ignore Translate to something flow sensitive typing understands return convert_hash(node.children[0]) if node.type == :kwsplat # @sg-ignore Translate to something flow sensitive typing understands - return convert_hash(node.children[0]) if Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat + if Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat + return convert_hash(node.children[0]) + end # @sg-ignore Translate to something flow sensitive typing understands return {} unless node.type == :hash result = {} @@ -151,7 +149,7 @@ def splatted_call? node end # @param nodes [Enumerable] - def any_splatted_call?(nodes) + def any_splatted_call? nodes nodes.any? { |n| splatted_call?(n) } end @@ -174,7 +172,7 @@ def call_nodes_from node result.concat call_nodes_from(node.children.first) # @sg-ignore Need to add nil check here node.children[2..-1].each { |child| result.concat call_nodes_from(child) } - elsif [:super, :zsuper].include?(node.type) + elsif %i[super zsuper].include?(node.type) result.push node node.children.each { |child| result.concat call_nodes_from(child) } else @@ -205,29 +203,31 @@ def returns_from_method_body node # @return [Array] low-level value nodes in # value position. Does not include explicit return # statements - def value_position_nodes_only(node) + def value_position_nodes_only node DeepInference.value_position_nodes_only(node).map { |n| n || NIL_NODE } end # @param cursor [Solargraph::Source::Cursor] # @return [Parser::AST::Node, nil] def find_recipient_node cursor - return repaired_find_recipient_node(cursor) if cursor.source.repaired? && cursor.source.code[cursor.offset - 1] == '(' + if cursor.source.repaired? && cursor.source.code[cursor.offset - 1] == '(' + return repaired_find_recipient_node(cursor) + end source = cursor.source position = cursor.position offset = cursor.offset tree = if source.synchronized? - # @sg-ignore Need to add nil check here - match = source.code[0..offset-1].match(/,\s*\z/) - if match - # @sg-ignore Need to add nil check here - source.tree_at(position.line, position.column - match[0].length) - else - source.tree_at(position.line, position.column) - end - else - source.tree_at(position.line, position.column - 1) - end + # @sg-ignore Need to add nil check here + match = source.code[0..offset - 1].match(/,\s*\z/) + if match + # @sg-ignore Need to add nil check here + source.tree_at(position.line, position.column - match[0].length) + else + source.tree_at(position.line, position.column) + end + else + source.tree_at(position.line, position.column - 1) + end # @type [AST::Node, nil] prev = nil tree.each do |node| @@ -237,12 +237,10 @@ def find_recipient_node cursor if !args.empty? # @sg-ignore Need to add nil check here return node if prev && args.include?(prev) - else - 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/ - end + elsif source.synchronized? + return node if source.code[0..offset - 1] =~ /\(\s*\z/ && source.code[offset..-1] =~ /^\s*\)/ + elsif source.code[0..offset - 1] =~ /\([^(]*\z/ + return node end end prev = node @@ -255,7 +253,7 @@ def find_recipient_node cursor def repaired_find_recipient_node cursor cursor = cursor.source.cursor_at([cursor.position.line, cursor.position.column - 1]) node = cursor.source.tree_at(cursor.position.line, cursor.position.column).first - return node if node && node.type == :send + node if node && node.type == :send end # @@ -312,11 +310,11 @@ def repaired_find_recipient_node cursor # statements in value positions. module DeepInference class << self - CONDITIONAL_ALL_BUT_FIRST = [:if, :unless] + CONDITIONAL_ALL_BUT_FIRST = %i[if unless] ONLY_ONE_CHILD = [:return] FIRST_TWO_CHILDREN = [:rescue] - COMPOUND_STATEMENTS = [:begin, :kwbegin] - SKIPPABLE = [:def, :defs, :class, :sclass, :module] + COMPOUND_STATEMENTS = %i[begin kwbegin] + SKIPPABLE = %i[def defs class sclass module] FUNCTION_VALUE = [:block] CASE_STATEMENT = [:case] @@ -333,7 +331,7 @@ def from_method_body node # @return [Array] low-level value nodes in # value position. Does not include explicit return # statements - def value_position_nodes_only(node) + def value_position_nodes_only node from_value_position_statement(node, include_explicit_returns: false) end @@ -367,7 +365,9 @@ def from_value_position_statement node, include_explicit_returns: true # @todo any explicit returns actually return from # scope in which the proc is run. This asssumes # that the function is executed here. - result.concat explicit_return_values_from_compound_statement(node.children[2]) if include_explicit_returns + if include_explicit_returns + result.concat explicit_return_values_from_compound_statement(node.children[2]) + end elsif CASE_STATEMENT.include?(node.type) # @sg-ignore Need to add nil check here node.children[1..-1].each do |cc| @@ -403,7 +403,7 @@ def from_value_position_statement node, include_explicit_returns: true # @return [Array] def from_value_position_compound_statement parent result = [] - nodes = parent.children.select{|n| n.is_a?(AST::Node)} + nodes = parent.children.select { |n| n.is_a?(AST::Node) } nodes.each_with_index do |node, idx| if node.type == :block result.concat explicit_return_values_from_compound_statement(node.children[2]) @@ -428,7 +428,10 @@ def from_value_position_compound_statement parent # value position. we already have the explicit values # from above; now we need to also gather the value # position nodes - result.concat from_value_position_statement(nodes.last, include_explicit_returns: false) if idx == nodes.length - 1 + if idx == nodes.length - 1 + result.concat from_value_position_statement(nodes.last, + include_explicit_returns: false) + end end result end @@ -444,7 +447,7 @@ def from_value_position_compound_statement parent def explicit_return_values_from_compound_statement parent return [] unless parent.is_a?(::Parser::AST::Node) result = [] - nodes = parent.children.select{|n| n.is_a?(::Parser::AST::Node)} + nodes = parent.children.select { |n| n.is_a?(::Parser::AST::Node) } nodes.each do |node| next if SKIPPABLE.include?(node.type) if node.type == :return diff --git a/lib/solargraph/parser/parser_gem/node_processors/args_node.rb b/lib/solargraph/parser/parser_gem/node_processors/args_node.rb index ef7630921..9a22b8edd 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/args_node.rb @@ -14,17 +14,17 @@ def process node.children.each do |u| loc = get_node_location(u) locals.push Solargraph::Pin::Parameter.new( - location: loc, - closure: callable, - comments: comments_for(node), - name: u.children[0].to_s, - assignment: u.children[1], - asgn_code: u.children[1] ? region.code_for(u.children[1]) : nil, - # @sg-ignore Need to add nil check here - presence: callable.location.range, - decl: get_decl(u), - source: :parser - ) + location: loc, + closure: callable, + comments: comments_for(node), + name: u.children[0].to_s, + assignment: u.children[1], + asgn_code: u.children[1] ? region.code_for(u.children[1]) : nil, + # @sg-ignore Need to add nil check here + presence: callable.location.range, + decl: get_decl(u), + source: :parser + ) callable.parameters.push locals.last end end @@ -36,7 +36,7 @@ def process # @param callable [Pin::Callable] # @return [void] - def forward(callable) + def forward callable loc = get_node_location(node) locals.push Solargraph::Pin::Parameter.new( location: loc, 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 b16bde064..dadc1e3ad 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/block_node.rb @@ -37,7 +37,7 @@ def other_class_eval? node.children[0].type == :send && node.children[0].children[1] == :class_eval && # @sg-ignore Need to add nil check here - [:cbase, :const].include?(node.children[0].children[0]&.type) + %i[cbase const].include?(node.children[0].children[0]&.type) end end end diff --git a/lib/solargraph/parser/parser_gem/node_processors/def_node.rb b/lib/solargraph/parser/parser_gem/node_processors/def_node.rb index 1b9fd442d..f45f5544d 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/def_node.rb @@ -21,7 +21,7 @@ def process scope: scope, visibility: scope == :instance && name == 'initialize' ? :private : region.visibility, node: node, - source: :parser, + source: :parser ) if region.visibility == :module_function pins.push Solargraph::Pin::Method.new( @@ -34,7 +34,7 @@ def process visibility: :public, parameters: methpin.parameters, node: methpin.node, - source: :parser, + source: :parser ) pins.push Solargraph::Pin::Method.new( location: methpin.location, @@ -46,7 +46,7 @@ def process visibility: :private, parameters: methpin.parameters, node: methpin.node, - source: :parser, + source: :parser ) else pins.push methpin diff --git a/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb b/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb index ce807cd47..09679c7f7 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb @@ -11,14 +11,14 @@ def process s_visi = region.visibility s_visi = :public if s_visi == :module_function || region.scope != :class loc = get_node_location(node) - if node.children[0].is_a?(AST::Node) && node.children[0].type == :self - closure = region.closure - else - closure = Solargraph::Pin::Namespace.new( - name: unpack_name(node.children[0]), - source: :parser, - ) - end + closure = if node.children[0].is_a?(AST::Node) && node.children[0].type == :self + region.closure + else + Solargraph::Pin::Namespace.new( + name: unpack_name(node.children[0]), + source: :parser + ) + end pins.push Solargraph::Pin::Method.new( location: loc, closure: closure, @@ -27,7 +27,7 @@ def process scope: :class, visibility: s_visi, node: node, - source: :parser, + source: :parser ) process_children region.update(closure: pins.last, scope: :class) end 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 bf8f95635..0b9a75e77 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb @@ -18,7 +18,7 @@ def process location: get_node_location(condition_node), closure: region.closure, node: condition_node, - source: :parser, + source: :parser ) NodeProcessor.process(condition_node, region, pins, locals, ivars) end @@ -28,7 +28,7 @@ def process location: get_node_location(then_node), closure: region.closure, node: then_node, - source: :parser, + source: :parser ) NodeProcessor.process(then_node, region, pins, locals, ivars) end @@ -39,7 +39,7 @@ def process location: get_node_location(else_node), closure: region.closure, node: else_node, - source: :parser, + source: :parser ) NodeProcessor.process(else_node, region, pins, locals, ivars) end 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 0da611e22..0c4099d41 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb @@ -24,7 +24,8 @@ def process if named_path.is_a?(Pin::Method) ivars.push Solargraph::Pin::InstanceVariable.new( location: loc, - closure: Pin::Namespace.new(type: :module, closure: region.closure.closure, name: region.closure.name), + closure: Pin::Namespace.new(type: :module, closure: region.closure.closure, + name: region.closure.name), name: node.children[0].to_s, comments: comments_for(node), assignment: node.children[1], 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 ab23ffa0e..aab8106aa 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb @@ -88,7 +88,7 @@ def process_vasgn_target asgn, operator, argument argument ] send_node = node.updated(:send, send_children) - new_asgn = node.updated(asgn.type, [variable_name, send_node]) + new_asgn = node.updated(asgn.type, [variable_name, send_node]) NodeProcessor.process(new_asgn, region, pins, locals, ivars) end end 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 d3d2cef4f..2c8ef1c7b 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb @@ -31,9 +31,7 @@ def process 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 - names << also - end + names << also if also != region.closure.name name = names.reject(&:empty?).join('::') closure = Solargraph::Pin::Namespace.new(name: name, location: region.closure.location, source: :parser) else @@ -42,7 +40,7 @@ def process pins.push Solargraph::Pin::Singleton.new( location: get_node_location(node), closure: closure, - source: :parser, + source: :parser ) process_children region.update(visibility: :public, scope: :class, closure: pins.last) end 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 86c762c98..81404fcbf 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -14,16 +14,17 @@ def process 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}") + 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) + if %i[private public protected].include?(method_name) process_visibility elsif method_name == :module_function process_module_function - elsif [:attr_reader, :attr_writer, :attr_accessor].include?(method_name) + elsif %i[attr_reader attr_writer attr_accessor].include?(method_name) process_attribute elsif method_name == :include process_include @@ -44,7 +45,10 @@ def process return if process_private_class_method end 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) + 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 end @@ -53,7 +57,7 @@ def process # @return [void] def process_visibility - if (node.children.length > 2) + if node.children.length > 2 # @sg-ignore Need to add nil check here node.children[2..-1].each do |child| # @sg-ignore Variable type could not be inferred for method_name @@ -61,13 +65,14 @@ def process_visibility 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}") + 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?(::Parser::AST::Node) && (child.type == :sym || child.type == :str) + if child.is_a?(::Parser::AST::Node) && %i[sym str].include?(child.type) 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 = 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| # @todo Smelly instance variable access pin.instance_variable_set(:@visibility, visibility) @@ -89,7 +94,7 @@ def process_attribute loc = get_node_location(node) clos = region.closure cmnt = comments_for(node) - if node.children[1] == :attr_reader || node.children[1] == :attr_accessor + if %i[attr_reader attr_accessor].include?(node.children[1]) pins.push Solargraph::Pin::Method.new( location: loc, closure: clos, @@ -101,56 +106,55 @@ def process_attribute source: :parser ) end - if node.children[1] == :attr_writer || node.children[1] == :attr_accessor - method_pin = Solargraph::Pin::Method.new( - location: loc, - closure: clos, - name: "#{a.children[0]}=", - comments: cmnt, - scope: region.scope || :instance, - visibility: region.visibility, - attribute: true, - source: :parser - ) - pins.push method_pin - method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last, source: :parser) - if method_pin.return_type.defined? - pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.items.map(&:rooted_tags), 'value') - end + next unless %i[attr_writer attr_accessor].include?(node.children[1]) + method_pin = Solargraph::Pin::Method.new( + location: loc, + closure: clos, + name: "#{a.children[0]}=", + comments: cmnt, + scope: region.scope || :instance, + visibility: region.visibility, + attribute: true, + source: :parser + ) + pins.push method_pin + method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last, + source: :parser) + if method_pin.return_type.defined? + pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', + pins.last.return_type.items.map(&:rooted_tags), 'value') end end end # @return [void] def process_include - if node.children[2].is_a?(AST::Node) && node.children[2].type == :const - cp = region.closure - # @sg-ignore Need to add nil check here - node.children[2..-1].each do |i| - type = region.scope == :class ? Pin::Reference::Extend : Pin::Reference::Include - pins.push type.new( - location: get_node_location(i), - closure: cp, - name: unpack_name(i), - source: :parser - ) - end + return unless node.children[2].is_a?(AST::Node) && node.children[2].type == :const + cp = region.closure + # @sg-ignore Need to add nil check here + node.children[2..-1].each do |i| + type = region.scope == :class ? Pin::Reference::Extend : Pin::Reference::Include + pins.push type.new( + location: get_node_location(i), + closure: cp, + name: unpack_name(i), + source: :parser + ) end end # @return [void] def process_prepend - if node.children[2].is_a?(AST::Node) && node.children[2].type == :const - cp = region.closure - # @sg-ignore Need to add nil check here - node.children[2..-1].each do |i| - pins.push Pin::Reference::Prepend.new( - location: get_node_location(i), - closure: cp, - name: unpack_name(i), - source: :parser - ) - end + return unless node.children[2].is_a?(AST::Node) && node.children[2].type == :const + cp = region.closure + # @sg-ignore Need to add nil check here + node.children[2..-1].each do |i| + pins.push Pin::Reference::Prepend.new( + location: get_node_location(i), + closure: cp, + name: unpack_name(i), + source: :parser + ) end end @@ -179,18 +183,16 @@ def process_extend # @return [void] def process_require - if node.children[2].is_a?(AST::Node) && node.children[2].type == :str - path = node.children[2].children[0].to_s - pins.push Pin::Reference::Require.new(get_node_location(node), path, source: :parser) - end + return unless node.children[2].is_a?(AST::Node) && node.children[2].type == :str + path = node.children[2].children[0].to_s + pins.push Pin::Reference::Require.new(get_node_location(node), path, source: :parser) end # @return [void] def process_autoload - if node.children[3].is_a?(AST::Node) && node.children[3].type == :str - path = node.children[3].children[0].to_s - pins.push Pin::Reference::Require.new(get_node_location(node), path, source: :parser) - end + return unless node.children[3].is_a?(AST::Node) && node.children[3].type == :str + path = node.children[3].children[0].to_s + pins.push Pin::Reference::Require.new(get_node_location(node), path, source: :parser) end # @return [void] @@ -198,55 +200,55 @@ def process_module_function if node.children[2].nil? # @todo Smelly instance variable access region.instance_variable_set(:@visibility, :module_function) - elsif node.children[2].type == :sym || node.children[2].type == :str + elsif %i[sym str].include?(node.children[2].type) # @sg-ignore Need to add nil check here node.children[2..-1].each do |x| cn = x.children[0].to_s # @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 - mm = Solargraph::Pin::Method.new( - location: ref.location, - closure: ref.closure, - name: ref.name, - parameters: ref.parameters, - comments: ref.comments, - scope: :class, - visibility: :public, - node: ref.node, + next if ref.nil? + pins.delete ref + mm = Solargraph::Pin::Method.new( + location: ref.location, + closure: ref.closure, + name: ref.name, + parameters: ref.parameters, + comments: ref.comments, + scope: :class, + visibility: :public, + node: ref.node, + source: :parser + ) + cm = Solargraph::Pin::Method.new( + location: ref.location, + closure: ref.closure, + name: ref.name, + parameters: ref.parameters, + comments: ref.comments, + scope: :instance, + visibility: :private, + node: ref.node, + source: :parser + ) + pins.push mm, cm + ivars.select { |pin| pin.is_a?(Pin::InstanceVariable) && pin.closure.path == ref.path }.each do |ivar| + ivars.delete ivar + ivars.push Solargraph::Pin::InstanceVariable.new( + location: ivar.location, + closure: cm, + name: ivar.name, + comments: ivar.comments, + assignment: ivar.assignment, + source: :parser + ) + ivars.push Solargraph::Pin::InstanceVariable.new( + location: ivar.location, + closure: mm, + name: ivar.name, + comments: ivar.comments, + assignment: ivar.assignment, source: :parser ) - cm = Solargraph::Pin::Method.new( - location: ref.location, - closure: ref.closure, - name: ref.name, - parameters: ref.parameters, - comments: ref.comments, - scope: :instance, - visibility: :private, - node: ref.node, - source: :parser) - pins.push mm, cm - ivars.select{|pin| pin.is_a?(Pin::InstanceVariable) && pin.closure.path == ref.path}.each do |ivar| - ivars.delete ivar - ivars.push Solargraph::Pin::InstanceVariable.new( - location: ivar.location, - closure: cm, - name: ivar.name, - comments: ivar.comments, - assignment: ivar.assignment, - source: :parser - ) - ivars.push Solargraph::Pin::InstanceVariable.new( - location: ivar.location, - closure: mm, - name: ivar.name, - comments: ivar.comments, - assignment: ivar.assignment, - source: :parser - ) - end end end elsif node.children[2].type == :def @@ -256,17 +258,19 @@ def process_module_function # @return [void] def process_private_constant - if node.children[2] && (node.children[2].type == :sym || node.children[2].type == :str) - cn = node.children[2].children[0].to_s - ref = pins.select{|p| [Solargraph::Pin::Namespace, Solargraph::Pin::Constant].include?(p.class) && p.namespace == region.closure.full_context.namespace && p.name == cn}.first - # HACK: Smelly instance variable access - ref.instance_variable_set(:@visibility, :private) unless ref.nil? - end + return unless node.children[2] && %i[sym str].include?(node.children[2].type) + cn = node.children[2].children[0].to_s + ref = pins.select do |p| + [Solargraph::Pin::Namespace, + Solargraph::Pin::Constant].include?(p.class) && p.namespace == region.closure.full_context.namespace && p.name == cn + end.first + # HACK: Smelly instance variable access + ref.instance_variable_set(:@visibility, :private) unless ref.nil? end # @return [void] def process_alias_method - loc = get_node_location(node) + get_node_location(node) pins.push Solargraph::Pin::MethodAlias.new( location: get_node_location(node), closure: region.closure, @@ -279,8 +283,10 @@ def process_alias_method # @return [Boolean] def process_private_class_method - if node.children[2].type == :sym || node.children[2].type == :str - ref = pins.select { |p| p.is_a?(Pin::Method) && p.namespace == region.closure.full_context.namespace && p.name == node.children[2].children[0].to_s }.first + if %i[sym str].include?(node.children[2].type) + ref = pins.select do |p| + p.is_a?(Pin::Method) && p.namespace == region.closure.full_context.namespace && p.name == node.children[2].children[0].to_s + end.first # HACK: Smelly instance variable access ref.instance_variable_set(:@visibility, :private) unless ref.nil? false diff --git a/lib/solargraph/parser/parser_gem/node_processors/until_node.rb b/lib/solargraph/parser/parser_gem/node_processors/until_node.rb index bcfae287d..2e091f41d 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/until_node.rb @@ -18,7 +18,7 @@ def process closure: region.closure, node: node, comments: comments_for(node), - source: :parser, + source: :parser ) process_children region end diff --git a/lib/solargraph/parser/parser_gem/node_processors/when_node.rb b/lib/solargraph/parser/parser_gem/node_processors/when_node.rb index b2b11dec1..915eb57e6 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/when_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/when_node.rb @@ -12,7 +12,7 @@ def process location: get_node_location(node), closure: region.closure, node: node, - source: :parser, + source: :parser ) process_children end diff --git a/lib/solargraph/parser/parser_gem/node_processors/while_node.rb b/lib/solargraph/parser/parser_gem/node_processors/while_node.rb index 38ccc53b5..6c4fe33d8 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/while_node.rb @@ -22,7 +22,7 @@ def process closure: region.closure, node: node, comments: comments_for(node), - source: :parser, + source: :parser ) process_children region end diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index a057fef31..bd58cd47b 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -46,7 +46,8 @@ def presence_certain? # @param docstring [YARD::Docstring, nil] # @param directives [::Array, nil] # @param combine_priority [::Numeric, nil] See attr_reader for combine_priority - def initialize location: nil, type_location: nil, closure: nil, source: nil, name: '', comments: '', docstring: nil, directives: nil, combine_priority: nil + def initialize location: nil, type_location: nil, closure: nil, source: nil, name: '', comments: '', + docstring: nil, directives: nil, combine_priority: nil @location = location @type_location = type_location @closure = closure @@ -60,7 +61,6 @@ def initialize location: nil, type_location: nil, closure: nil, source: nil, nam # @type [ComplexType, ComplexType::UniqueType, nil] @binder = nil - assert_source_provided assert_location_provided end @@ -69,12 +69,16 @@ def initialize location: nil, type_location: nil, closure: nil, source: nil, nam def assert_location_provided return unless best_location.nil? && %i[yardoc source rbs].include?(source) - Solargraph.assert_or_log(:best_location, "Neither location nor type_location provided - #{path} #{source} #{self.class}") + 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 + unless @closure + Solargraph.assert_or_log(:closure, + "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") + end @closure end @@ -82,7 +86,7 @@ def closure # @param attrs [Hash{::Symbol => Object}] # # @return [self] - def combine_with(other, attrs={}) + def combine_with other, attrs = {} priority_choice = choose_priority(other) return priority_choice unless priority_choice.nil? @@ -101,7 +105,9 @@ def combine_with(other, attrs={}) combine_priority: combine_priority }.merge(attrs) assert_same_macros(other) - logger.debug { "Base#combine_with(path=#{path}) - other.comments=#{other.comments.inspect}, self.comments = #{self.comments}" } + logger.debug do + "Base#combine_with(path=#{path}) - other.comments=#{other.comments.inspect}, self.comments = #{comments}" + end out = self.class.new(**new_attrs) out.reset_generated! out @@ -110,7 +116,7 @@ def combine_with(other, attrs={}) # @param other [self] # @return [self, nil] Returns either the pin chosen based on priority or nil # A nil return means that the combination process must proceed - def choose_priority(other) + def choose_priority other if combine_priority.nil? && !other.combine_priority.nil? return other elsif other.combine_priority.nil? && !combine_priority.nil? @@ -130,7 +136,7 @@ def choose_priority(other) # @param attr [::Symbol] # @sg-ignore # @return [undefined] - def choose_longer(other, attr) + def choose_longer other, attr # @type [undefined] val1 = send(attr) # @type [undefined] @@ -143,22 +149,22 @@ def choose_longer(other, attr) # @param other [self] # # @return [::Array, nil] - def combine_directives(other) - return self.directives if other.directives.empty? + def combine_directives other + return directives if other.directives.empty? return other.directives if directives.empty? (directives + other.directives).uniq end # @param other [self] # @return [Pin::Closure, nil] - def combine_closure(other) + def combine_closure other choose_pin_attr_with_same_name(other, :closure) end # @param other [self] # @sg-ignore @type should override probed type # @return [String] - def combine_name(other) + def combine_name other if needs_consistent_name? || other.needs_consistent_name? assert_same(other, :name) else @@ -196,7 +202,7 @@ def needs_consistent_name? # @param other [self] # @return [ComplexType] - def combine_return_type(other) + def combine_return_type other if return_type.undefined? other.return_type elsif other.return_type.undefined? @@ -211,7 +217,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.reduce_class_type.rooted_tag } + if all_items.any? { |item| item.selfy? } && all_items.any? do |item| + item.rooted_tag == context.reduce_class_type.rooted_tag + end # assume this was a declaration that should have said 'self' all_items.delete_if { |item| item.rooted_tag == context.reduce_class_type.rooted_tag } end @@ -234,14 +242,14 @@ def dodgy_return_type_source? # # @sg-ignore # @return [undefined, nil] - def choose(other, attr) + def choose other, attr results = [self, other].map(&attr).compact # true and false are different classes and can't be sorted - return true if results.any? { |r| r == true || r == false } + return true if results.any? { |r| [true, false].include?(r) } return results.first if results.any? { |r| r.is_a? AST::Node } results.min - rescue - STDERR.puts("Problem handling #{attr} for \n#{self.inspect}\n and \n#{other.inspect}\n\n#{self.send(attr).inspect} vs #{other.send(attr).inspect}") + rescue StandardError + warn("Problem handling #{attr} for \n#{inspect}\n and \n#{other.inspect}\n\n#{send(attr).inspect} vs #{other.send(attr).inspect}") raise end @@ -249,7 +257,7 @@ def choose(other, attr) # @param attr [::Symbol] # @sg-ignore # @return [undefined] - def choose_node(other, attr) + def choose_node other, attr if other.object_id < attr.object_id other.send(attr) else @@ -261,9 +269,9 @@ def choose_node(other, attr) # @param attr [::Symbol] # @sg-ignore # @return [undefined] - def prefer_rbs_location(other, attr) + def prefer_rbs_location other, attr if rbs_location? && !other.rbs_location? - self.send(attr) + send(attr) elsif !rbs_location? && other.rbs_location? other.send(attr) else @@ -278,8 +286,8 @@ def rbs_location? # @param other [self] # @return [void] - def assert_same_macros(other) - return unless self.source == :yardoc && other.source == :yardoc + def assert_same_macros other + return unless source == :yardoc && other.source == :yardoc assert_same_count(other, :macros) # @param [YARD::Tags::MacroDirective] assert_same_array_content(other, :macros) { |macro| macro.tag.name } @@ -289,7 +297,7 @@ def assert_same_macros(other) # @param attr [::Symbol] # @return [void] # @todo strong typechecking should complain when there are no block-related tags - def assert_same_array_content(other, attr, &block) + def assert_same_array_content other, attr, &block arr1 = send(attr) raise "Expected #{attr} on #{self} to be an Enumerable, got #{arr1.class}" unless arr1.is_a?(::Enumerable) # @type arr1 [::Enumerable] @@ -303,7 +311,7 @@ def assert_same_array_content(other, attr, &block) values2 = arr2.map(&block) # @sg-ignore return arr1 if values1 == values2 - Solargraph.assert_or_log("combine_with_#{attr}".to_sym, + Solargraph.assert_or_log(:"combine_with_#{attr}", "Inconsistent #{attr.inspect} values between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self values = #{values1}\nother values =#{attr} = #{values2}") arr1 end @@ -312,15 +320,15 @@ def assert_same_array_content(other, attr, &block) # @param attr [::Symbol] # # @return [::Enumerable] - def assert_same_count(other, attr) + def assert_same_count other, attr # @type [::Enumerable] - arr1 = self.send(attr) + arr1 = send(attr) raise "Expected #{attr} on #{self} to be an Enumerable, got #{arr1.class}" unless arr1.is_a?(::Enumerable) # @type [::Enumerable] arr2 = other.send(attr) raise "Expected #{attr} on #{other} to be an Enumerable, got #{arr2.class}" unless arr2.is_a?(::Enumerable) return arr1 if arr1.count == arr2.count - Solargraph.assert_or_log("combine_with_#{attr}".to_sym, + Solargraph.assert_or_log(:"combine_with_#{attr}", "Inconsistent #{attr.inspect} count value between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self.#{attr} = #{arr1.inspect}\nother.#{attr} = #{arr2.inspect}") arr1 end @@ -330,16 +338,16 @@ def assert_same_count(other, attr) # # @sg-ignore # @return [undefined] - def assert_same(other, attr) + def assert_same other, attr if other.nil? - Solargraph.assert_or_log("combine_with_#{attr}_nil".to_sym, + Solargraph.assert_or_log(:"combine_with_#{attr}_nil", "Other was passed in nil in assert_same on #{self}") return send(attr) end val1 = send(attr) val2 = other.send(attr) return val1 if val1 == val2 - Solargraph.assert_or_log("combine_with_#{attr}".to_sym, + Solargraph.assert_or_log(:"combine_with_#{attr}", "Inconsistent #{attr.inspect} values between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self.#{attr} = #{val1.inspect}\nother.#{attr} = #{val2.inspect}") val1 end @@ -348,15 +356,17 @@ def assert_same(other, attr) # @param attr [::Symbol] # @sg-ignore # @return [undefined] - def choose_pin_attr_with_same_name(other, attr) + def choose_pin_attr_with_same_name other, attr # @type [Pin::Base, nil] val1 = send(attr) # @type [Pin::Base, nil] val2 = other.send(attr) - raise "Expected pin for #{attr} on\n#{self.inspect},\ngot #{val1.inspect}" unless val1.nil? || val1.is_a?(Pin::Base) - raise "Expected pin for #{attr} on\n#{other.inspect},\ngot #{val2.inspect}" unless val2.nil? || val2.is_a?(Pin::Base) + raise "Expected pin for #{attr} on\n#{inspect},\ngot #{val1.inspect}" unless val1.nil? || val1.is_a?(Pin::Base) + unless val2.nil? || val2.is_a?(Pin::Base) + raise "Expected pin for #{attr} on\n#{other.inspect},\ngot #{val2.inspect}" + end if val1&.name != val2&.name - Solargraph.assert_or_log("combine_with_#{attr}_name".to_sym, + Solargraph.assert_or_log(:"combine_with_#{attr}_name", "Inconsistent #{attr.inspect} name values between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self.#{attr} = #{val1.inspect}\nother.#{attr} = #{val2.inspect}") end choose_pin_attr(other, attr) @@ -367,14 +377,14 @@ def choose_pin_attr_with_same_name(other, attr) # # @sg-ignore Missing @return tag for Solargraph::Pin::Base#choose_pin_attr # @return [undefined] - def choose_pin_attr(other, attr) + def choose_pin_attr other, attr # @type [Pin::Base, nil] val1 = send(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, + Solargraph.assert_or_log(:"combine_with_#{attr}_class", "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: @@ -408,7 +418,7 @@ def comments # @param return_type_context [ComplexType, ComplexType::UniqueType, nil] # @param resolved_generic_values [Hash{String => ComplexType}] # @return [self] - def resolve_generics_from_context(generics_to_resolve, return_type_context = nil, resolved_generic_values: {}) + def resolve_generics_from_context generics_to_resolve, return_type_context = nil, resolved_generic_values: {} proxy return_type.resolve_generics_from_context(generics_to_resolve, return_type_context, resolved_generic_values: resolved_generic_values) @@ -417,7 +427,7 @@ def resolve_generics_from_context(generics_to_resolve, return_type_context = nil # @yieldparam [ComplexType] # @yieldreturn [ComplexType] # @return [self] - def transform_types(&transform) + def transform_types &transform proxy return_type.transform(&transform) end @@ -438,7 +448,7 @@ def all_rooted? # @param generics_to_erase [::Array] # @return [self] - def erase_generics(generics_to_erase) + def erase_generics generics_to_erase return self if generics_to_erase.empty? transform_types { |t| t.erase_generics(generics_to_erase) } end @@ -489,7 +499,8 @@ def nearly? other # @sg-ignore Translate to something flow sensitive typing understands (comments == other.comments || # @sg-ignore Translate to something flow sensitive typing understands - (((maybe_directives? == false && other.maybe_directives? == false) || compare_directives(directives, other.directives)) && + (((maybe_directives? == false && other.maybe_directives? == false) || compare_directives(directives, + other.directives)) && # @sg-ignore Translate to something flow sensitive typing understands compare_docstring_tags(docstring, other.docstring)) ) @@ -572,7 +583,8 @@ def probe api_map # @param api_map [ApiMap] # @return [ComplexType, ComplexType::UniqueType] def infer api_map - Solargraph.assert_or_log(:pin_infer, '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 @@ -665,7 +677,7 @@ def desc # @return [String] def inspect - "#<#{self.class} `#{self.inner_desc}`#{all_location_text} via #{source.inspect}>" + "#<#{self.class} `#{inner_desc}`#{all_location_text} via #{source.inspect}>" end # @return [String] @@ -692,9 +704,7 @@ def all_location_text # @return [ComplexType, ComplexType::UniqueType, nil] attr_writer :return_type - attr_writer :docstring - - attr_writer :directives + attr_writer :docstring, :directives private @@ -753,7 +763,7 @@ def compare_tags tag1, tag2 def collect_macros return [] unless maybe_directives? parse = Solargraph::Source.parse_docstring(comments) - parse.directives.select{ |d| d.tag.tag_name == 'macro' } + parse.directives.select { |d| d.tag.tag_name == 'macro' } end end end diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 0a3132b29..c7945e599 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -45,6 +45,7 @@ class BaseVariable < Base # @see https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types # @see https://en.wikipedia.org/wiki/Intersection_type#TypeScript_example # @param presence [Range, nil] + # @param [Hash{Symbol => Object}] splat def initialize assignment: nil, assignments: [], mass_assignment: nil, presence: nil, return_type: nil, intersection_return_type: nil, exclude_return_type: nil, @@ -81,28 +82,28 @@ def reset_generated! super end - def combine_with(other, attrs={}) + def combine_with other, attrs = {} new_assignments = combine_assignments(other) new_attrs = attrs.merge({ - # default values don't exist in RBS parameters; it just - # tells you if the arg is optional or not. Prefer a - # provided value if we have one here since we can't rely on - # it from RBS so we can infer from it and typecheck on it. - assignment: choose(other, :assignment), - assignments: new_assignments, - mass_assignment: combine_mass_assignment(other), - return_type: combine_return_type(other), - intersection_return_type: combine_types(other, :intersection_return_type), - exclude_return_type: combine_types(other, :exclude_return_type), - presence: combine_presence(other) - }) + # default values don't exist in RBS parameters; it just + # tells you if the arg is optional or not. Prefer a + # provided value if we have one here since we can't rely on + # it from RBS so we can infer from it and typecheck on it. + assignment: choose(other, :assignment), + assignments: new_assignments, + mass_assignment: combine_mass_assignment(other), + return_type: combine_return_type(other), + intersection_return_type: combine_types(other, :intersection_return_type), + exclude_return_type: combine_types(other, :exclude_return_type), + presence: combine_presence(other) + }) super(other, new_attrs) end # @param other [self] # # @return [Array(AST::Node, Integer), nil] - def combine_mass_assignment(other) + def combine_mass_assignment other # @todo pick first non-nil arbitrarily - we don't yet support # mass assignment merging mass_assignment || other.mass_assignment @@ -116,7 +117,7 @@ def assignment # @param other [self] # # @return [::Array] - def combine_assignments(other) + def combine_assignments other (other.assignments + assignments).uniq end @@ -148,7 +149,7 @@ def variable? # @param parent_node [Parser::AST::Node] # @param api_map [ApiMap] # @return [::Array] - def return_types_from_node(parent_node, api_map) + def return_types_from_node parent_node, api_map types = [] value_position_nodes_only(parent_node).each do |node| # Nil nodes may not have a location @@ -229,7 +230,7 @@ def presence_certain? # @param other_loc [Location] # @sg-ignore flow sensitive typing needs to handle attrs - def starts_at?(other_loc) + def starts_at? other_loc location&.filename == other_loc.filename && presence && # @sg-ignore flow sensitive typing needs to handle attrs @@ -241,7 +242,7 @@ def starts_at?(other_loc) # @param other [self] # # @return [Range, nil] - def combine_presence(other) + def combine_presence other return presence || other.presence if presence.nil? || other.presence.nil? # @sg-ignore flow sensitive typing needs to handle attrs @@ -250,14 +251,14 @@ def combine_presence(other) # @param other [self] # @return [Pin::Closure, nil] - def combine_closure(other) - return closure if self.closure == other.closure + def combine_closure other + return closure if closure == other.closure # choose first defined, as that establishes the scope of the variable if closure.nil? || other.closure.nil? Solargraph.assert_or_log(:varible_closure_missing) do - "One of the local variables being combined is missing a closure: " \ - "#{self.inspect} vs #{other.inspect}" + 'One of the local variables being combined is missing a closure: ' \ + "#{inspect} vs #{other.inspect}" end return closure || other.closure end @@ -277,7 +278,7 @@ def combine_closure(other) # @param other_closure [Pin::Closure] # @param other_loc [Location] - def visible_at?(other_closure, other_loc) + def visible_at? other_closure, other_loc # @sg-ignore flow sensitive typing needs to handle attrs location.filename == other_loc.filename && # @sg-ignore flow sensitive typing needs to handle attrs @@ -298,7 +299,7 @@ def visible_at?(other_closure, other_loc) # @param raw_return_type [ComplexType, ComplexType::UniqueType] # # @return [ComplexType, ComplexType::UniqueType] - def adjust_type(api_map, raw_return_type) + def adjust_type api_map, raw_return_type qualified_exclude = exclude_return_type&.qualify(api_map, *(closure&.gates || [''])) minus_exclusions = raw_return_type.exclude qualified_exclude, api_map qualified_intersection = intersection_return_type&.qualify(api_map, *(closure&.gates || [''])) @@ -337,7 +338,7 @@ def visible_in_closure? viewing_closure # @param other [self] # @return [ComplexType, nil] - def combine_return_type(other) + def combine_return_type other combine_types(other, :return_type) end @@ -345,7 +346,7 @@ def combine_return_type(other) # @param attr [::Symbol] # # @return [ComplexType, nil] - def combine_types(other, attr) + def combine_types other, attr # @type [ComplexType, nil] type1 = send(attr) # @type [ComplexType, nil] diff --git a/lib/solargraph/pin/block.rb b/lib/solargraph/pin/block.rb index 39f0d6495..1ad503317 100644 --- a/lib/solargraph/pin/block.rb +++ b/lib/solargraph/pin/block.rb @@ -15,6 +15,7 @@ class Block < Callable # @param node [Parser::AST::Node, nil] # @param context [ComplexType, nil] # @param args [::Array] + # @param [Hash{Symbol => Object}] splat def initialize receiver: nil, args: [], context: nil, node: nil, **splat super(**splat, parameters: args) @receiver = receiver @@ -44,7 +45,7 @@ def context # @param parameters [::Array] # # @return [::Array] - def destructure_yield_types(yield_types, parameters) + def destructure_yield_types yield_types, parameters # yielding a tuple into a block will destructure the tuple if yield_types.length == 1 yield_type = yield_types.first @@ -55,7 +56,7 @@ def destructure_yield_types(yield_types, parameters) # @param api_map [ApiMap] # @return [::Array] - def typify_parameters(api_map) + def typify_parameters api_map chain = Parser.chain(receiver, filename, node) # @sg-ignore Need to add nil check here clip = api_map.clip_at(location.filename, location.range.start) diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index a7c74ab35..05a72413b 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -14,6 +14,7 @@ class Callable < Closure # @param block [Signature, nil] # @param return_type [ComplexType, nil] # @param parameters [::Array] + # @param [Hash{Symbol => Object}] splat def initialize block: nil, return_type: nil, parameters: [], **splat super(**splat) @block = block @@ -36,7 +37,7 @@ def method_namespace # @param other [self] # # @return [Pin::Signature, nil] - def combine_blocks(other) + def combine_blocks other if block.nil? other.block elsif other.block.nil? @@ -51,10 +52,10 @@ def combine_blocks(other) # @param attrs [Hash{Symbol => Object}] # # @return [self] - def combine_with(other, attrs={}) + def combine_with other, attrs = {} new_attrs = { block: combine_blocks(other), - return_type: combine_return_type(other), + return_type: combine_return_type(other) }.merge(attrs) new_attrs[:parameters] = choose_parameters(other).clone.freeze unless new_attrs.key?(:parameters) super(other, new_attrs) @@ -72,8 +73,10 @@ def generics # @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 + def choose_parameters other + if other.arity != arity + raise "Trying to combine two pins with different arities - \nself =#{inspect}, \nother=#{other.inspect}, \n\n self.arity=#{arity}, \nother.arity=#{other.arity}" + end # @param param [Pin::Parameter] # @param other_param [Pin::Parameter] parameters.zip(other.parameters).map do |param, other_param| @@ -132,12 +135,12 @@ def full_type_arity # @param resolved_generic_values [Hash{String => ComplexType}] # # @return [self] - def resolve_generics_from_context(generics_to_resolve, + def resolve_generics_from_context generics_to_resolve, arg_types = nil, return_type_context = nil, yield_arg_types = nil, yield_return_type_context = nil, - resolved_generic_values: {}) + resolved_generic_values: {} callable = super(generics_to_resolve, return_type_context, resolved_generic_values: resolved_generic_values) callable.parameters = callable.parameters.each_with_index.map do |param, i| if arg_types.nil? @@ -148,10 +151,12 @@ def resolve_generics_from_context(generics_to_resolve, resolved_generic_values: resolved_generic_values) end end - callable.block = block.resolve_generics_from_context(generics_to_resolve, - yield_arg_types, - yield_return_type_context, - resolved_generic_values: resolved_generic_values) if callable.block? + if callable.block? + callable.block = block.resolve_generics_from_context(generics_to_resolve, + yield_arg_types, + yield_return_type_context, + resolved_generic_values: resolved_generic_values) + end callable end @@ -170,7 +175,7 @@ def typify api_map # @sg-ignore Need to add nil check here # @return [String] def method_name - raise "closure was nil in #{self.inspect}" if closure.nil? + raise "closure was nil in #{inspect}" if closure.nil? # @sg-ignore Need to add nil check here @method_name ||= closure.name end @@ -183,12 +188,12 @@ def method_name # @param resolved_generic_values [Hash{String => ComplexType}] # # @return [self] - def resolve_generics_from_context_until_complete(generics_to_resolve, + def resolve_generics_from_context_until_complete generics_to_resolve, arg_types = nil, return_type_context = nil, yield_arg_types = nil, yield_return_type_context = nil, - resolved_generic_values: {}) + resolved_generic_values: {} # See # https://github.com/soutaro/steep/tree/master/lib/steep/type_inference # and @@ -206,7 +211,7 @@ def resolve_generics_from_context_until_complete(generics_to_resolve, resolved_generic_values: resolved_generic_values) if last_resolved_generic_values == resolved_generic_values # erase anything unresolved - return new_pin.erase_generics(self.generics) + return new_pin.erase_generics(generics) end new_pin.resolve_generics_from_context_until_complete(generics_to_resolve, arg_types, @@ -219,7 +224,7 @@ def resolve_generics_from_context_until_complete(generics_to_resolve, # @yieldparam [ComplexType] # @yieldreturn [ComplexType] # @return [self] - def transform_types(&transform) + def transform_types &transform # @todo 'super' alone should work here I think, but doesn't typecheck at level typed callable = super(&transform) callable.block = block.transform_types(&transform) if block? @@ -252,7 +257,9 @@ def mandatory_positional_param_count # @return [String] def parameters_to_rbs # @sg-ignore Need to add nil check here - rbs_generics + '(' + parameters.map { |param| param.to_rbs }.join(', ') + ') ' + (block.nil? ? '' : '{ ' + block.to_rbs + ' } ') + rbs_generics + '(' + parameters.map { |param| + param.to_rbs + }.join(', ') + ') ' + (block.nil? ? '' : '{ ' + block.to_rbs + ' } ') end def to_rbs diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index 44265dcab..b01d760df 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -9,7 +9,8 @@ class Closure < CompoundStatement # @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 + # @param [Hash{Symbol => Object}] splat + def initialize scope: :class, generics: nil, generic_defaults: {}, **splat super(**splat) @scope = scope @generics = generics @@ -25,10 +26,10 @@ def generic_defaults # @param attrs [Hash{Symbol => Object}] # # @return [self] - def combine_with(other, attrs={}) + def combine_with other, attrs = {} new_attrs = { scope: assert_same(other, :scope), - generics: generics.empty? ? other.generics : generics, + generics: generics.empty? ? other.generics : generics }.merge(attrs) super(other, new_attrs) end diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 658c983e2..d52502afe 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -18,7 +18,7 @@ module Common # @param value [Pin::Closure] # @return [void] - def closure=(value) + def closure= value @closure = value # remove cached values generated from closure reset_generated! @@ -26,7 +26,10 @@ def closure=(value) # @return [Pin::Closure, nil] def closure - Solargraph.assert_or_log(:closure, "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") unless @closure + unless @closure + Solargraph.assert_or_log(:closure, + "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") + end @closure end diff --git a/lib/solargraph/pin/compound_statement.rb b/lib/solargraph/pin/compound_statement.rb index 9194e6975..415996642 100644 --- a/lib/solargraph/pin/compound_statement.rb +++ b/lib/solargraph/pin/compound_statement.rb @@ -43,6 +43,7 @@ class CompoundStatement < Pin::Base attr_reader :node # @param node [Parser::AST::Node, nil] + # @param [Hash{Symbol => Object}] splat def initialize node: nil, **splat super(**splat) @node = node diff --git a/lib/solargraph/pin/constant.rb b/lib/solargraph/pin/constant.rb index 94a968e7e..8da79f682 100644 --- a/lib/solargraph/pin/constant.rb +++ b/lib/solargraph/pin/constant.rb @@ -34,11 +34,9 @@ def path # @return [ComplexType] def generate_complex_type tags = docstring.tags(:return).map(&:types).flatten.reject(&:nil?) - if tags.empty? - tags = docstring.tags(:type).map(&:types).flatten.reject(&:nil?) - end + tags = docstring.tags(:type).map(&:types).flatten.reject(&:nil?) if tags.empty? return ComplexType::UNDEFINED if tags.empty? - ComplexType.try_parse *tags + ComplexType.try_parse(*tags) end end end diff --git a/lib/solargraph/pin/conversions.rb b/lib/solargraph/pin/conversions.rb index 5ad3573f7..25d585ea0 100644 --- a/lib/solargraph/pin/conversions.rb +++ b/lib/solargraph/pin/conversions.rb @@ -73,7 +73,13 @@ def detail # This property is not cached in an instance variable because it can # change when pins get proxied. detail = String.new - detail += "=#{probed? ? '~' : (proxied? ? '^' : '>')} #{return_type.to_s}" unless return_type.undefined? + unless return_type.undefined? + detail += "=#{if probed? + '~' + else + (proxied? ? '^' : '>') + end} #{return_type}" + end detail.strip! return nil if detail.empty? detail @@ -117,7 +123,7 @@ def generate_link # @return [String] def escape_brackets text # text.gsub(/(\<|\>)/, "\\#{$1}") - text.gsub("<", '\<').gsub(">", '\>') + text.gsub('<', '\<').gsub('>', '\>') end end end diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index 917e3a4e6..83273705f 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -15,6 +15,7 @@ class DelegatedMethod < Pin::Method # @param receiver [Source::Chain, nil] the source code used to resolve the receiver for this delegated method. # @param name [String, nil] # @param receiver_method_name [String, nil] the method name that will be called on the receiver (defaults to :name). + # @param [Hash{Symbol => Object}] splat 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) # @sg-ignore Need to add nil check here @@ -35,7 +36,6 @@ def location @resolved_method&.send(:location) end - def type_location return super if super @@ -59,7 +59,7 @@ def type_location end # @param api_map [ApiMap] - def resolvable?(api_map) + def resolvable? api_map resolve_method(api_map) !!@resolved_method end @@ -112,7 +112,7 @@ def resolve_method api_map # # @param chain [Source::Chain] # @return [String] - def print_chain(chain) + def print_chain chain out = +'' chain.links.each_with_index do |link, index| if index > 0 diff --git a/lib/solargraph/pin/documenting.rb b/lib/solargraph/pin/documenting.rb index cbeaf2a0d..94bd8a551 100644 --- a/lib/solargraph/pin/documenting.rb +++ b/lib/solargraph/pin/documenting.rb @@ -62,7 +62,7 @@ def to_s # @return [String] def to_code - "\n```ruby\n#{Documenting.normalize_indentation(@plaintext)}#{@plaintext.end_with?("\n") ? '' : "\n"}```\n\n" + "\n```ruby\n#{Documenting.normalize_indentation(@plaintext)}#{"\n" unless @plaintext.end_with?("\n")}```\n\n" end # @return [String] @@ -78,7 +78,8 @@ def documentation # line and at least two spaces of indentation. This is a common # convention in Ruby core documentation, e.g., String#split. sections = [DocSection.new(false)] - Documenting.normalize_indentation(Documenting.strip_html_comments(docstring.to_s.gsub("\t", ' '))).lines.each do |l| + Documenting.normalize_indentation(Documenting.strip_html_comments(docstring.to_s.gsub("\t", + ' '))).lines.each do |l| if l.start_with?(' ') # Code block sections.push DocSection.new(true) unless sections.last.code? diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 7aaab4615..580775bd7 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -16,9 +16,9 @@ def probe api_map super end - def combine_with(other, attrs={}) + def combine_with other, attrs = {} # keep this as a parameter - return other.combine_with(self, attrs) if other.is_a?(Parameter) && !self.is_a?(Parameter) + return other.combine_with(self, attrs) if other.is_a?(Parameter) && !is_a?(Parameter) super end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index d23adb599..51588b88a 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -23,6 +23,7 @@ class Method < Callable # @param signatures [::Array, nil] # @param anon_splat [Boolean] # @param context [ComplexType, ComplexType::UniqueType, nil] + # @param [Hash{Symbol => Object}] splat def initialize visibility: :public, explicit: true, block: :undefined, node: nil, attribute: false, signatures: nil, anon_splat: false, context: nil, **splat super(**splat) @@ -38,7 +39,7 @@ def initialize visibility: :public, explicit: true, block: :undefined, node: nil # @param other [Pin::Method] # @return [::Symbol] - def combine_visibility(other) + def combine_visibility other if dodgy_visibility_source? && !other.dodgy_visibility_source? other.visibility elsif other.dodgy_visibility_source? && !dodgy_visibility_source? @@ -48,16 +49,16 @@ def combine_visibility(other) end end - def combine_with(other, attrs = {}) + def combine_with other, attrs = {} priority_choice = choose_priority(other) return priority_choice unless priority_choice.nil? sigs = combine_signatures(other) parameters = if sigs.length > 0 - [].freeze - else - choose(other, :parameters).clone.freeze - end + [].freeze + else + choose(other, :parameters).clone.freeze + end new_attrs = { visibility: combine_visibility(other), explicit: explicit? || other.explicit?, @@ -77,7 +78,7 @@ def == other super && other.node == node end - def transform_types(&transform) + def transform_types &transform # @todo 'super' alone should work here I think, but doesn't typecheck at level typed m = super(&transform) m.signatures = m.signatures.map do |sig| @@ -92,14 +93,12 @@ def transform_types(&transform) def reset_generated! super unless signatures.empty? - return_type = nil @block = :undefined - parameters = [] + [] end block&.reset_generated! @signatures&.each(&:reset_generated!) - signature_help = nil - documentation = nil + nil end def all_rooted? @@ -108,7 +107,7 @@ def all_rooted? # @param signature [Pin::Signature] # @return [Pin::Method] - def with_single_signature(signature) + def with_single_signature signature m = proxy signature.return_type m.reset_generated! # @todo populating the single parameters/return_type/block @@ -150,7 +149,7 @@ def return_type # @param parameters [::Array] # @param return_type [ComplexType, nil] # @return [Signature] - def generate_signature(parameters, return_type) + def generate_signature parameters, return_type # @type [Pin::Signature, nil] block = nil yieldparam_tags = docstring.tags(:yieldparam) @@ -194,7 +193,11 @@ def signatures top_type = generate_complex_type result = [] result.push generate_signature(parameters, top_type) if top_type.defined? - result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty? + unless overloads.empty? + result.concat(overloads.map do |meth| + generate_signature(meth.parameters, meth.return_type) + end) + end result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? result end @@ -214,12 +217,18 @@ def detail # change when pins get proxied. detail = String.new detail += if signatures.length > 1 - "(*) " - else - "(#{signatures.first.parameters.map(&:full).join(', ')}) " unless signatures.first.parameters.empty? - end.to_s + '(*) ' + else + "(#{signatures.first.parameters.map(&:full).join(', ')}) " unless signatures.first.parameters.empty? + end.to_s # @sg-ignore Need to add nil check here - detail += "=#{probed? ? '~' : (proxied? ? '^' : '>')} #{return_type.to_s}" unless return_type.undefined? + unless return_type.undefined? + detail += "=#{if probed? + '~' + else + (proxied? ? '^' : '>') + end} #{return_type}" + end detail.strip! return nil if detail.empty? detail @@ -258,7 +267,7 @@ def to_rbs end def path - @path ||= "#{namespace}#{(scope == :instance ? '#' : '.')}#{name}" + @path ||= "#{namespace}#{scope == :instance ? '#' : '.'}#{name}" end # @return [String] @@ -268,10 +277,14 @@ def method_name def typify api_map # @sg-ignore Need to add nil check here - logger.debug { "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context.rooted_tags}, return_type=#{return_type.rooted_tags}) - starting" } + logger.debug do + "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context.rooted_tags}, return_type=#{return_type.rooted_tags}) - starting" + end decl = super unless decl.undefined? - logger.debug { "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context}) => #{decl.rooted_tags.inspect} - decl found" } + logger.debug do + "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context}) => #{decl.rooted_tags.inspect} - decl found" + end return decl end type = see_reference(api_map) || typify_from_super(api_map) @@ -320,7 +333,7 @@ def documentation method_docs += "Block Returns:\n" lines = [] yieldreturn_tags.each do |r| - l = "*" + l = '*' l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? or r.types.empty? l += " #{r.text}" lines.push l @@ -333,7 +346,7 @@ def documentation method_docs += "Returns:\n" lines = [] return_tags.each do |r| - l = "*" + l = '*' l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? or r.types.empty? l += " #{r.text}" lines.push l @@ -397,7 +410,7 @@ def overloads end, closure: self, return_type: ComplexType.try_parse(*tag.docstring.tags(:return).flat_map(&:types)), - source: :overloads, + source: :overloads ) end @overloads @@ -416,13 +429,13 @@ def resolve_ref_tag api_map return self unless docstring.ref_tags.any? docstring.ref_tags.each do |tag| ref = if tag.owner.to_s.start_with?(/[#.]/) - api_map.get_methods(namespace) - .select { |pin| pin.path.end_with?(tag.owner.to_s) } - .first - else - # @todo Resolve relative namespaces - api_map.get_path_pins(tag.owner.to_s).first - end + api_map.get_methods(namespace) + .select { |pin| pin.path.end_with?(tag.owner.to_s) } + .first + else + # @todo Resolve relative namespaces + api_map.get_path_pins(tag.owner.to_s).first + end next unless ref docstring.add_tag(*ref.docstring.tags(:param)) @@ -438,11 +451,7 @@ def rest_of_stack api_map protected - attr_writer :block - - attr_writer :signature_help - - attr_writer :documentation + attr_writer :block, :signature_help, :documentation, :return_type # @sg-ignore Need to add nil check here def dodgy_visibility_source? @@ -450,22 +459,22 @@ def dodgy_visibility_source? # e.g. activesupport did not understand 'private' markings # inside 'class << self' blocks, but YARD did OK at it # @sg-ignore Need to add nil check here - source == :rbs && scope == :class && type_location&.filename&.include?('generated') && return_type.undefined? || + (source == :rbs && scope == :class && type_location&.filename&.include?('generated') && return_type.undefined?) || # YARD's RBS generator seems to miss a lot of should-be protected instance methods - source == :rbs && scope == :instance && namespace.start_with?('YARD::') || + (source == :rbs && scope == :instance && namespace.start_with?('YARD::')) || # private on attr_readers seems to be broken in Prism's auto-generator script - source == :rbs && scope == :instance && namespace.start_with?('Prism::') || + (source == :rbs && scope == :instance && namespace.start_with?('Prism::')) || # The RBS for the RBS gem itself seems to use private as a # 'is this a public API' concept, more aggressively than the # actual code. Let's respect that and ignore the actual .rb file. - source == :yardoc && scope == :instance && namespace.start_with?('RBS::') + (source == :yardoc && scope == :instance && namespace.start_with?('RBS::')) end private # @param other [Pin::Method] # @return [Array] - def combine_signatures(other) + def combine_signatures other all_undefined = signatures.all? { |sig| !sig.return_type&.defined? } other_all_undefined = other.signatures.all? { |sig| !sig.return_type&.defined? } if all_undefined && !other_all_undefined @@ -497,7 +506,7 @@ def combine_signatures_by_type_arity(*signature_pins) # @param same_type_arity_signatures [Array] # # @return [Array] - def combine_same_type_arity_signatures(same_type_arity_signatures) + def combine_same_type_arity_signatures same_type_arity_signatures # This is an O(n^2) operation, so bail out if n is not small return same_type_arity_signatures if same_type_arity_signatures.length > 10 @@ -505,8 +514,6 @@ def combine_same_type_arity_signatures(same_type_arity_signatures) # @param new_signature [Pin::Signature] same_type_arity_signatures.reduce([]) do |old_signatures, new_signature| next old_signatures + [new_signature] if old_signatures.empty? - - found_merge = false old_signatures.flat_map do |old_signature| potential_new_signature = old_signature.combine_with(new_signature) @@ -560,7 +567,7 @@ def clean_param name # @param name [String] # # @return [ComplexType] - def param_type_from_name(tag, name) + def param_type_from_name tag, name # @param t [YARD::Tags::Tag] param = tag.tags(:param).select { |t| t.name == name }.first return ComplexType::UNDEFINED unless param @@ -571,7 +578,7 @@ def param_type_from_name(tag, name) def generate_complex_type tags = docstring.tags(:return).map(&:types).flatten.compact return ComplexType::UNDEFINED if tags.empty? - ComplexType.try_parse *tags + ComplexType.try_parse(*tags) end # @param api_map [ApiMap] @@ -635,7 +642,7 @@ def method_body_node return nil if node.nil? return node.children[1].children.last if node.type == :DEFN return node.children[2].children.last if node.type == :DEFS - return node.children[2] if node.type == :def || node.type == :DEFS + return node.children[2] if %i[def DEFS].include?(node.type) return node.children[3] if node.type == :defs nil end @@ -648,7 +655,7 @@ def infer_from_return_nodes api_map has_nil = false return ComplexType::NIL if method_body_node.nil? returns_from_method_body(method_body_node).each do |n| - if n.nil? || [:NIL, :nil].include?(n.type) + if n.nil? || %i[NIL nil].include?(n.type) has_nil = true next end @@ -688,7 +695,7 @@ def infer_from_iv api_map # # @param name [String] # @return [::Array(String, ::Symbol)] - def parse_overload_param(name) + def parse_overload_param name # @todo this needs to handle mandatory vs not args, kwargs, blocks, etc if name.start_with?('**') [name[2..-1], :kwrestarg] @@ -711,10 +718,6 @@ def concat_example_tags .join("\n") .concat("```\n") end - - protected - - attr_writer :return_type end end end diff --git a/lib/solargraph/pin/namespace.rb b/lib/solargraph/pin/namespace.rb index f41a7ae2b..f090716c8 100644 --- a/lib/solargraph/pin/namespace.rb +++ b/lib/solargraph/pin/namespace.rb @@ -20,6 +20,7 @@ class Namespace < Closure # @param visibility [::Symbol] :public or :private # @param gates [::Array] # @param name [String] + # @param [Hash{Symbol => Object}] splat def initialize type: :class, visibility: :public, gates: [''], name: '', **splat # super(location, namespace, name, comments) super(**splat, name: name) @@ -36,11 +37,11 @@ def initialize type: :class, visibility: :public, gates: [''], name: '', **splat parts = name.split('::') name = parts.pop closure_name = if [Solargraph::Pin::ROOT_PIN, nil].include?(closure) - '' - else - # @sg-ignore Need to add nil check here - closure.full_context.namespace + '::' - end + '' + else + # @sg-ignore Need to add nil check here + closure.full_context.namespace + '::' + end closure_name += parts.join('::') @closure = Pin::Namespace.new(name: closure_name, gates: [parts.join('::')], source: :namespace) @context = nil @@ -55,7 +56,7 @@ def reset_generated! end def to_rbs - "#{@type.to_s} #{return_type.all_params.first.to_rbs}#{rbs_generics}".strip + "#{@type} #{return_type.all_params.first.to_rbs}#{rbs_generics}".strip end def inner_desc @@ -97,7 +98,7 @@ def path end def return_type - @return_type ||= ComplexType.try_parse( (type == :class ? '::Class' : '::Module') + "<::#{path}>") + @return_type ||= ComplexType.try_parse((type == :class ? '::Class' : '::Module') + "<::#{path}>") end # @return [Array] @@ -111,10 +112,10 @@ def typify api_map def gates @gates ||= if path.empty? - @open_gates - else - [path] + @open_gates - end + @open_gates + else + [path] + @open_gates + end end end end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 05a8cb490..4aa8f0e19 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -15,6 +15,7 @@ class Parameter < LocalVariable # @param decl [::Symbol] :arg, :optarg, :kwarg, :kwoptarg, :restarg, :kwrestarg, :block, :blockarg # @param asgn_code [String, nil] + # @param [Hash{Symbol => Object}] splat def initialize decl: :arg, asgn_code: nil, **splat super(**splat) @asgn_code = asgn_code @@ -29,7 +30,7 @@ def location super || closure&.type_location end - def combine_with(other, attrs={}) + def combine_with other, attrs = {} # Parameters can be combined with local variables new_attrs = if other.is_a?(Parameter) { @@ -45,7 +46,7 @@ def combine_with(other, attrs={}) super(other, new_attrs.merge(attrs)) end - def combine_return_type(other) + def combine_return_type other out = super if out&.undefined? # allow our return_type method to provide a better type @@ -56,12 +57,12 @@ def combine_return_type(other) end def keyword? - [:kwarg, :kwoptarg].include?(decl) + %i[kwarg kwoptarg].include?(decl) end def kwrestarg? # @sg-ignore flow sensitive typing needs to handle attrs - decl == :kwrestarg || (assignment && [:HASH, :hash].include?(assignment.type)) + decl == :kwrestarg || (assignment && %i[HASH hash].include?(assignment.type)) end def needs_consistent_name? @@ -70,21 +71,21 @@ def needs_consistent_name? # @return [String] def arity_decl - name = (self.name || '(anon)') - type = (return_type&.to_rbs || 'untyped') + name = self.name || '(anon)' + return_type&.to_rbs || 'untyped' case decl when :arg - "" + '' when :optarg - "?" + '?' when :kwarg "#{name}:" when :kwoptarg "?#{name}:" when :restarg - "*" + '*' when :kwrestarg - "**" + '**' else "(unknown decl: #{decl})" end @@ -112,11 +113,11 @@ def positional? end def rest? - decl == :restarg || decl == :kwrestarg + %i[restarg kwrestarg].include?(decl) end def block? - [:block, :blockarg].include?(decl) + %i[block blockarg].include?(decl) end def to_rbs @@ -217,7 +218,7 @@ def typify api_map # @param atype [ComplexType] # @param api_map [ApiMap] - def compatible_arg?(atype, api_map) + 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 @@ -226,7 +227,7 @@ def compatible_arg?(atype, api_map) return true if atype.conforms_to?(api_map, ptype, :method_call, - [:allow_empty_params, :allow_undefined]) + %i[allow_empty_params allow_undefined]) ptype.generic? end @@ -259,9 +260,7 @@ def param_tag # @return [ComplexType] def typify_block_param api_map block_pin = closure - if block_pin.is_a?(Pin::Block) && block_pin.receiver && index - return block_pin.typify_parameters(api_map)[index] - end + return block_pin.typify_parameters(api_map)[index] if block_pin.is_a?(Pin::Block) && block_pin.receiver && index ComplexType::UNDEFINED end @@ -279,11 +278,14 @@ def typify_method_param api_map found = p break end - if found.nil? and !index.nil? - found = params[index] if params[index] && (params[index].name.nil? || params[index].name.empty?) + if found.nil? and !index.nil? && params[index] && (params[index].name.nil? || params[index].name.empty?) + found = params[index] end # @sg-ignore Need to add nil check here - return ComplexType.try_parse(*found.types).qualify(api_map, *meth.closure.gates) unless found.nil? || found.types.nil? + unless found.nil? || found.types.nil? + return ComplexType.try_parse(*found.types).qualify(api_map, + *meth.closure.gates) + end end ComplexType::UNDEFINED end diff --git a/lib/solargraph/pin/proxy_type.rb b/lib/solargraph/pin/proxy_type.rb index e856c8833..fd37bab85 100644 --- a/lib/solargraph/pin/proxy_type.rb +++ b/lib/solargraph/pin/proxy_type.rb @@ -7,6 +7,7 @@ class ProxyType < Base # @param gates [Array, nil] Namespaces to try while resolving non-rooted types # @param binder [ComplexType, ComplexType::UniqueType, nil] # @param gates [Array, nil] + # @param [Hash{Symbol => Object}] splat def initialize return_type: ComplexType::UNDEFINED, binder: nil, gates: nil, **splat super(**splat) @gates = gates @@ -22,6 +23,7 @@ def context # @param closure [Pin::Namespace, nil] Used as the closure for this pin # @param binder [ComplexType, ComplexType::UniqueType, nil] # @return [ProxyType] + # @param [Hash{Symbol => Object}] kwargs def self.anonymous context, closure: nil, binder: nil, **kwargs unless closure parts = context.namespace.split('::') diff --git a/lib/solargraph/pin/reference.rb b/lib/solargraph/pin/reference.rb index 4ad64fcb8..fc54db021 100644 --- a/lib/solargraph/pin/reference.rb +++ b/lib/solargraph/pin/reference.rb @@ -29,6 +29,7 @@ class Reference < Base # # @param name [String] rooted name of the referenced type # @param generic_values [Array] + # @param [Hash{Symbol => Object}] splat def initialize generic_values: [], **splat super(**splat) @generic_values = generic_values diff --git a/lib/solargraph/pin/search.rb b/lib/solargraph/pin/search.rb index 67bd74d24..532b9774e 100644 --- a/lib/solargraph/pin/search.rb +++ b/lib/solargraph/pin/search.rb @@ -46,7 +46,7 @@ def do_query # @param b [self] # @sg-ignore https://github.com/castwide/solargraph/pull/1050 .sort { |a, b| b.match <=> a.match } - .map(&:pin) + .map(&:pin) end # @param str1 [String] diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 60967274f..437744c37 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -47,16 +47,15 @@ def typify api_map method_stack = closure.rest_of_stack api_map logger.debug { "Signature#typify(self=#{self}) - method_stack: #{method_stack}" } method_stack.each do |pin| - sig = pin.signatures.find { |s| s.arity == self.arity } + sig = pin.signatures.find { |s| s.arity == arity } next unless sig # @sg-ignore Need to add nil check here - unless sig.return_type.undefined? - # @sg-ignore Need to add nil check here - qualified = sig.return_type.qualify(api_map, closure.namespace) - # @sg-ignore Need to add nil check here - logger.debug { "Signature#typify(self=#{self}) => #{qualified.rooted_tags.inspect}" } - return qualified - end + next if sig.return_type.undefined? + # @sg-ignore Need to add nil check here + qualified = sig.return_type.qualify(api_map, closure.namespace) + # @sg-ignore Need to add nil check here + logger.debug { "Signature#typify(self=#{self}) => #{qualified.rooted_tags.inspect}" } + return qualified end out = super logger.debug { "Signature#typify(self=#{self}) => #{out}" } diff --git a/lib/solargraph/pin/symbol.rb b/lib/solargraph/pin/symbol.rb index 18178e9b9..f28fb2a71 100644 --- a/lib/solargraph/pin/symbol.rb +++ b/lib/solargraph/pin/symbol.rb @@ -5,6 +5,7 @@ module Pin class Symbol < Base # @param location [Solargraph::Location, nil] # @param name [String] + # @param [Hash{Symbol => Object}] kwargs def initialize(location, name, **kwargs) # @sg-ignore "Unrecognized keyword argument kwargs to Solargraph::Pin::Base#initialize" super(location: location, name: name, **kwargs) diff --git a/lib/solargraph/pin/until.rb b/lib/solargraph/pin/until.rb index 7497c0f09..f6da568c6 100644 --- a/lib/solargraph/pin/until.rb +++ b/lib/solargraph/pin/until.rb @@ -6,6 +6,7 @@ class Until < CompoundStatement include Breakable # @param node [Parser::AST::Node, nil] + # @param [Hash{Symbol => Object}] splat def initialize node: nil, **splat super(**splat) @node = node diff --git a/lib/solargraph/pin/while.rb b/lib/solargraph/pin/while.rb index 19b0bdc8b..f10616891 100644 --- a/lib/solargraph/pin/while.rb +++ b/lib/solargraph/pin/while.rb @@ -6,6 +6,7 @@ class While < CompoundStatement include Breakable # @param node [Parser::AST::Node, nil] + # @param [Hash{Symbol => Object}] splat def initialize node: nil, **splat super(**splat) @node = node diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index de41ede82..e3311e6ea 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -539,34 +539,34 @@ def yard_gem_path gemspec # @param gemspec [Gem::Specification] # @return [Array, nil] - def deserialize_yard_gem(gemspec) + def deserialize_yard_gem gemspec load(yard_gem_path(gemspec)) end # @param gemspec [Gem::Specification] # @param pins [Array] # @return [void] - def serialize_yard_gem(gemspec, pins) + def serialize_yard_gem gemspec, pins save(yard_gem_path(gemspec), pins) end # @param gemspec [Gem::Specification] # @param hash [String, nil] # @return [String] - def rbs_collection_path(gemspec, hash) + def rbs_collection_path gemspec, hash File.join(work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-#{hash || 0}.ser") end # @param gemspec [Gem::Specification] # @return [String] - def rbs_collection_path_prefix(gemspec) + def rbs_collection_path_prefix gemspec File.join(work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-") end # @param gemspec [Gem::Specification] # @param hash [String, nil] # @return [Array, nil] - def deserialize_rbs_collection_gem(gemspec, hash) + def deserialize_rbs_collection_gem gemspec, hash load(rbs_collection_path(gemspec, hash)) end @@ -574,20 +574,20 @@ def deserialize_rbs_collection_gem(gemspec, hash) # @param hash [String, nil] # @param pins [Array]n # @return [void] - def serialize_rbs_collection_gem(gemspec, hash, pins) + def serialize_rbs_collection_gem gemspec, hash, pins save(rbs_collection_path(gemspec, hash), pins) end # @param gemspec [Gem::Specification] # @param hash [String, nil] # @return [String] - def combined_path(gemspec, hash) + def combined_path gemspec, hash File.join(work_dir, 'combined', "#{gemspec.name}-#{gemspec.version}-#{hash || 0}.ser") end # @param gemspec [Gem::Specification] # @return [String] - def combined_path_prefix(gemspec) + def combined_path_prefix gemspec File.join(work_dir, 'combined', "#{gemspec.name}-#{gemspec.version}-") end @@ -595,7 +595,7 @@ def combined_path_prefix(gemspec) # @param hash [String, nil] # @param pins [Array] # @return [void] - def serialize_combined_gem(gemspec, hash, pins) + def serialize_combined_gem gemspec, hash, pins save(combined_path(gemspec, hash), pins) end @@ -609,7 +609,7 @@ def deserialize_combined_gem gemspec, hash # @param gemspec [Gem::Specification] # @param hash [String, nil] # @return [Boolean] - def has_rbs_collection?(gemspec, hash) + def has_rbs_collection? gemspec, hash exist?(rbs_collection_path(gemspec, hash)) end diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 332ffa527..e8092ea78 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -26,7 +26,7 @@ def initialize line, character end # @param other [Position] - def <=>(other) + def <=> other return nil unless other.is_a?(Position) if line == other.line character <=> other.character @@ -68,7 +68,6 @@ def self.to_offset text, position break if line == position.line # @sg-ignore oflow sensitive typing should be able to handle redefinition - line_length = newline_index - last_line_index last_line_index = newline_index end diff --git a/lib/solargraph/range.rb b/lib/solargraph/range.rb index 50bec73d8..337e1db0d 100644 --- a/lib/solargraph/range.rb +++ b/lib/solargraph/range.rb @@ -24,7 +24,7 @@ def initialize start, ending end # @param other [BasicObject] - def <=>(other) + def <=> other return nil unless other.is_a?(Range) if start == other.start ending <=> other.ending @@ -36,7 +36,7 @@ def <=>(other) # Get a hash of the range. This representation is suitable for use in # the language server protocol. # - # @return [Hash] + # @return [Hash{Symbol => Position}] def to_hash { start: start.to_hash, @@ -86,9 +86,8 @@ def self.from_to l1, c1, l2, c2 # @param node [::Parser::AST::Node] # @return [Range, nil] def self.from_node node - if node&.loc && node.loc.expression - from_expr(node.loc.expression) - end + return unless node&.loc && node.loc.expression + from_expr(node.loc.expression) end # Get a range from a Parser range, usually found in diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index d5a4d93ed..fd366f8d5 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -16,11 +16,7 @@ class RbsMap # @type [Hash{String => RbsMap}] @@rbs_maps_hash = {} - attr_reader :library - - attr_reader :rbs_collection_paths - - attr_reader :rbs_collection_config_path + attr_reader :library, :rbs_collection_paths, :rbs_collection_config_path # @param library [String] # @param version [String, nil] diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 0182a11ea..bc6befe5b 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -78,7 +78,8 @@ def convert_decl_to_pin decl, closure # @sg-ignore flow sensitive typing should support case/when unless closure.name == '' || decl.name.absolute? # @sg-ignore flow sensitive typing should support case/when - Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on alias type name #{decl.name.to_s}") + Solargraph.assert_or_log(:rbs_closure, + "Ignoring closure #{closure.inspect} on alias type name #{decl.name}") end # @sg-ignore flow sensitive typing should support case/when type_aliases[decl.name.to_s] = decl @@ -86,7 +87,8 @@ def convert_decl_to_pin decl, closure # @sg-ignore flow sensitive typing should support case/when unless closure.name == '' || decl.name.absolute? # @sg-ignore flow sensitive typing should support case/when - Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on alias type name #{decl.name.to_s}") + Solargraph.assert_or_log(:rbs_closure, + "Ignoring closure #{closure.inspect} on alias type name #{decl.name}") end module_decl_to_pin decl when RBS::AST::Declarations::Constant @@ -103,7 +105,8 @@ def convert_decl_to_pin decl, closure class_alias_decl_to_pin decl when RBS::AST::Declarations::ModuleAlias unless closure.name == '' - Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on module alias #{decl.inspect}") + Solargraph.assert_or_log(:rbs_closure, + "Ignoring closure #{closure.inspect} on module alias #{decl.inspect}") end module_alias_decl_to_pin decl when RBS::AST::Declarations::Global @@ -127,7 +130,7 @@ def convert_self_types_to_pins decl, module_pin RBS_TO_CLASS = { 'bool' => 'Boolean', 'string' => 'String', - 'int' => 'Integer', + 'int' => 'Integer' }.freeze private_constant :RBS_TO_CLASS @@ -138,7 +141,7 @@ def convert_self_types_to_pins decl, module_pin # @param type_name [RBS::TypeName] # # @return [String] - def rooted_name(type_name) + def rooted_name type_name name = type_name.to_s RBS_TO_CLASS.fetch(name, name) end @@ -149,7 +152,8 @@ def rooted_name(type_name) # @param [RBS::TypeName] # # @return [String] - def fqns(type_name) + # @param [Object] type_name + def fqns type_name unless type_name.absolute? Solargraph.assert_or_log(:rbs_fqns, "Received unexpected unqualified type name: #{type_name}") end @@ -169,9 +173,11 @@ def build_type type_name, type_args = [] params = type_args.map { |a| other_type_to_type(a) } # tuples have their own class and are handled in other_type_to_type if base == 'Hash' && params.length == 2 - ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: type_name.absolute?, parameters_type: :hash) + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: type_name.absolute?, + parameters_type: :hash) else - ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: type_name.absolute?, parameters_type: :list) + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: type_name.absolute?, + parameters_type: :list) end end @@ -374,9 +380,7 @@ def create_constant fqns, type, comments, decl, base = nil source: :rbs ) rooted_tag = type.rooted_tags - if base - rooted_tag = "#{base}<#{rooted_tag}>" - end + rooted_tag = "#{base}<#{rooted_tag}>" if base constant_pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) constant_pin end diff --git a/lib/solargraph/rbs_map/core_fills.rb b/lib/solargraph/rbs_map/core_fills.rb index 3bb32a0da..809e802a1 100644 --- a/lib/solargraph/rbs_map/core_fills.rb +++ b/lib/solargraph/rbs_map/core_fills.rb @@ -38,7 +38,7 @@ module CoreFills source: :core_fill), # RBS does not define Class with a generic, so all calls to # generic() return an 'untyped'. We can do better: - Override.method_return('Class#allocate', 'self', source: :core_fill), + Override.method_return('Class#allocate', 'self', source: :core_fill) ] # @todo I don't see any direct link in RBS to build this from - @@ -46,26 +46,32 @@ module CoreFills # against concrete classes INCLUDES = [ Solargraph::Pin::Reference::Include.new(name: '_ToAry', - closure: Solargraph::Pin::Namespace.new(name: 'Array', source: :core_fill), + 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), + 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), + 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), + 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), + 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), + closure: Solargraph::Pin::Namespace.new(name: 'String', + source: :core_fill), source: :core_fill) ] @@ -74,7 +80,8 @@ module CoreFills errnos = [] Errno.constants.each do |const| errnos.push Solargraph::Pin::Namespace.new(type: :class, name: const.to_s, closure: errno, source: :core_fill) - errnos.push Solargraph::Pin::Reference::Superclass.new(closure: errnos.last, name: 'SystemCallError', source: :core_fill) + errnos.push Solargraph::Pin::Reference::Superclass.new(closure: errnos.last, name: 'SystemCallError', + source: :core_fill) end ERRNOS = errnos diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index a92410895..e6ebcf90f 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -12,7 +12,6 @@ class StdlibMap < RbsMap # @type [Hash{String => RbsMap}] @stdlib_maps_hash = {} - # @param rebuild [Boolean] build pins regardless of whether we # have cached them already # @param library [String] diff --git a/lib/solargraph/server_methods.rb b/lib/solargraph/server_methods.rb index bdac3f19c..86cd107d0 100644 --- a/lib/solargraph/server_methods.rb +++ b/lib/solargraph/server_methods.rb @@ -7,7 +7,7 @@ module ServerMethods # @return [Integer] def available_port socket = Socket.new(:INET, :STREAM, 0) - socket.bind(Addrinfo.tcp("127.0.0.1", 0)) + socket.bind(Addrinfo.tcp('127.0.0.1', 0)) port = socket.local_address.ip_port socket.close port diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 072f79f85..2859ae325 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -16,7 +16,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 @@ -31,15 +31,15 @@ def socket port = options[:port] port = available_port if port.zero? Backport.run do - Signal.trap("INT") do + Signal.trap('INT') do Backport.stop end - Signal.trap("TERM") do + 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}" + warn "Solargraph is listening PORT=#{port} PID=#{Process.pid}" end end @@ -48,15 +48,15 @@ def socket def stdio require 'backport' Backport.run do - Signal.trap("INT") do + Signal.trap('INT') do Backport.stop end - Signal.trap("TERM") do + 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}" + warn "Solargraph is listening on stdio PID=#{Process.pid}" end end @@ -64,11 +64,11 @@ def stdio option :extensions, type: :boolean, aliases: :e, desc: 'Add installed extensions', default: true # @param directory [String] # @return [void] - def config(directory = '.') + def config directory = '.' matches = [] if options[:extensions] Gem::Specification.each do |g| - if g.name.match(/^solargraph\-[A-Za-z0-9_\-]*?\-ext/) + if g.name.match(/^solargraph-[A-Za-z0-9_-]*?-ext/) require g.name matches.push g.name end @@ -84,7 +84,7 @@ def config(directory = '.') File.open(File.join(directory, '.solargraph.yml'), 'w') do |file| file.puts conf.to_yaml end - STDOUT.puts "Configuration file initialized." + STDOUT.puts 'Configuration file initialized.' end desc 'clear', 'Delete all cached documentation' @@ -93,7 +93,7 @@ def config(directory = '.') ) # @return [void] def clear - puts "Deleting all cached documentation (gems, core and stdlib)" + puts 'Deleting all cached documentation (gems, core and stdlib)' Solargraph::PinCache.clear end map 'clear-cache' => :clear @@ -109,7 +109,7 @@ def cache gem, version = nil # ' 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. @@ -174,7 +174,7 @@ def gems *names if names.empty? workspace.cache_all_for_workspace!($stdout, rebuild: options[:rebuild]) else - $stderr.puts("Caching these gems: #{names}") + warn("Caching these gems: #{names}") names.each do |name| if name == 'core' PinCache.cache_core(out: $stdout) if !PinCache.core? || options[:rebuild] @@ -195,7 +195,7 @@ def gems *names # @sg-ignore Need to add nil check here warn e.backtrace.join("\n") end - $stderr.puts "Documentation cached for #{names.count} gems." + warn "Documentation cached for #{names.count} gems." end end @@ -212,7 +212,7 @@ def reporters Type checking levels are normal, typed, strict, and strong. ) - option :level, type: :string, aliases: [:mode, :m, :l], desc: 'Type checking level', default: 'normal' + option :level, type: :string, aliases: %i[mode m l], desc: 'Type checking level', default: 'normal' option :directory, type: :string, aliases: :d, desc: 'The workspace directory', default: '.' # @return [void] def typecheck *files @@ -231,19 +231,22 @@ def typecheck *files files.map! { |file| File.realpath(file) } end filecount = 0 - time = Benchmark.measure { + time = Benchmark.measure do files.each do |file| - checker = TypeChecker.new(file, api_map: api_map, rules: rules, level: options[:level].to_sym, workspace: workspace) + checker = TypeChecker.new(file, api_map: api_map, rules: rules, level: options[:level].to_sym, + workspace: workspace) problems = checker.problems next if problems.empty? problems.sort! { |a, b| a.location.range.start.line <=> b.location.range.start.line } - puts problems.map { |prob| "#{prob.location.filename}:#{prob.location.range.start.line + 1} - #{prob.message}" }.join("\n") + puts problems.map { |prob| + "#{prob.location.filename}:#{prob.location.range.start.line + 1} - #{prob.message}" + }.join("\n") filecount += 1 probcount += problems.length end - } + end puts "Typecheck finished in #{time.real} seconds." - puts "#{probcount} problem#{probcount != 1 ? 's' : ''} found#{files.length != 1 ? " in #{filecount} of #{files.length} files" : ''}." + puts "#{probcount} problem#{'s' if probcount != 1} found#{" in #{filecount} of #{files.length} files" if files.length != 1}." # " exit 1 if probcount > 0 end @@ -262,26 +265,26 @@ def scan directory = File.realpath(options[:directory]) # @type [Solargraph::ApiMap, nil] api_map = nil - time = Benchmark.measure { + time = Benchmark.measure do api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout) # @sg-ignore flow sensitive typing should be able to handle redefinition api_map.pins.each do |pin| - begin - puts pin_description(pin) if options[:verbose] - pin.typify api_map - pin.probe api_map - rescue StandardError => e - # @todo to add nil check here - # @todo should warn on nil dereference below - STDERR.puts "Error testing #{pin_description(pin)} #{pin.location ? "at #{pin.location.filename}:#{pin.location.range.start.line + 1}" : ''}" - STDERR.puts "[#{e.class}]: #{e.message}" - # @todo Need to add nil check here - # @todo flow sensitive typing should be able to handle redefinition - STDERR.puts e.backtrace.join("\n") - exit 1 - end + puts pin_description(pin) if options[:verbose] + pin.typify api_map + pin.probe api_map + rescue StandardError => e + # @todo to add nil check here + # @todo should warn on nil dereference below + warn "Error testing #{pin_description(pin)} #{if pin.location + "at #{pin.location.filename}:#{pin.location.range.start.line + 1}" + end}" + warn "[#{e.class}]: #{e.message}" + # @todo Need to add nil check here + # @todo flow sensitive typing should be able to handle redefinition + warn e.backtrace.join("\n") + exit 1 end - } + end # @sg-ignore Need to add nil check here puts "Scanned #{directory} (#{api_map.pins.length} pins) in #{time.real} seconds." end @@ -298,10 +301,13 @@ def list desc 'pin [PATH]', 'Describe a 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 :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 + 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 @@ -326,7 +332,7 @@ def pin path pin = pins.first case pin when nil - $stderr.puts "Pin not found for path '#{path}'" + warn "Pin not found for path '#{path}'" exit 1 when Pin::Namespace if options[:references] @@ -362,11 +368,11 @@ def pin path option :memory, type: :boolean, aliases: :m, desc: 'Include memory usage counter', default: true # @param file [String, nil] # @return [void] - def profile(file = nil) + def profile file = nil begin require 'vernier' rescue LoadError - STDERR.puts "vernier gem not found. Install with: gem install vernier" + warn 'vernier gem not found. Install with: gem install vernier' return end @@ -385,16 +391,16 @@ def host.send_notification method, params puts "Notification: #{method} - #{params}" end - puts "Parsing and mapping source files..." + puts 'Parsing and mapping source files...' prepare_start = Time.now Vernier.profile(out: "#{options[:output_dir]}/parse_benchmark.json.gz", hooks: hooks) do - puts "Mapping libraries" + 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..." + puts 'Building the catalog...' catalog_start = Time.now Vernier.profile(out: "#{options[:output_dir]}/catalog_benchmark.json.gz", hooks: hooks) do host.catalog @@ -411,7 +417,7 @@ def host.send_notification method, params 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" + warn 'No Ruby files found in workspace' return end end @@ -432,7 +438,7 @@ def host.send_notification method, params } } ) - puts "Processing go-to-definition request..." + puts 'Processing go-to-definition request...' result = message.process puts "Result: #{result.inspect}" @@ -452,7 +458,7 @@ def host.send_notification method, params 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." + puts 'Or use https://rubygems.org/gems/profile-viewer to view them locally.' end private @@ -461,15 +467,15 @@ def host.send_notification method, params # @return [String] def pin_description pin desc = if pin.path.nil? || pin.path.empty? - if pin.closure - # @sg-ignore Need to add nil check here - "#{pin.closure.path} | #{pin.name}" - else - "#{pin.context.namespace} | #{pin.name}" - end - else - pin.path - end + if pin.closure + # @sg-ignore Need to add nil check here + "#{pin.closure.path} | #{pin.name}" + else + "#{pin.context.namespace} | #{pin.name}" + end + else + pin.path + end # @sg-ignore Need to add nil check here desc += " (#{pin.location.filename} #{pin.location.range.start.line})" if pin.location desc @@ -477,7 +483,7 @@ def pin_description pin # @param type [ComplexType, ComplexType::UniqueType] # @return [void] - def print_type(type) + def print_type type if options[:rbs] puts type.to_rbs else @@ -487,7 +493,7 @@ def print_type(type) # @param pin [Solargraph::Pin::Base] # @return [void] - def print_pin(pin) + def print_pin pin if options[:rbs] puts pin.to_rbs else diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 615269d73..1451692a5 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -66,7 +66,7 @@ def at range def from_to l1, c1, l2, c2 b = Solargraph::Position.line_char_to_offset(code, l1, c1) e = Solargraph::Position.line_char_to_offset(code, l2, c2) - code[b..e-1] + code[b..e - 1] end # Get the nearest node that contains the specified index. @@ -74,7 +74,7 @@ def from_to l1, c1, l2, c2 # @param line [Integer] # @param column [Integer] # @return [AST::Node] - def node_at(line, column) + def node_at line, column tree_at(line, column).first end @@ -84,7 +84,7 @@ def node_at(line, column) # @param line [Integer] # @param column [Integer] # @return [Array] - def tree_at(line, column) + def tree_at line, column position = Position.new(line, column) stack = [] inner_tree_at node, position, stack @@ -140,7 +140,7 @@ def string_at? position # @sg-ignore Need to add nil check here return true if node.type == :str && range.include?(position) && range.start != position # @sg-ignore Need to add nil check here - return true if [:STR, :str].include?(node.type) && range.include?(position) && range.start != position + return true if %i[STR str].include?(node.type) && range.include?(position) && range.start != position if node.type == :dstr inner = node_at(position.line, position.column) next if inner.nil? @@ -151,7 +151,7 @@ def string_at? position # @sg-ignore Need to add nil check here inner_code = at(Solargraph::Range.new(inner_range.start, position)) # @sg-ignore Need to add nil check here - return true if (inner.type == :dstr && inner_range.ending.character <= position.character) && !inner_code.end_with?('}') || + return true if (inner.type == :dstr && inner_range.ending.character <= position.character && !inner_code.end_with?('}')) || # @sg-ignore Need to add nil check here (inner.type != :dstr && inner_range.ending.line == position.line && position.character <= inner_range.ending.character && inner_code.end_with?('}')) end @@ -171,7 +171,7 @@ def string_ranges def comment_at? position comment_ranges.each do |range| return true if range.include?(position) || - (range.ending.line == position.line && range.ending.column < position.column) + (range.ending.line == position.line && range.ending.column < position.column) break if range.ending.line > position.line end false @@ -190,13 +190,13 @@ def error_ranges # @param node [Parser::AST::Node] # @return [String] - def code_for(node) + def code_for node rng = Range.from_node(node) # @sg-ignore Need to add nil check here b = Position.line_char_to_offset(code, rng.start.line, rng.start.column) # @sg-ignore Need to add nil check here e = Position.line_char_to_offset(code, rng.ending.line, rng.ending.column) - frag = code[b..e-1].to_s + frag = code[b..e - 1].to_s frag.strip.gsub(/,$/, '') end @@ -224,8 +224,8 @@ def location end FOLDING_NODE_TYPES = %i[ - class sclass module def defs if str dstr array while unless kwbegin hash block - ].freeze + class sclass module def defs if str dstr array while unless kwbegin hash block + ].freeze # Get an array of ranges that can be folded, e.g., the range of a class # definition or an if condition. @@ -293,9 +293,9 @@ def inner_folding_ranges top, result = [], parent = nil # @sg-ignore Translate to something flow sensitive typing understands range = Range.from_node(top) # @sg-ignore Need to add nil check here - if result.empty? || range.start.line > result.last.start.line + if (result.empty? || range.start.line > result.last.start.line) && !(range.ending.line - range.start.line < 2) # @sg-ignore Need to add nil check here - result.push range unless range.ending.line - range.start.line < 2 + result.push range end end # @sg-ignore Translate to something flow sensitive typing understands @@ -312,7 +312,7 @@ def stringify_comment_array comments ctxt = String.new('') started = false skip = nil - comments.lines.each { |l| + comments.lines.each do |l| # Trim the comment and minimum leading whitespace p = l.force_encoding('UTF-8').encode('UTF-8', invalid: :replace, replace: '?').gsub(/^#+/, '') if p.strip.empty? @@ -325,7 +325,7 @@ def stringify_comment_array comments ctxt.concat p[skip..-1] end started = true - } + end ctxt end @@ -372,11 +372,11 @@ def string_nodes_in n result = [] if Parser.is_ast_node?(n) # @sg-ignore Translate to something flow sensitive typing understands - if n.type == :str || n.type == :dstr || n.type == :STR || n.type == :DSTR + if %i[str dstr STR DSTR].include?(n.type) result.push n else # @sg-ignore Translate to something flow sensitive typing understands - n.children.each{ |c| result.concat string_nodes_in(c) } + n.children.each { |c| result.concat string_nodes_in(c) } end end result @@ -390,13 +390,12 @@ def inner_tree_at node, position, stack return if node.nil? here = Range.from_node(node) # @sg-ignore Need to add nil check here - if here.contain?(position) - stack.unshift node - node.children.each do |c| - next unless Parser.is_ast_node?(c) - next if c.loc.expression.nil? - inner_tree_at(c, position, stack) - end + return unless here.contain?(position) + stack.unshift node + node.children.each do |c| + next unless Parser.is_ast_node?(c) + next if c.loc.expression.nil? + inner_tree_at(c, position, stack) end end @@ -425,7 +424,7 @@ def finalize @node, @comments = Solargraph::Parser.parse_with_comments(@code, filename, 0) @parsed = true @repaired = @code - rescue Parser::SyntaxError, EncodingError => e + rescue Parser::SyntaxError, EncodingError @node = nil @comments = {} @parsed = false @@ -440,7 +439,7 @@ def finalize begin @node, @comments = Solargraph::Parser.parse_with_comments(@repaired, filename, 0) @parsed = true - rescue Parser::SyntaxError, EncodingError => e + rescue Parser::SyntaxError, EncodingError @node = nil @comments = {} @parsed = false @@ -453,7 +452,7 @@ def finalize # @param val [String] # @return [String] - def code=(val) + def code= val @code_lines = nil @finalized = false @code = val diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index a1e439ffb..b4cda9456 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -119,7 +119,9 @@ def define api_map, name_pin, locals pins = link.resolve(api_map, working_pin, locals) type = infer_from_definitions(pins, working_pin, api_map, locals) if type.undefined? - logger.debug { "Chain#define(links=#{links.map(&:desc)}, name_pin=#{name_pin.inspect}, locals=#{locals}) => [] - undefined type from #{link.desc}" } + logger.debug do + "Chain#define(links=#{links.map(&:desc)}, name_pin=#{name_pin.inspect}, locals=#{locals}) => [] - undefined type from #{link.desc}" + end return [] end # We continue to use the context from the head pin, in case @@ -128,7 +130,9 @@ def define api_map, name_pin, locals # for the binder, as this is chaining off of it, and the # binder is now the lhs of the rhs we are evaluating. working_pin = Pin::ProxyType.anonymous(name_pin.context, binder: type, closure: name_pin, source: :chain) - logger.debug { "Chain#define(links=#{links.map(&:desc)}, name_pin=#{name_pin.inspect}, locals=#{locals}) - after processing #{link.desc}, new working_pin=#{working_pin} with binder #{working_pin.binder}" } + logger.debug do + "Chain#define(links=#{links.map(&:desc)}, name_pin=#{name_pin.inspect}, locals=#{locals}) - after processing #{link.desc}, new working_pin=#{working_pin} with binder #{working_pin.binder}" + end end links.last.last_context = working_pin links.last.resolve(api_map, working_pin, locals) @@ -150,7 +154,9 @@ def infer api_map, name_pin, locals @@inference_cache = {} end out = infer_uncached(api_map, name_pin, locals).downcast_to_literal_if_possible - logger.debug { "Chain#infer() - caching result - cache_key_hash=#{cache_key.hash}, links.map(&:hash)=#{links.map(&:hash)}, links=#{links}, cache_key.map(&:hash) = #{cache_key.map(&:hash)}, cache_key=#{cache_key}" } + logger.debug do + "Chain#infer() - caching result - cache_key_hash=#{cache_key.hash}, links.map(&:hash)=#{links.map(&:hash)}, links=#{links}, cache_key.map(&:hash) = #{cache_key.map(&:hash)}, cache_key=#{cache_key}" + end @@inference_cache[cache_key] = out end @@ -161,12 +167,16 @@ def infer api_map, name_pin, locals def infer_uncached api_map, name_pin, locals pins = define(api_map, name_pin, locals) if pins.empty? - logger.debug { "Chain#infer_uncached(links=#{links.map(&:desc)}, locals=#{locals.map(&:desc)}) => undefined - no pins" } + logger.debug do + "Chain#infer_uncached(links=#{links.map(&:desc)}, locals=#{locals.map(&:desc)}) => undefined - no pins" + end return ComplexType::UNDEFINED end type = infer_from_definitions(pins, links.last.last_context, api_map, locals) out = maybe_nil(type) - logger.debug { "Chain#infer_uncached(links=#{self.links.map(&:desc)}, locals=#{locals.map(&:desc)}, name_pin=#{name_pin}, name_pin.closure=#{name_pin.closure.inspect}, name_pin.binder=#{name_pin.binder}) => #{out.rooted_tags.inspect}" } + logger.debug do + "Chain#infer_uncached(links=#{links.map(&:desc)}, locals=#{locals.map(&:desc)}, name_pin=#{name_pin}, name_pin.closure=#{name_pin.closure.inspect}, name_pin.binder=#{name_pin.binder}) => #{out.rooted_tags.inspect}" + end out end @@ -245,17 +255,13 @@ def infer_from_definitions pins, name_pin, api_map, locals end # Limit method inference recursion - if @@inference_depth >= 10 && pins.first.is_a?(Pin::Method) - return ComplexType::UNDEFINED - end + return ComplexType::UNDEFINED if @@inference_depth >= 10 && pins.first.is_a?(Pin::Method) @@inference_depth += 1 # @param pin [Pin::Base] unresolved_pins.each do |pin| # Avoid infinite recursion - if @@inference_stack.include?(pin.identity) - next - end + next if @@inference_stack.include?(pin.identity) @@inference_stack.push(pin.identity) type = pin.probe(api_map) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index f66f95f53..5f4f57e13 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -70,9 +70,7 @@ def resolve api_map, name_pin, locals [stack.first].compact end # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array - if !api_map.loose_unions && pin_groups.any? { |pins| pins.empty? } - pin_groups = [] - end + pin_groups = [] if !api_map.loose_unions && pin_groups.any? { |pins| pins.empty? } pins = pin_groups.flatten.uniq(&:path) return [] if pins.empty? inferred_pins(pins, api_map, name_pin, locals) @@ -129,38 +127,42 @@ def inferred_pins pins, api_map, name_pin, locals if ol.block && with_block? block_atypes = ol.block.parameters.map(&:return_type) # @todo Need to add nil check here - if block.links.map(&:class) == [BlockSymbol] - # like the bar in foo(&:bar) - blocktype = block_symbol_call_type(api_map, name_pin.context, block_atypes, locals) - else - blocktype = block_call_type(api_map, name_pin, locals) - end + blocktype = if block.links.map(&:class) == [BlockSymbol] + # like the bar in foo(&:bar) + block_symbol_call_type(api_map, name_pin.context, block_atypes, locals) + else + block_call_type(api_map, name_pin, locals) + end end # @type new_signature_pin [Pin::Signature] - new_signature_pin = ol.resolve_generics_from_context_until_complete(ol.generics, atypes, nil, nil, blocktype) + new_signature_pin = ol.resolve_generics_from_context_until_complete(ol.generics, atypes, nil, nil, + blocktype) new_return_type = new_signature_pin.return_type - if head? - # If we're at the head of the chain, we called a - # method somewhere that marked itself as returning - # self. Given we didn't invoke this on an object, - # this must be a method in this same class - so we - # use our own self type - self_type = name_pin.context - else - # if we're past the head in the chain, whatever the - # type of the lhs side is what 'self' will be in its - # declaration - we can't just use the type of the - # method pin, as this might be a subclass of the - # place where the method is defined - self_type = name_pin.binder - end + self_type = if head? + # If we're at the head of the chain, we called a + # method somewhere that marked itself as returning + # self. Given we didn't invoke this on an object, + # this must be a method in this same class - so we + # use our own self type + name_pin.context + else + # if we're past the head in the chain, whatever the + # type of the lhs side is what 'self' will be in its + # declaration - we can't just use the type of the + # method pin, as this might be a subclass of the + # place where the method is defined + name_pin.binder + end # This same logic applies to the YARD work done by # 'with_params()'. # # qualify(), however, happens in the namespace where # the docs were written - from the method pin. # @todo Need to add nil check here - type = with_params(new_return_type.self_to_type(self_type), self_type).qualify(api_map, *p.gates) if new_return_type.defined? + if new_return_type.defined? + type = with_params(new_return_type.self_to_type(self_type), self_type).qualify(api_map, + *p.gates) + end type ||= ComplexType::UNDEFINED end break if type.defined? @@ -178,8 +180,10 @@ def inferred_pins pins, api_map, name_pin, locals end p end - logger.debug { "Call#inferred_pins(name_pin.binder=#{name_pin.binder}, word=#{word}, pins=#{pins.map(&:desc)}, name_pin=#{name_pin}) - result=#{result}" } - out = result.map do |pin| + logger.debug do + "Call#inferred_pins(name_pin.binder=#{name_pin.binder}, word=#{word}, pins=#{pins.map(&:desc)}, name_pin=#{name_pin}) - result=#{result}" + end + result.map do |pin| if pin.path == 'Class#new' && name_pin.binder.tag != 'Class' reduced_context = name_pin.binder.reduce_class_type pin.proxy(reduced_context) @@ -235,7 +239,7 @@ def process_directive pin, api_map, context, locals # @param locals [::Array] # @return [Pin::ProxyType] def inner_process_macro pin, macro, api_map, context, locals - vals = arguments.map{ |c| Pin::ProxyType.anonymous(c.infer(api_map, pin, locals), source: :chain) } + vals = arguments.map { |c| Pin::ProxyType.anonymous(c.infer(api_map, pin, locals), source: :chain) } txt = macro.tag.text.clone # @sg-ignore Need to add nil check here if txt.empty? && macro.tag.name @@ -261,7 +265,7 @@ def inner_process_macro pin, macro, api_map, context, locals # @param context [ComplexType] # @return [ComplexType, nil] def extra_return_type docstring, context - if docstring.has_tag?('return_single_parameter') #&& context.subtypes.one? + if docstring.has_tag?('return_single_parameter') # && context.subtypes.one? return context.subtypes.first || ComplexType::UNDEFINED elsif docstring.has_tag?('return_value_parameter') && context.value_types.one? return context.value_types.first @@ -271,7 +275,7 @@ def extra_return_type docstring, context # @param name_pin [Pin::Base] # @return [Pin::Method, nil] - def find_method_pin(name_pin) + def find_method_pin name_pin method_pin = name_pin until method_pin.is_a?(Pin::Method) # @sg-ignore Need to support this in flow sensitive typing @@ -288,7 +292,7 @@ def super_pins api_map, name_pin method_pin = find_method_pin(name_pin) return [] if method_pin.nil? pins = api_map.get_method_stack(method_pin.namespace, method_pin.name, scope: method_pin.context.scope) - pins.reject{|p| p.path == name_pin.path} + pins.reject { |p| p.path == name_pin.path } end # @param api_map [ApiMap] @@ -325,7 +329,7 @@ def fix_block_pass # @param block_parameter_types [::Array] # @param locals [::Array] # @return [ComplexType, nil] - def block_symbol_call_type(api_map, context, block_parameter_types, locals) + def block_symbol_call_type api_map, context, block_parameter_types, locals # Ruby's shorthand for sending the passed in method name # to the first yield parameter with no arguments # @sg-ignore Need to add nil check here @@ -343,7 +347,7 @@ def block_symbol_call_type(api_map, context, block_parameter_types, locals) # @param api_map [ApiMap] # @return [Pin::Block, nil] - def find_block_pin(api_map) + def find_block_pin api_map # @sg-ignore Need to add nil check here node_location = Solargraph::Location.from_node(block.node) return if node_location.nil? @@ -356,7 +360,7 @@ def find_block_pin(api_map) # @param name_pin [Pin::Base] # @param locals [::Array] # @return [ComplexType, nil] - def block_call_type(api_map, name_pin, locals) + def block_call_type api_map, name_pin, locals return nil unless with_block? block_pin = find_block_pin(api_map) diff --git a/lib/solargraph/source/chain/class_variable.rb b/lib/solargraph/source/chain/class_variable.rb index a804d89e5..f50028ffa 100644 --- a/lib/solargraph/source/chain/class_variable.rb +++ b/lib/solargraph/source/chain/class_variable.rb @@ -5,7 +5,7 @@ class Source class Chain class ClassVariable < Link def resolve api_map, name_pin, locals - api_map.get_class_variable_pins(name_pin.context.namespace).select{|p| p.name == word} + api_map.get_class_variable_pins(name_pin.context.namespace).select { |p| p.name == word } end end end diff --git a/lib/solargraph/source/chain/global_variable.rb b/lib/solargraph/source/chain/global_variable.rb index 0842803a9..335b8e42c 100644 --- a/lib/solargraph/source/chain/global_variable.rb +++ b/lib/solargraph/source/chain/global_variable.rb @@ -5,7 +5,7 @@ class Source class Chain class GlobalVariable < Link def resolve api_map, name_pin, locals - api_map.get_global_variable_pins.select{|p| p.name == word} + api_map.get_global_variable_pins.select { |p| p.name == word } end end end diff --git a/lib/solargraph/source/chain/if.rb b/lib/solargraph/source/chain/if.rb index de578419e..454a85647 100644 --- a/lib/solargraph/source/chain/if.rb +++ b/lib/solargraph/source/chain/if.rb @@ -19,7 +19,8 @@ def initialize links def resolve api_map, name_pin, locals types = @links.map { |link| link.infer(api_map, name_pin, locals) } - [Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.try_parse(types.map(&:tag).uniq.join(', ')), source: :chain)] + [Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.try_parse(types.map(&:tag).uniq.join(', ')), + source: :chain)] end end end diff --git a/lib/solargraph/source/chain/instance_variable.rb b/lib/solargraph/source/chain/instance_variable.rb index 2cb1a0ef8..60df51bfe 100644 --- a/lib/solargraph/source/chain/instance_variable.rb +++ b/lib/solargraph/source/chain/instance_variable.rb @@ -18,7 +18,9 @@ def initialize word, node, location # type ::Array<::Solargraph::Pin::BaseVariable, ::NilClass> # for Solargraph::Source::Chain::InstanceVariable#resolve def resolve api_map, name_pin, locals - ivars = api_map.get_instance_variable_pins(name_pin.context.namespace, name_pin.context.scope).select{|p| p.name == word} + ivars = api_map.get_instance_variable_pins(name_pin.context.namespace, name_pin.context.scope).select do |p| + p.name == word + end out = api_map.var_at_location(ivars, word, name_pin, location) [out].compact end diff --git a/lib/solargraph/source/chain/link.rb b/lib/solargraph/source/chain/link.rb index 56df1d135..dcc5789e1 100644 --- a/lib/solargraph/source/chain/link.rb +++ b/lib/solargraph/source/chain/link.rb @@ -49,7 +49,7 @@ def to_s end def inspect - "#<#{self.class} - `#{self.desc}`>" + "#<#{self.class} - `#{desc}`>" end def head? diff --git a/lib/solargraph/source/chain/literal.rb b/lib/solargraph/source/chain/literal.rb index 59afa32b3..e8d9b753c 100644 --- a/lib/solargraph/source/chain/literal.rb +++ b/lib/solargraph/source/chain/literal.rb @@ -21,7 +21,7 @@ def initialize type, node elsif node.type == :false @value = false # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check - elsif [:int, :sym].include?(node.type) + elsif %i[int sym].include?(node.type) # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check @value = node.children.first end diff --git a/lib/solargraph/source/chain/variable.rb b/lib/solargraph/source/chain/variable.rb index 975fbf6f5..8bf424e3b 100644 --- a/lib/solargraph/source/chain/variable.rb +++ b/lib/solargraph/source/chain/variable.rb @@ -5,7 +5,9 @@ class Source class Chain class Variable < Link def resolve api_map, name_pin, locals - api_map.get_instance_variable_pins(name_pin.context.namespace, name_pin.context.scope).select{|p| p.name == word} + api_map.get_instance_variable_pins(name_pin.context.namespace, name_pin.context.scope).select do |p| + p.name == word + end end end end diff --git a/lib/solargraph/source/chain/z_super.rb b/lib/solargraph/source/chain/z_super.rb index 92f3bc313..f6ef77106 100644 --- a/lib/solargraph/source/chain/z_super.rb +++ b/lib/solargraph/source/chain/z_super.rb @@ -20,7 +20,7 @@ def initialize word, with_block = false # @param name_pin [Pin::Base] # @param locals [::Array] def resolve api_map, name_pin, locals - return super_pins(api_map, name_pin) + super_pins(api_map, name_pin) end end end diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index eb7a550c0..97df1c140 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -82,7 +82,7 @@ def commit text, insert start_offset = Position.to_offset(text, range.start) # @sg-ignore Need to add nil check here end_offset = Position.to_offset(text, range.ending) - (start_offset == 0 ? '' : text[0..start_offset-1].to_s) + normalize(insert) + text[end_offset..-1].to_s + (start_offset == 0 ? '' : text[0..start_offset - 1].to_s) + normalize(insert) + text[end_offset..-1].to_s end end end diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 5ee9ac4b8..0b3b40606 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -39,11 +39,13 @@ def word # @return [String] def start_of_word @start_of_word ||= begin - match = source.code[0..offset-1].to_s.match(start_word_pattern) + match = source.code[0..offset - 1].to_s.match(start_word_pattern) result = (match ? match[0] : '') # Including the preceding colon if the word appears to be a symbol # @sg-ignore Need to add nil check here - result = ":#{result}" if source.code[0..offset-result.length-1].end_with?(':') and !source.code[0..offset-result.length-1].end_with?('::') + if source.code[0..offset - result.length - 1].end_with?(':') and !source.code[0..offset - result.length - 1].end_with?('::') + result = ":#{result}" + end result end end @@ -62,7 +64,7 @@ def end_of_word # @return [Boolean] def start_of_constant? - source.code[offset-2, 2] == '::' + source.code[offset - 2, 2] == '::' end # The range of the word at the current position. @@ -126,20 +128,18 @@ def node # @return [Position] def node_position - @node_position ||= begin - if start_of_word.empty? - # @sg-ignore Need to add nil check here - match = source.code[0, offset].match(/\s*(\.|:+)\s*$/) - if match - # @sg-ignore Need to add nil check here - Position.from_offset(source.code, offset - match[0].length) - else - position - end - else - position - end - end + @node_position ||= if start_of_word.empty? + # @sg-ignore Need to add nil check here + match = source.code[0, offset].match(/\s*(\.|:+)\s*$/) + if match + # @sg-ignore Need to add nil check here + Position.from_offset(source.code, offset - match[0].length) + else + position + end + else + position + end end # @return [Parser::AST::Node, nil] diff --git a/lib/solargraph/source/encoding_fixes.rb b/lib/solargraph/source/encoding_fixes.rb index 2ed70037c..0bb8d7f7e 100644 --- a/lib/solargraph/source/encoding_fixes.rb +++ b/lib/solargraph/source/encoding_fixes.rb @@ -10,13 +10,12 @@ module EncodingFixes # @param string [String] # @return [String] def normalize string - begin - string.dup.force_encoding('UTF-8') - rescue ::Encoding::CompatibilityError, ::Encoding::UndefinedConversionError, ::Encoding::InvalidByteSequenceError => e - # @todo Improve error handling - Solargraph::Logging.logger.warn "Normalize error: #{e.message}" - string - end + string.dup.force_encoding('UTF-8') + rescue ::Encoding::CompatibilityError, ::Encoding::UndefinedConversionError, + ::Encoding::InvalidByteSequenceError => e + # @todo Improve error handling + Solargraph::Logging.logger.warn "Normalize error: #{e.message}" + string end end end diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index aeffbeeec..5c235058a 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -32,10 +32,22 @@ def initialize source, position # @return [Source::Chain] def chain # Special handling for files that end with an integer and a period - return Chain.new([Chain::Literal.new('Integer', Integer(phrase[0..-2])), Chain::UNDEFINED_CALL]) if phrase =~ /^[0-9]+\.$/ + if phrase =~ /^[0-9]+\.$/ + return Chain.new([Chain::Literal.new('Integer', Integer(phrase[0..-2])), + Chain::UNDEFINED_CALL]) + end # @sg-ignore Need to add nil check here - return Chain.new([Chain::Literal.new('Symbol', phrase[1..].to_sym)]) if phrase.start_with?(':') && !phrase.start_with?('::') - return SourceChainer.chain(source, Position.new(position.line, position.character + 1)) if end_of_phrase.strip == '::' && source.code[Position.to_offset(source.code, position)].to_s.match?(/[a-z]/i) + if phrase.start_with?(':') && !phrase.start_with?('::') + return Chain.new([Chain::Literal.new('Symbol', + phrase[1..].to_sym)]) + end + if end_of_phrase.strip == '::' && source.code[Position.to_offset( + source.code, position + )].to_s.match?(/[a-z]/i) + return SourceChainer.chain(source, + Position.new(position.line, + position.character + 1)) + end begin return Chain.new([]) if phrase.end_with?('..') # @type [::Parser::AST::Node, nil] @@ -52,7 +64,12 @@ def chain elsif source.repaired? node = Parser.parse(fixed_phrase, source.filename, fixed_position.line) else - node, parent = source.tree_at(fixed_position.line, fixed_position.column)[0..2] unless source.error_ranges.any?{|r| r.nil? || r.include?(fixed_position)} + unless source.error_ranges.any? do |r| + r.nil? || r.include?(fixed_position) + end + node, parent = source.tree_at(fixed_position.line, + fixed_position.column)[0..2] + end # Exception for positions that chain literal nodes in unsynchronized sources node = nil unless source.synchronized? || !Parser.infer_literal_node_type(node).nil? node = Parser.parse(fixed_phrase, source.filename, fixed_position.line) if node.nil? @@ -86,13 +103,13 @@ def chain # @sg-ignore Need to add nil check here # @return [String] def phrase - @phrase ||= source.code[signature_data..offset-1] + @phrase ||= source.code[signature_data..offset - 1] end # @sg-ignore Need to add nil check here # @return [String] def fixed_phrase - @fixed_phrase ||= phrase[0..-(end_of_phrase.length+1)] + @fixed_phrase ||= phrase[0..-(end_of_phrase.length + 1)] end # @return [Position] @@ -144,7 +161,7 @@ def get_signature_data_at index brackets = 0 squares = 0 parens = 0 - index -=1 + index -= 1 in_whitespace = false while index >= 0 pos = Position.from_offset(@source.code, index) @@ -155,21 +172,19 @@ def get_signature_data_at index if brackets.zero? and parens.zero? and squares.zero? and [' ', "\r", "\n", "\t"].include?(char) in_whitespace = true else - if brackets.zero? and parens.zero? and squares.zero? and in_whitespace + # @sg-ignore Need to add nil check here + if brackets.zero? and parens.zero? and squares.zero? and in_whitespace && !(char == '.' or @source.code[index + 1..-1].strip.start_with?('.')) + @source.code[index + 1..-1] # @sg-ignore Need to add nil check here - unless char == '.' or @source.code[index+1..-1].strip.start_with?('.') - old = @source.code[index+1..-1] - # @sg-ignore Need to add nil check here - nxt = @source.code[index+1..-1].lstrip - # @sg-ignore Need to add nil check here - index += (@source.code[index+1..-1].length - @source.code[index+1..-1].lstrip.length) - break - end + @source.code[index + 1..-1].lstrip + # @sg-ignore Need to add nil check here + index += (@source.code[index + 1..-1].length - @source.code[index + 1..-1].lstrip.length) + break end if char == ')' - parens -=1 + parens -= 1 elsif char == ']' - squares -=1 + squares -= 1 elsif char == '}' brackets -= 1 elsif char == '(' @@ -185,9 +200,7 @@ def get_signature_data_at index break if char == '$' if char == '@' index -= 1 - if @source.code[index, 1] == '@' - index -= 1 - end + index -= 1 if @source.code[index, 1] == '@' break end elsif parens == 1 || brackets == 1 || squares == 1 diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 714970f21..b22a058f9 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -62,7 +62,9 @@ def pins_by_class klass # # @return [Integer] def api_hash - @api_hash ||= (pins_by_class(Pin::Constant) + pins_by_class(Pin::Namespace).select { |pin| pin.namespace.to_s > '' } + pins_by_class(Pin::Reference) + pins_by_class(Pin::Method).map(&:node) + locals).hash + @api_hash ||= (pins_by_class(Pin::Constant) + pins_by_class(Pin::Namespace).select do |pin| + pin.namespace.to_s > '' + end + pins_by_class(Pin::Reference) + pins_by_class(Pin::Method).map(&:node) + locals).hash end # @return [String, nil] @@ -144,7 +146,7 @@ def references name # @param location [Location] # @return [Array] - def locals_at(location) + def locals_at location return [] if location.filename != filename closure = locate_closure_pin(location.range.start.line, location.range.start.character) locals.select { |pin| pin.visible_at?(closure, location) } @@ -209,7 +211,9 @@ def _locate_pin line, character, *klasses # there's probably a better way to handle it next if pin.is_a?(Pin::Method) && pin.attribute? # @sg-ignore Need to add nil check here - found = pin if (klasses.empty? || klasses.any? { |kls| pin.is_a?(kls) } ) && pin.location.range.contain?(position) + found = pin if (klasses.empty? || klasses.any? do |kls| + pin.is_a?(kls) + end) && pin.location.range.contain?(position) # @sg-ignore Need to add nil check here break if pin.location.range.start.line > line end diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index b2b4d2b5d..2e48b0c89 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -13,7 +13,9 @@ def initialize api_map, cursor @cursor = cursor closure_pin = closure # @sg-ignore Need to add nil check here - closure_pin.rebind(api_map) if closure_pin.is_a?(Pin::Block) && !Solargraph::Range.from_node(closure_pin.receiver).contain?(cursor.range.start) + if closure_pin.is_a?(Pin::Block) && !Solargraph::Range.from_node(closure_pin.receiver).contain?(cursor.range.start) + closure_pin.rebind(api_map) + end end # @return [Array] Relevant pins for infering the type of the Cursor's position @@ -22,7 +24,11 @@ def define result = cursor.chain.define(api_map, closure, locals) result.concat file_global_methods # @sg-ignore Need to add nil check here - result.concat((source_map.pins + source_map.locals).select{ |p| p.name == cursor.word && p.location.range.contain?(cursor.position) }) if result.empty? + if result.empty? + result.concat((source_map.pins + source_map.locals).select do |p| + p.name == cursor.word && p.location.range.contain?(cursor.position) + end) + end result end @@ -34,7 +40,9 @@ def types # @return [Completion] def complete return package_completions([]) if !source_map.source.parsed? || cursor.string? - return package_completions(api_map.get_symbols) if cursor.chain.literal? && cursor.chain.links.last.word == '' + if cursor.chain.literal? && cursor.chain.links.last.word == '' + return package_completions(api_map.get_symbols) + end return Completion.new([], cursor.range) if cursor.chain.literal? if cursor.comment? tag_complete @@ -128,12 +136,11 @@ def complete_keyword_parameters next unless param.keyword? result.push Pin::KeywordParam.new(pin.location, "#{param.name}:") end - if !pin.parameters.empty? && pin.parameters.last.kwrestarg? - pin.docstring.tags(:param).each do |tag| - next if done.include?(tag.name) - done.push tag.name - result.push Pin::KeywordParam.new(pin.location, "#{tag.name}:") - end + next unless !pin.parameters.empty? && pin.parameters.last.kwrestarg? + pin.docstring.tags(:param).each do |tag| + next if done.include?(tag.name) + done.push tag.name + result.push Pin::KeywordParam.new(pin.location, "#{tag.name}:") end end result @@ -143,10 +150,10 @@ def complete_keyword_parameters # @return [Completion] def package_completions result frag_start = cursor.start_of_word.to_s.downcase - filtered = result.uniq(&:name).select { |s| + filtered = result.uniq(&:name).select do |s| s.name.downcase.start_with?(frag_start) && - (!s.is_a?(Pin::Method) || s.name.match(/^[a-z0-9_]+(\!|\?|=)?$/i)) - } + (!s.is_a?(Pin::Method) || s.name.match(/^[a-z0-9_]+(!|\?|=)?$/i)) + end Completion.new(filtered, cursor.range) end @@ -154,7 +161,7 @@ def package_completions result def tag_complete result = [] # @sg-ignore Need to add nil check here - match = source_map.code[0..cursor.offset-1].match(/[\[<, ]([a-z0-9_:]*)\z/i) + match = source_map.code[0..cursor.offset - 1].match(/[\[<, ]([a-z0-9_:]*)\z/i) if match # @sg-ignore Need to add nil check here full = match[1] @@ -170,7 +177,7 @@ def tag_complete end else # @sg-ignore Need to add nil check here - result.concat api_map.get_constants('', full.end_with?('::') ? '' : context_pin.full_context.namespace, *gates) #.select { |pin| pin.name.start_with?(full) } + result.concat api_map.get_constants('', full.end_with?('::') ? '' : context_pin.full_context.namespace, *gates) # .select { |pin| pin.name.start_with?(full) } end end package_completions(result) @@ -183,25 +190,24 @@ def code_complete if cursor.chain.constant? || cursor.start_of_constant? full = cursor.chain.links.first.word type = if cursor.chain.undefined? - cursor.chain.base.infer(api_map, context_pin, locals) - else - if full.include?('::') && cursor.chain.links.length == 1 - # @sg-ignore Need to add nil check here - ComplexType.try_parse(full.split('::')[0..-2].join('::')) - elsif cursor.chain.links.length > 1 - ComplexType.try_parse(full) - else - ComplexType::UNDEFINED - end - end + cursor.chain.base.infer(api_map, context_pin, locals) + elsif full.include?('::') && cursor.chain.links.length == 1 + # @sg-ignore Need to add nil check here + ComplexType.try_parse(full.split('::')[0..-2].join('::')) + elsif cursor.chain.links.length > 1 + ComplexType.try_parse(full) + else + ComplexType::UNDEFINED + end if type.undefined? if full.include?('::') result.concat api_map.get_constants(full, *gates) else - result.concat api_map.get_constants('', cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates) #.select { |pin| pin.name.start_with?(full) } + result.concat api_map.get_constants('', cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates) # .select { |pin| pin.name.start_with?(full) } end else - result.concat api_map.get_constants(type.namespace, cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates) + result.concat api_map.get_constants(type.namespace, + cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates) end else type = cursor.chain.base.infer(api_map, closure, locals) @@ -210,14 +216,16 @@ def code_complete if cursor.word.start_with?('@@') return package_completions(api_map.get_class_variable_pins(context_pin.full_context.namespace)) elsif cursor.word.start_with?('@') - return package_completions(api_map.get_instance_variable_pins(closure.full_context.namespace, closure.context.scope)) + return package_completions(api_map.get_instance_variable_pins(closure.full_context.namespace, + closure.context.scope)) elsif cursor.word.start_with?('$') return package_completions(api_map.get_global_variable_pins) end result.concat locals result.concat file_global_methods unless closure.binder.namespace.empty? result.concat api_map.get_constants(context_pin.context.namespace, *gates) - result.concat api_map.get_methods(closure.binder.namespace, scope: closure.binder.scope, visibility: [:public, :private, :protected]) + result.concat api_map.get_methods(closure.binder.namespace, scope: closure.binder.scope, + visibility: %i[public private protected]) result.concat api_map.get_methods('Kernel') result.concat api_map.keyword_pins.to_a end diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index e6b8f6878..5d8d66a9c 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -12,7 +12,7 @@ class Mapper private_class_method :new - DIRECTIVE_REGEXP = /(@\!method|@\!attribute|@\!visibility|@\!domain|@\!macro|@\!parse|@\!override)/.freeze + DIRECTIVE_REGEXP = /(@!method|@!attribute|@!visibility|@!domain|@!macro|@!parse|@!override)/ # Generate the data. # @@ -29,10 +29,10 @@ def map source @locals.each { |l| l.source = :code } process_comment_directives [@pins, @locals] - # rescue Exception => e - # Solargraph.logger.warn "Error mapping #{source.filename}: [#{e.class}] #{e.message}" - # Solargraph.logger.warn e.backtrace.join("\n") - # [[], []] + # rescue Exception => e + # Solargraph.logger.warn "Error mapping #{source.filename}: [#{e.class}] #{e.message}" + # Solargraph.logger.warn e.backtrace.join("\n") + # [[], []] end # @param filename [String] @@ -63,9 +63,9 @@ def pins # @param position [Solargraph::Position] # @return [Solargraph::Pin::Closure] - def closure_at(position) + def closure_at position # @sg-ignore Need to add nil check here - pins.select{|pin| pin.is_a?(Pin::Closure) and pin.location.range.contain?(position)}.last + pins.select { |pin| pin.is_a?(Pin::Closure) and pin.location.range.contain?(position) }.last end # @param source_position [Position] @@ -116,9 +116,7 @@ def process_directive source_position, comment_position, directive namespace = closure_at(source_position) || @pins.first # @todo Missed nil violation # @todo Need to add nil check here - if namespace.location.range.start.line < comment_position.line - namespace = closure_at(comment_position) - end + namespace = closure_at(comment_position) if namespace.location.range.start.line < comment_position.line begin src = Solargraph::Source.load_string("def #{directive.tag.name};end", @source.filename) region = Parser::Region.new(source: src, closure: namespace) @@ -128,19 +126,20 @@ def process_directive source_position, comment_position, directive return if gen_pin.nil? # Move the location to the end of the line so it gets recognized # as originating from a comment - shifted = Solargraph::Position.new(comment_position.line, @code.lines[comment_position.line].to_s.chomp.length) + shifted = Solargraph::Position.new(comment_position.line, + @code.lines[comment_position.line].to_s.chomp.length) # @todo: Smelly instance variable access gen_pin.instance_variable_set(:@comments, docstring.all.to_s) gen_pin.instance_variable_set(:@location, Solargraph::Location.new(@filename, Range.new(shifted, shifted))) gen_pin.instance_variable_set(:@explicit, false) @pins.push gen_pin - rescue Parser::SyntaxError => e + rescue Parser::SyntaxError # @todo Handle error in directive end when 'attribute' return if directive.tag.name.nil? namespace = closure_at(source_position) - t = (directive.tag.types.nil? || directive.tag.types.empty?) ? nil : directive.tag.types.flatten.join('') + t = directive.tag.types.nil? || directive.tag.types.empty? ? nil : directive.tag.types.flatten.join('') if t.nil? || t.include?('r') pins.push Solargraph::Pin::Method.new( location: location, @@ -166,34 +165,36 @@ def process_directive source_position, comment_position, directive source: :source_map ) pins.push method_pin - method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last, source: :source_map) + method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last, + source: :source_map) if pins.last.return_type.defined? - pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.to_s.split(', '), 'value') + pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.to_s.split(', '), + 'value') end end when 'visibility' - kind = directive.tag.text&.to_sym - # @sg-ignore Need to look at Tuple#include? handling - return unless [:private, :protected, :public].include?(kind) + kind = directive.tag.text&.to_sym + # @sg-ignore Need to look at Tuple#include? handling + return unless %i[private protected public].include?(kind) - name = directive.tag.name - closure = closure_at(source_position) || @pins.first - # @todo Missed nil violation - # @todo Need to add nil check here - if closure.location.range.start.line < comment_position.line - closure = closure_at(comment_position) + name = directive.tag.name + closure = closure_at(source_position) || @pins.first + # @todo Missed nil violation + # @todo Need to add nil check here + closure = closure_at(comment_position) if closure.location.range.start.line < comment_position.line + if closure.is_a?(Pin::Method) && no_empty_lines?(comment_position.line, source_position.line) + # @todo Smelly instance variable access + closure.instance_variable_set(:@visibility, kind) + else + matches = pins.select do |pin| + pin.is_a?(Pin::Method) && pin.name == name && pin.namespace == namespace && pin.context.scope == namespace.is_a?(Pin::Singleton) ? :class : :instance end - if closure.is_a?(Pin::Method) && no_empty_lines?(comment_position.line, source_position.line) + matches.each do |pin| # @todo Smelly instance variable access - closure.instance_variable_set(:@visibility, kind) - else - matches = pins.select{ |pin| pin.is_a?(Pin::Method) && pin.name == name && pin.namespace == namespace && pin.context.scope == namespace.is_a?(Pin::Singleton) ? :class : :instance } - matches.each do |pin| - # @todo Smelly instance variable access - pin.instance_variable_set(:@visibility, kind) - end + pin.instance_variable_set(:@visibility, kind) end + end when 'parse' begin @@ -204,10 +205,10 @@ def process_directive source_position, comment_position, directive # @todo These pins may need to be marked not explicit index = @pins.length loff = if @code.lines[comment_position.line].strip.end_with?('@!parse') - comment_position.line + 1 - else - comment_position.line - end + comment_position.line + 1 + else + comment_position.line + end locals = [] ivars = [] Parser.process_node(src.node, region, @pins, locals, ivars) @@ -218,7 +219,7 @@ def process_directive source_position, comment_position, directive p.location.range.start.instance_variable_set(:@line, p.location.range.start.line + loff) p.location.range.ending.instance_variable_set(:@line, p.location.range.ending.line + loff) end - rescue Parser::SyntaxError => e + rescue Parser::SyntaxError # @todo Handle parser errors in !parse directives end when 'domain' @@ -237,7 +238,7 @@ def process_directive source_position, comment_position, directive # @param line1 [Integer] # @param line2 [Integer] # @sg-ignore Need to add nil check here - def no_empty_lines?(line1, line2) + def no_empty_lines? line1, line2 # @sg-ignore Need to add nil check here @code.lines[line1..line2].none? { |line| line.strip.empty? } end @@ -248,7 +249,7 @@ def remove_inline_comment_hashes comment ctxt = '' num = nil started = false - comment.lines.each { |l| + comment.lines.each do |l| # Trim the comment and minimum leading whitespace p = l.encode('UTF-8', invalid: :replace, replace: '?').gsub(/^#+/, '') if num.nil? && !p.strip.empty? @@ -260,7 +261,7 @@ def remove_inline_comment_hashes comment num = cur if cur < num end ctxt += "#{p[num..-1]}" if started - } + end ctxt end @@ -269,7 +270,14 @@ def process_comment_directives return unless @code.encode('UTF-8', invalid: :replace, replace: '?') =~ DIRECTIVE_REGEXP code_lines = @code.lines @source.associated_comments.each do |line, comments| - src_pos = line ? Position.new(line, code_lines[line].to_s.chomp.index(/[^\s]/) || 0) : Position.new(code_lines.length, 0) + src_pos = if line + Position.new(line, + code_lines[line].to_s.chomp.index(/[^\s]/) || 0) + else + Position.new( + code_lines.length, 0 + ) + end # @sg-ignore Need to add nil check here com_pos = Position.new(line + 1 - comments.lines.length, 0) process_comment(src_pos, com_pos, comments) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index afb0aaa03..6a3e37e0b 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -52,26 +52,26 @@ def source # @param inferred [ComplexType, ComplexType::UniqueType] # @param expected [ComplexType, ComplexType::UniqueType] - def return_type_conforms_to?(inferred, expected) + def return_type_conforms_to? inferred, expected conforms_to?(inferred, expected, :return_type) end # @param inferred [ComplexType, ComplexType::UniqueType] # @param expected [ComplexType, ComplexType::UniqueType] - def arg_conforms_to?(inferred, expected) + def arg_conforms_to? inferred, expected conforms_to?(inferred, expected, :method_call) end # @param inferred [ComplexType, ComplexType::UniqueType] # @param expected [ComplexType, ComplexType::UniqueType] - def assignment_conforms_to?(inferred, expected) + def assignment_conforms_to? inferred, expected conforms_to?(inferred, expected, :assignment) end # @param inferred [ComplexType, ComplexType::UniqueType] # @param expected [ComplexType, ComplexType::UniqueType] # @param scenario [Symbol] - def conforms_to?(inferred, expected, scenario) + 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_expected? @@ -86,12 +86,12 @@ def conforms_to?(inferred, expected, scenario) # @return [Array] def problems @problems ||= begin - all = method_tag_problems - .concat(variable_type_tag_problems) - .concat(const_problems) - .concat(call_problems) - unignored = without_ignored(all) - unignored.concat(unneeded_sgignore_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 @@ -148,7 +148,10 @@ def method_return_type_problems_for pin if pin.return_type.undefined? && rules.require_type_tags? if pin.attribute? inferred = pin.probe(api_map).self_to_type(pin.full_context) - result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", pin: pin) unless inferred.defined? + unless inferred.defined? + result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", + pin: pin) + end else result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", pin: pin) end @@ -167,7 +170,8 @@ def method_return_type_problems_for pin end else 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) + 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 end @@ -183,7 +187,7 @@ def method_return_type_problems_for pin def resolved_constant? pin return true if pin.typify(api_map).defined? constant_pins = api_map.get_constants('', *pin.closure.gates) - .select { |p| p.name == pin.return_type.namespace } + .select { |p| p.name == pin.return_type.namespace } return true if constant_pins.find { |p| p.typify(api_map).defined? } # will need to probe when a constant name is assigned to a # class/module (alias) @@ -205,19 +209,19 @@ def method_param_type_problems_for pin 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 + sig.parameters.each do |par| + break if %i[restarg kwrestarg blockarg].include?(par.decl) + 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 end + end end # @param name [String] # @param data [Hash{Symbol => BasicObject}] @@ -225,7 +229,8 @@ def method_param_type_problems_for pin # @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) + result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", + pin: pin) end end end @@ -257,20 +262,20 @@ def variable_type_tag_problems end else 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) + result.push Problem.new(pin.location, + "Declared type #{declared} does not match inferred type #{inferred} for variable #{pin.name}", pin: pin) end end elsif declared_externally?(pin) ignored_pins.push pin end elsif !pin.is_a?(Pin::Parameter) && !resolved_constant?(pin) - result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", pin: pin) + result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", + pin: pin) end elsif pin.assignment inferred = pin.probe(api_map) - if inferred.undefined? && declared_externally?(pin) - ignored_pins.push pin - end + ignored_pins.push pin if inferred.undefined? && declared_externally?(pin) end end result @@ -313,7 +318,6 @@ def call_problems chain = Solargraph::Parser.chain(call, filename) # @sg-ignore Need to add nil check here closure_pin = source_map.locate_closure_pin(rng.start.line, rng.start.column) - namespace_pin = closure_pin if call.type == :block # blocks in the AST include the method call as well, so the # node returned by #call_nodes_from needs to be backed out @@ -336,7 +340,6 @@ def call_problems found = nil # @type [Array] all_found = [] - closest = ComplexType::UNDEFINED until base.links.first.undefined? # @sg-ignore Need to add nil check here all_found = base.define(api_map, closure_pin, locals) @@ -348,16 +351,14 @@ def call_problems 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)) - # @sg-ignore Need to add nil check here - unless closest.generic? || ignored_pins.include?(found) - 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 + # @sg-ignore Need to add nil check here + if (!found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found))) && !(closest.generic? || ignored_pins.include?(found)) + 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 # @sg-ignore Need to add nil check here @@ -440,7 +441,7 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum # 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 + 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 @@ -453,18 +454,13 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum end else final_arg = arguments.last - argchain = final_arg if final_arg && [:kwsplat, :hash].include?(final_arg.node.type) + argchain = final_arg if final_arg && %i[kwsplat hash].include?(final_arg.node.type) 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) + if par.decl == :arg + final_arg = argchain if argchain.node.type == :splat && argchain == arguments.last + 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 @@ -489,10 +485,14 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum argtype = argchain.infer(api_map, closure_pin, locals) argtype = argtype.self_to_type(closure_pin.context) 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}") + errors.push Problem.new(location, + "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") return errors end end + else + errors.concat kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pin, params, idx + next end elsif par.decl == :kwarg errors.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}") @@ -521,27 +521,26 @@ def kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pi argchain = kwargs[par.name.to_sym] if par.decl == :kwrestarg || (par.decl == :optarg && idx == pin.parameters.length - 1 && par.asgn_code == '{}') result.concat kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, kwargs) - else - if argchain - data = params[par.name] - if data.nil? - # @todo Some level (strong, I guess) should require the param here - else - # @type [ComplexType, ComplexType::UniqueType] - ptype = data[:qualified] - ptype = ptype.self_to_type(pin.context) - unless ptype.undefined? - # @type [ComplexType] - argtype = argchain.infer(api_map, closure_pin, locals).self_to_type(closure_pin.context) - # @todo 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 + elsif argchain + data = params[par.name] + if data.nil? + # @todo Some level (strong, I guess) should require the param here + else + # @type [ComplexType, ComplexType::UniqueType] + ptype = data[:qualified] + ptype = ptype.self_to_type(pin.context) + unless ptype.undefined? + # @type [ComplexType] + argtype = argchain.infer(api_map, closure_pin, locals).self_to_type(closure_pin.context) + # @todo 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 end - elsif par.decl == :kwarg - result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}") end + elsif par.decl == :kwarg + result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}") end result end @@ -554,7 +553,7 @@ def kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pi # @param params [Hash{String => nil, Hash}] # @param kwargs [Hash{Symbol => Source::Chain}] # @return [Array] - def kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, kwargs) + def kwrestarg_problems_for api_map, closure_pin, locals, location, pin, params, kwargs result = [] kwargs.each_pair do |pname, argchain| next unless params.key?(pname.to_s) @@ -565,7 +564,8 @@ def kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, argtype = argchain.infer(api_map, closure_pin, locals) 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}") + result.push Problem.new(location, + "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}") end end result @@ -575,7 +575,7 @@ def kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, # @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, relevant_pin) + def add_restkwarg_param_tag_details param_details, pin, relevant_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) @@ -596,7 +596,7 @@ def add_restkwarg_param_tag_details(param_details, pin, relevant_pin) # @param pin [Pin::Signature] # @return [Hash{String => Hash{Symbol => String, ComplexType}}] - def signature_param_details(pin) + def signature_param_details pin # @type [Hash{String => Hash{Symbol => String, ComplexType}}] result = {} pin.parameters.each do |param| @@ -630,7 +630,7 @@ def signature_param_details(pin) # @param new_param_details [Hash{String => Hash{Symbol => String, ComplexType}}] # # @return [void] - def add_to_param_details(param_details, param_names, new_param_details) + 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) @@ -643,7 +643,7 @@ def add_to_param_details(param_details, param_names, new_param_details) # @param signature [Pin::Signature] # @param method_pin_stack [Array] # @return [Hash{String => Hash{Symbol => String, ComplexType}}] - def param_details_from_stack(signature, method_pin_stack) + 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) @@ -683,7 +683,7 @@ def external? pin # @param pin [Pin::BaseVariable] def declared_externally? pin - raise "No assignment found" if pin.assignment.nil? + raise 'No assignment found' if pin.assignment.nil? chain = Solargraph::Parser.chain(pin.assignment, filename) # @sg-ignore flow sensitive typing needs to handle attrs @@ -696,24 +696,19 @@ def declared_externally? pin type = chain.infer(api_map, closure_pin, locals) 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? all_found = base.define(api_map, closure_pin, locals) found = all_found.first break if found - missing = base base = base.base end 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 + return false if !found || closest.defined? || internal?(found) end true end @@ -736,7 +731,7 @@ def arity_problems_for pin, arguments, location # @param arguments [Array] # @param location [Location] # @return [Array] - def parameterized_arity_problems_for(pin, parameters, arguments, location) + def parameterized_arity_problems_for pin, parameters, arguments, location return [] unless pin.explicit? return [] if parameters.empty? && arguments.empty? return [] if pin.anon_splat? @@ -751,7 +746,7 @@ def parameterized_arity_problems_for(pin, parameters, arguments, location) settled_kwargs = parameters.count(&:keyword?) else kwargs = convert_hash(unchecked.last.node) - if parameters.any? { |param| [:kwarg, :kwoptarg].include?(param.decl) || param.kwrestarg? } + if parameters.any? { |param| %i[kwarg kwoptarg].include?(param.decl) || param.kwrestarg? } if kwargs.empty? add_params += 1 else @@ -780,10 +775,12 @@ def parameterized_arity_problems_for(pin, parameters, arguments, location) return [] if parameters.any?(&:rest?) opt = optional_param_count(parameters) return [] if unchecked.length <= req + opt - if req + add_params + 1 == unchecked.length && any_splatted_call?(unchecked.map(&:node)) && (parameters.map(&:decl) & [:kwarg, :kwoptarg, :kwrestarg]).any? + if req + add_params + 1 == unchecked.length && any_splatted_call?(unchecked.map(&:node)) && (parameters.map(&:decl) & %i[ + kwarg kwoptarg kwrestarg + ]).any? return [] end - return [] if arguments.length - req == parameters.select { |p| [:optarg, :kwoptarg].include?(p.decl) }.length + return [] if arguments.length - req == parameters.select { |p| %i[optarg kwoptarg].include?(p.decl) }.length return [Problem.new(location, "Too many arguments to #{pin.path}")] elsif unchecked.length < req - settled_kwargs && (arguments.empty? || (!arguments.last.splat? && !arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash))) # HACK: Kernel#raise signature is incorrect in Ruby 2.7 core docs. @@ -799,14 +796,14 @@ def parameterized_arity_problems_for(pin, parameters, arguments, location) # @todo need to use generic types in method to choose correct # signature and generate Integer as return type # @return [Integer] - def required_param_count(parameters) + def required_param_count parameters parameters.sum { |param| %i[arg kwarg].include?(param.decl) ? 1 : 0 } end # @param parameters [Enumerable] # # @return [Integer] - def optional_param_count(parameters) + def optional_param_count parameters parameters.select { |p| p.decl == :optarg }.length end @@ -820,14 +817,14 @@ def abstract? pin # @param pin [Pin::Method] # @return [Array] - def fake_args_for(pin) + def fake_args_for pin args = [] with_opts = false with_block = false # @param pin [Pin::Parameter] pin.parameters.each do |pin| # @sg-ignore flow sensitive typing should be able to handle redefinition - if [:kwarg, :kwoptarg, :kwrestarg].include?(pin.decl) + if %i[kwarg kwoptarg kwrestarg].include?(pin.decl) with_opts = true # @sg-ignore flow sensitive typing should be able to handle redefinition elsif pin.decl == :block diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index d551b066d..a0af63aec 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -23,11 +23,11 @@ class Rules # @param overrides [Hash{Symbol => Symbol}] def initialize level, overrides @rank = if LEVELS.key?(level) - LEVELS[level] - else - Solargraph.logger.warn "Unrecognized TypeChecker level #{level}, assuming normal" - 0 - end + LEVELS[level] + else + Solargraph.logger.warn "Unrecognized TypeChecker level #{level}, assuming normal" + 0 + end @level = LEVELS[LEVELS.values.index(@rank)] @overrides = overrides end @@ -149,7 +149,7 @@ def validate_sg_ignores? # @param type [Symbol] # @param level [Symbol] - def report?(type, level) + def report? type, level rank >= LEVELS[@overrides.fetch(type, level)] end end diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 01c58ef99..838bd2b22 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -119,7 +119,7 @@ def yard_plugins # @param level [Symbol] # @return [TypeChecker::Rules] - def rules(level) + def rules level @rules ||= TypeChecker::Rules.new(level, config.type_checker_rules) end @@ -139,7 +139,7 @@ def merge *sources includes_any = false sources.each do |source| # @sg-ignore Need to add nil check here - next unless directory == "*" || config.calculated.include?(source.filename) + next unless directory == '*' || config.calculated.include?(source.filename) # @sg-ignore Need to add nil check here source_hash[source.filename] = source @@ -191,7 +191,7 @@ def source filename def would_require? path require_paths.each do |rp| full = File.join rp, path - return true if File.file?(full) || File.file?(full << ".rb") + return true if File.file?(full) || File.file?(full << '.rb') end false end @@ -221,11 +221,9 @@ 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) - end + unless directory.empty? || directory == '*' + yaml_file = File.join(directory, 'rbs_collection.yaml') + yaml_file if File.file?(yaml_file) end end @@ -262,7 +260,7 @@ def cache_all_for_workspace! out, rebuild: false # which are likely to be newer and have more pins pin_cache.cache_all_stdlibs(out: out, rebuild: rebuild) - out&.puts "Documentation cached for core, standard library and gems." + out&.puts 'Documentation cached for core, standard library and gems.' end # Synchronize the workspace from the provided updater. @@ -307,30 +305,25 @@ def source_hash # @return [void] def load_sources source_hash.clear - unless directory.empty? || directory == '*' - size = config.calculated.length - 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) - rescue Errno::ENOENT => e - Solargraph.logger.warn("Error loading #{filename}: [#{e.class}] #{e.message}") - end - end + return if directory.empty? || directory == '*' + size = config.calculated.length + 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| + source_hash[filename] = Solargraph::Source.load(filename) + rescue Errno::ENOENT => e + Solargraph.logger.warn("Error loading #{filename}: [#{e.class}] #{e.message}") end end # @return [void] def require_plugins config.plugins.each do |plugin| - begin - require plugin - rescue LoadError - Solargraph.logger.warn "Failed to load plugin '#{plugin}'" - end + require plugin + rescue LoadError + Solargraph.logger.warn "Failed to load plugin '#{plugin}'" end end diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 31e065e17..bd494b380 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -54,7 +54,9 @@ def allow? filename # # @return [Array] def calculated - Solargraph.logger.info "Indexing workspace files in #{directory}" unless @calculated || directory.empty? || directory == '*' + unless @calculated || directory.empty? || directory == '*' + Solargraph.logger.info "Indexing workspace files in #{directory}" + end @calculated ||= included - excluded end @@ -146,7 +148,7 @@ def config_data global_config = read_config(global_config_path) defaults = default_config - defaults.merge({'exclude' => []}) unless workspace_config.nil? + defaults.merge({ 'exclude' => [] }) unless workspace_config.nil? defaults .merge(global_config || {}) @@ -160,7 +162,7 @@ def config_data def read_config config_path = '' return nil if config_path.empty? return nil unless File.file?(config_path) - YAML.safe_load(File.read(config_path)) + YAML.safe_load_file(config_path) end # @return [Hash{String => Array, Hash, Integer}] @@ -176,11 +178,11 @@ def default_config 'cops' => 'safe', 'except' => [], 'only' => [], - 'extra_args' =>[] + 'extra_args' => [] } }, 'type_checker' => { - 'rules' => { } + 'rules' => {} }, 'require_paths' => [], 'plugins' => [], @@ -193,12 +195,11 @@ def default_config # @param globs [Array] # @return [Array] def process_globs globs - result = globs.flat_map do |glob| + globs.flat_map do |glob| Dir[File.absolute_path(glob, directory)] - .map{ |f| f.gsub(/\\/, '/') } + .map { |f| f.gsub('\\', '/') } .select { |f| File.file?(f) } end - result end # Modify the included files based on excluded directories and get an @@ -241,7 +242,7 @@ def glob_is_directory? glob # @param glob [String] # @return [String] def glob_to_directory glob - glob.gsub(/(\/\*|\/\*\*\/\*\*?)$/, '') + glob.gsub(%r{(/\*|/\*\*/\*\*?)$}, '') end # @return [Array] diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index c6af306dd..2389c2d76 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -117,7 +117,7 @@ def find_gem name, version = nil, out: $stderr # # @return [Array] def fetch_dependencies gemspec, out: $stderr - gemspecs = all_gemspecs_from_bundle + all_gemspecs_from_bundle # @type [Hash{String => Gem::Specification}] deps_so_far = {} diff --git a/lib/solargraph/yard_map/helpers.rb b/lib/solargraph/yard_map/helpers.rb index cd4be9acc..7969ffce5 100644 --- a/lib/solargraph/yard_map/helpers.rb +++ b/lib/solargraph/yard_map/helpers.rb @@ -22,10 +22,12 @@ def object_location code_object, spec # @param code_object [YARD::CodeObjects::Base] # @param spec [Gem::Specification, nil] # @return [Solargraph::Pin::Namespace] - def create_closure_namespace_for(code_object, spec) + def create_closure_namespace_for code_object, spec code_object_for_location = code_object # code_object.namespace is sometimes a YARD proxy object pointing to a method path ("Object#new") - code_object_for_location = code_object.namespace if code_object.namespace.is_a?(YARD::CodeObjects::NamespaceObject) + if code_object.namespace.is_a?(YARD::CodeObjects::NamespaceObject) + code_object_for_location = code_object.namespace + end namespace_location = object_location(code_object_for_location, spec) ns_name = code_object.namespace.to_s if ns_name.empty? diff --git a/lib/solargraph/yard_map/mapper.rb b/lib/solargraph/yard_map/mapper.rb index f0708e9d9..67a9ff860 100644 --- a/lib/solargraph/yard_map/mapper.rb +++ b/lib/solargraph/yard_map/mapper.rb @@ -46,12 +46,12 @@ def generate_pins code_object # yardoc and converted to a fully qualified namespace. # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check superclass = if code_object.superclass.is_a?(YARD::CodeObjects::Proxy) - # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check - "::#{code_object.superclass}" - else - # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check - code_object.superclass.to_s - end + # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check + "::#{code_object.superclass}" + else + # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check + code_object.superclass.to_s + end result.push Solargraph::Pin::Reference::Superclass.new(name: superclass, closure: nspin, source: :yard_map) end code_object.class_mixins.each do |m| diff --git a/lib/solargraph/yard_map/mapper/to_method.rb b/lib/solargraph/yard_map/mapper/to_method.rb index 42593ed8c..f4d5b0dad 100644 --- a/lib/solargraph/yard_map/mapper/to_method.rb +++ b/lib/solargraph/yard_map/mapper/to_method.rb @@ -9,7 +9,7 @@ module ToMethod # @type [Hash{Array => Symbol}] VISIBILITY_OVERRIDE = { # YARD pays attention to 'private' statements prior to class methods but shouldn't - ["Rails::Engine", :class, "find_root_with_flag"] => :public + ['Rails::Engine', :class, 'find_root_with_flag'] => :public } # @param code_object [YARD::CodeObjects::MethodObject] @@ -32,7 +32,9 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = # @sg-ignore Need to add nil check here final_visibility ||= VISIBILITY_OVERRIDE[[closure.path, final_scope]] # @sg-ignore Need to add nil check here - final_visibility ||= :private if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(name.to_sym) + if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(name.to_sym) + final_visibility ||= :private + end 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 @@ -50,7 +52,7 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = explicit: code_object.is_explicit?, return_type: return_type, parameters: [], - source: :yardoc, + source: :yardoc ) else # @sg-ignore Need to add nil check here @@ -66,7 +68,7 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = return_type: return_type, attribute: code_object.is_attribute?, parameters: [], - source: :yardoc, + source: :yardoc ) pin.parameters.concat get_parameters(code_object, location, comments, pin) pin.parameters.freeze @@ -99,7 +101,7 @@ def get_parameters code_object, location, comments, pin presence: nil, decl: arg_type(a), asgn_code: a[1], - source: :yardoc, + source: :yardoc ) end end diff --git a/lib/solargraph/yard_map/mapper/to_namespace.rb b/lib/solargraph/yard_map/mapper/to_namespace.rb index 7d1d3ce6e..3e0887ecd 100644 --- a/lib/solargraph/yard_map/mapper/to_namespace.rb +++ b/lib/solargraph/yard_map/mapper/to_namespace.rb @@ -23,7 +23,7 @@ def self.make code_object, spec, closure = nil closure: closure, # @sg-ignore need to add a nil check here gates: closure.gates, - source: :yardoc, + source: :yardoc ) end end diff --git a/lib/solargraph/yard_tags.rb b/lib/solargraph/yard_tags.rb index c34710b63..462138f9c 100644 --- a/lib/solargraph/yard_tags.rb +++ b/lib/solargraph/yard_tags.rb @@ -14,7 +14,7 @@ def call; end end # Define a @type tag for documenting variables -YARD::Tags::Library.define_tag("Type", :type, :with_types_and_name) +YARD::Tags::Library.define_tag('Type', :type, :with_types_and_name) # Define an @!override directive for overriding method tags -YARD::Tags::Library.define_directive("override", :with_name, Solargraph::DomainDirective) +YARD::Tags::Library.define_directive('override', :with_name, Solargraph::DomainDirective) diff --git a/solargraph.gemspec b/solargraph.gemspec index a9b4a6617..e8435db6f 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -7,12 +7,11 @@ require 'date' Gem::Specification.new do |s| s.name = 'solargraph' s.version = Solargraph::VERSION - s.date = Date.today.strftime("%Y-%m-%d") - s.summary = "A Ruby language server" - s.description = "IDE tools for code completion, inline documentation, and static analysis" - s.authors = ["Fred Snyder"] + s.summary = 'A Ruby language server' + s.description = 'IDE tools for code completion, inline documentation, and static analysis' + s.authors = ['Fred Snyder'] s.email = 'admin@castwide.com' - s.files = Dir.chdir(File.expand_path('..', __FILE__)) do + s.files = Dir.chdir(File.expand_path(__dir__)) do # @sg-ignore Need backtick support # @type [String] all_files = `git ls-files -z` @@ -21,36 +20,36 @@ Gem::Specification.new do |s| s.homepage = 'https://solargraph.org' s.license = 'MIT' s.executables = ['solargraph'] - s.metadata["funding_uri"] = "https://www.patreon.com/castwide" - s.metadata["bug_tracker_uri"] = "https://github.com/castwide/solargraph/issues" - s.metadata["changelog_uri"] = "https://github.com/castwide/solargraph/blob/master/CHANGELOG.md" - s.metadata["source_code_uri"] = "https://github.com/castwide/solargraph" - s.metadata["rubygems_mfa_required"] = "true" + s.metadata['funding_uri'] = 'https://www.patreon.com/castwide' + s.metadata['bug_tracker_uri'] = 'https://github.com/castwide/solargraph/issues' + s.metadata['changelog_uri'] = 'https://github.com/castwide/solargraph/blob/master/CHANGELOG.md' + s.metadata['source_code_uri'] = 'https://github.com/castwide/solargraph' + s.metadata['rubygems_mfa_required'] = 'true' 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' - s.add_runtime_dependency 'diff-lcs', '~> 1.4' - s.add_runtime_dependency 'jaro_winkler', '~> 1.6', '>= 1.6.1' - s.add_runtime_dependency 'kramdown', '~> 2.3' - s.add_runtime_dependency 'kramdown-parser-gfm', '~> 1.1' - 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.6.1', '<= 4.0.0.dev.5'] - s.add_runtime_dependency 'reverse_markdown', '~> 3.0' - s.add_runtime_dependency 'rubocop', '~> 1.76' - s.add_runtime_dependency 'thor', '~> 1.0' - s.add_runtime_dependency 'tilt', '~> 2.0' - s.add_runtime_dependency 'yard', '~> 0.9', '>= 0.9.24' - s.add_runtime_dependency 'yard-solargraph', '~> 0.1' - s.add_runtime_dependency 'yard-activesupport-concern', '~> 0.0' + s.add_dependency 'ast', '~> 2.4.3' + s.add_dependency 'backport', '~> 1.2' + s.add_dependency 'benchmark', '~> 0.4' + s.add_dependency 'bundler', '>= 2.0' + s.add_dependency 'diff-lcs', '~> 1.4' + s.add_dependency 'jaro_winkler', '~> 1.6', '>= 1.6.1' + s.add_dependency 'kramdown', '~> 2.3' + s.add_dependency 'kramdown-parser-gfm', '~> 1.1' + s.add_dependency 'logger', '~> 1.6' + s.add_dependency 'observer', '~> 0.1' + s.add_dependency 'open3', '~> 0.2.1' + s.add_dependency 'ostruct', '~> 0.6' + s.add_dependency 'parser', '~> 3.0' + s.add_dependency 'prism', '~> 1.4' + s.add_dependency 'rbs', ['>= 3.6.1', '<= 4.0.0.dev.5'] + s.add_dependency 'reverse_markdown', '~> 3.0' + s.add_dependency 'rubocop', '~> 1.76' + s.add_dependency 'thor', '~> 1.0' + s.add_dependency 'tilt', '~> 2.0' + s.add_dependency 'yard', '~> 0.9', '>= 0.9.24' + s.add_dependency 'yard-activesupport-concern', '~> 0.0' + s.add_dependency 'yard-solargraph', '~> 0.1' s.add_development_dependency 'pry', '~> 0.15' s.add_development_dependency 'public_suffix', '~> 3.1' @@ -62,6 +61,7 @@ Gem::Specification.new do |s| # # even more specific on RuboCop itself, which is written into _todo # file. + s.add_development_dependency 'overcommit', '~> 0.68.0' s.add_development_dependency 'rubocop', '~> 1.80.0.0' s.add_development_dependency 'rubocop-rake', '~> 0.7.1' s.add_development_dependency 'rubocop-rspec', '~> 3.6.0' @@ -69,9 +69,8 @@ Gem::Specification.new do |s| s.add_development_dependency 'simplecov', '~> 0.21' s.add_development_dependency 'simplecov-lcov', '~> 0.8' 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', '< 2' + s.add_development_dependency 'webmock', '~> 3.6' # work around missing yard dependency needed as of Ruby 3.5 s.add_development_dependency 'irb', '~> 1.15' end diff --git a/spec/api_map/cache_spec.rb b/spec/api_map/cache_spec.rb index 20e5b9df1..0a8d002de 100644 --- a/spec/api_map/cache_spec.rb +++ b/spec/api_map/cache_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::ApiMap::Cache do - it "recognizes empty caches" do + it 'recognizes empty caches' do cache = Solargraph::ApiMap::Cache.new expect(cache).to be_empty cache.set_methods('', :class, [:public], true, []) diff --git a/spec/api_map/config_spec.rb b/spec/api_map/config_spec.rb index 79ad924c9..936d05e27 100644 --- a/spec/api_map/config_spec.rb +++ b/spec/api_map/config_spec.rb @@ -5,62 +5,62 @@ let(:workspace_path) { File.realpath(Dir.mktmpdir) } let(:global_path) { @global_path } - before(:each) do + before do @global_path = File.realpath(Dir.mktmpdir) - @orig_env = ENV['SOLARGRAPH_GLOBAL_CONFIG'] + @orig_env = ENV.fetch('SOLARGRAPH_GLOBAL_CONFIG', nil) ENV['SOLARGRAPH_GLOBAL_CONFIG'] = File.join(@global_path, '.solargraph.yml') end - after(:each) do + after do ENV['SOLARGRAPH_GLOBAL_CONFIG'] = @orig_env FileUtils.remove_entry(workspace_path) FileUtils.remove_entry(global_path) end - it "reads workspace files from config" do + it 'reads workspace files from config' do File.write(File.join(workspace_path, 'foo.rb'), 'test') File.write(File.join(workspace_path, 'bar.rb'), 'test') File.open(File.join(workspace_path, '.solargraph.yml'), 'w') do |file| - file.puts "include:" - file.puts " - foo.rb" - file.puts "exclude:" - file.puts " - bar.rb" + file.puts 'include:' + file.puts ' - foo.rb' + file.puts 'exclude:' + file.puts ' - bar.rb' end expect(config.included).to eq([File.join(workspace_path, 'foo.rb')]) expect(config.excluded).to eq([File.join(workspace_path, 'bar.rb')]) end - it "reads workspace files from global config" do + it 'reads workspace files from global config' do File.write(File.join(workspace_path, 'foo.rb'), 'test') File.write(File.join(workspace_path, 'bar.rb'), 'test') File.open(File.join(global_path, '.solargraph.yml'), 'w') do |file| - file.puts "include:" - file.puts " - foo.rb" - file.puts "exclude:" - file.puts " - bar.rb" + file.puts 'include:' + file.puts ' - foo.rb' + file.puts 'exclude:' + file.puts ' - bar.rb' end expect(config.included).to eq([File.join(workspace_path, 'foo.rb')]) expect(config.excluded).to eq([File.join(workspace_path, 'bar.rb')]) end - it "overrides global config with workspace config" do + it 'overrides global config with workspace config' do File.write(File.join(workspace_path, 'foo.rb'), 'test') File.write(File.join(workspace_path, 'bar.rb'), 'test') File.open(File.join(workspace_path, '.solargraph.yml'), 'w') do |file| - file.puts "include:" - file.puts " - foo.rb" - file.puts "max_files: 8000" + file.puts 'include:' + file.puts ' - foo.rb' + file.puts 'max_files: 8000' end File.open(File.join(global_path, '.solargraph.yml'), 'w') do |file| - file.puts "include:" - file.puts " - include.rb" - file.puts "exclude:" - file.puts " - bar.rb" - file.puts "max_files: 1000" + file.puts 'include:' + file.puts ' - include.rb' + file.puts 'exclude:' + file.puts ' - bar.rb' + file.puts 'max_files: 1000' end expect(config.included).to eq([File.join(workspace_path, 'foo.rb')]) diff --git a/spec/api_map/source_to_yard_spec.rb b/spec/api_map/source_to_yard_spec.rb index 88be54c64..e7dcf3d0d 100644 --- a/spec/api_map/source_to_yard_spec.rb +++ b/spec/api_map/source_to_yard_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::ApiMap::SourceToYard do - it "rakes sources" do + it 'rakes sources' do source = Solargraph::SourceMap.load_string(%( module Foo class Bar @@ -17,7 +17,7 @@ def baz expect(object.code_object_paths).to include('Foo::Bar#baz') end - it "generates docstrings" do + it 'generates docstrings' do source = Solargraph::SourceMap.load_string(%( # My foo class 描述 class Foo @@ -40,7 +40,7 @@ def self.baz expect(class_method_object.tag(:return).types).to eq(['Foo']) end - it "generates instance mixins" do + it 'generates instance mixins' do source = Solargraph::SourceMap.load_string(%( module Foo def bar @@ -58,7 +58,7 @@ class Baz expect(class_object.instance_mixins).to include(module_object) end - it "generates class mixins" do + it 'generates class mixins' do source = Solargraph::SourceMap.load_string(%( module Foo def bar; end @@ -75,7 +75,7 @@ class Baz expect(class_object.class_mixins).to include(module_object) end - it "generates methods for attributes" do + it 'generates methods for attributes' do source = Solargraph::SourceMap.load_string(%( class Foo attr_reader :bar @@ -86,15 +86,15 @@ class Foo object = Object.new object.extend Solargraph::ApiMap::SourceToYard object.rake_yard Solargraph::ApiMap::Store.new(source.pins) - expect(object.code_object_at('Foo#bar')).not_to be(nil) - expect(object.code_object_at('Foo#bar=')).to be(nil) - expect(object.code_object_at('Foo#baz')).to be(nil) - expect(object.code_object_at('Foo#baz=')).not_to be(nil) - expect(object.code_object_at('Foo#boo')).not_to be(nil) - expect(object.code_object_at('Foo#boo=')).not_to be(nil) + expect(object.code_object_at('Foo#bar')).not_to be_nil + expect(object.code_object_at('Foo#bar=')).to be_nil + expect(object.code_object_at('Foo#baz')).to be_nil + expect(object.code_object_at('Foo#baz=')).not_to be_nil + expect(object.code_object_at('Foo#boo')).not_to be_nil + expect(object.code_object_at('Foo#boo=')).not_to be_nil end - it "generates method parameters" do + it 'generates method parameters' do source = Solargraph::SourceMap.load_string(%( class Foo def bar baz, boo = 'boo' @@ -110,7 +110,7 @@ def bar baz, boo = 'boo' expect(method_object.parameters[1]).to eq(['boo', "'boo'"]) end - it "generates method keyword parameters" do + it 'generates method keyword parameters' do source = Solargraph::SourceMap.load_string(%( class Foo def bar baz, boo: 'boo' diff --git a/spec/api_map_spec.rb b/spec/api_map_spec.rb index 2340c8bf9..8c396e5ee 100755 --- a/spec/api_map_spec.rb +++ b/spec/api_map_spec.rb @@ -432,7 +432,7 @@ class Sup method_pins = @api_map.get_method_stack("Array(1, 2, 'a')", 'include?') method_pin = method_pins.first - expect(method_pin).to_not be_nil + expect(method_pin).not_to be_nil expect(method_pin.path).to eq('Array#include?') parameter_type = method_pin.signatures.first.parameters.first.return_type expect(parameter_type.rooted_tags).to eq("1, 2, 'a'") diff --git a/spec/complex_type_spec.rb b/spec/complex_type_spec.rb index 4b5eac972..cb056cee7 100644 --- a/spec/complex_type_spec.rb +++ b/spec/complex_type_spec.rb @@ -160,7 +160,6 @@ expect(types.to_rbs).to eq('Array[Symbol, String]') end - # Note that parametrized types are typically not order-dependent, in # other words, a list of parametrized types can occur in any order # inside of a type. An array specified as Array can @@ -543,13 +542,13 @@ let(:complex_type) { Solargraph::ComplexType.parse(tag) } let(:unique_type) { complex_type.first } + let(:context_type) { Solargraph::ComplexType.parse(context_type_tag) } + let(:generic_value) { unfrozen_input_map.transform_values! { |tag| Solargraph::ComplexType.parse(tag) } } + it '#{tag} is a unique type' do expect(complex_type.length).to eq(1) end - let(:generic_value) { unfrozen_input_map.transform_values! { |tag| Solargraph::ComplexType.parse(tag) } } - let(:context_type) { Solargraph::ComplexType.parse(context_type_tag) } - it "resolves to #{expected_tag} with updated map #{expected_output_map}" do resolved_generic_values = unfrozen_input_map.transform_values { |tag| Solargraph::ComplexType.parse(tag) } resolved_type = unique_type.resolve_generics_from_context(expected_output_map.keys, context_type, @@ -607,7 +606,22 @@ end end - context "when supporting arbitrary combinations of the above syntax and features" do + context 'when supporting arbitrary combinations of the above syntax and features' do + let(:foo_bar_api_map) do + api_map = Solargraph::ApiMap.new + source = Solargraph::Source.load_string(%( + module Foo + class Bar + # @return [Bar] + def make_bar + end + end + end + )) + api_map.map source + api_map + end + it 'returns string representations of the entire type array' do type = Solargraph::ComplexType.parse('String', 'Array') expect(type.to_s).to eq('String, Array') @@ -635,21 +649,6 @@ expect(types.to_rbs).to eq('(Array[String] | Hash[String, Symbol] | [String, Integer])') end - let(:foo_bar_api_map) do - api_map = Solargraph::ApiMap.new - source = Solargraph::Source.load_string(%( - module Foo - class Bar - # @return [Bar] - def make_bar - end - end - end - )) - api_map.map source - api_map - end - it 'qualifies types with list parameters' do original = Solargraph::ComplexType.parse('Class').first expect(original).not_to be_rooted diff --git a/spec/convention/activesupport_concern_spec.rb b/spec/convention/activesupport_concern_spec.rb index a0eae979a..2360e2a83 100644 --- a/spec/convention/activesupport_concern_spec.rb +++ b/spec/convention/activesupport_concern_spec.rb @@ -101,7 +101,7 @@ def self.my_method; end # 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 @@ -151,11 +151,11 @@ class Base it { is_expected.not_to be_empty } - it "has one item" do + it 'has one item' do expect(method_pins.size).to eq(1) end - it "is a Pin::Method" do + it 'is a Pin::Method' do expect(method_pins.first).to be_a(Solargraph::Pin::Method) end end diff --git a/spec/convention/struct_definition_spec.rb b/spec/convention/struct_definition_spec.rb index 5c3fc5211..51a71f673 100644 --- a/spec/convention/struct_definition_spec.rb +++ b/spec/convention/struct_definition_spec.rb @@ -103,7 +103,7 @@ expect(params.map(&:name)).to eql(%w[bar baz]) expect(params.map(&:return_type).map(&:tag)).to eql(%w[Integer String]) - expect(params[1].documentation).to eql("Some text") + expect(params[1].documentation).to eql('Some text') end [true, false].each do |kw_args| @@ -116,21 +116,21 @@ Foo = Struct.new(:bar, :baz, keyword_init: #{kw_args}) ), 'test.rb') - params_bar = source.pins.find { |p| p.path == "Foo#bar=" }.parameters + params_bar = source.pins.find { |p| p.path == 'Foo#bar=' }.parameters expect(params_bar.length).to be(1) - expect(params_bar.first.return_type.tag).to eql("String") + expect(params_bar.first.return_type.tag).to eql('String') expect(params_bar.first.arg?).to be(true) - params_baz = source.pins.find { |p| p.path == "Foo#baz=" }.parameters + params_baz = source.pins.find { |p| p.path == 'Foo#baz=' }.parameters expect(params_baz.length).to be(1) - expect(params_baz.first.return_type.tag).to eql("Integer") + expect(params_baz.first.return_type.tag).to eql('Integer') expect(params_baz.first.arg?).to be(true) - iv_bar = source.pins.find { |p| p.name == "@bar" } - expect(iv_bar.return_type.tag).to eql("String") + iv_bar = source.pins.find { |p| p.name == '@bar' } + expect(iv_bar.return_type.tag).to eql('String') - iv_baz = source.pins.find { |p| p.name == "@baz" } - expect(iv_baz.return_type.tag).to eql("Integer") + iv_baz = source.pins.find { |p| p.name == '@baz' } + expect(iv_baz.return_type.tag).to eql('Integer') end end end diff --git a/spec/diagnostics/base_spec.rb b/spec/diagnostics/base_spec.rb index d2068caf1..42fbb097e 100644 --- a/spec/diagnostics/base_spec.rb +++ b/spec/diagnostics/base_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Diagnostics::Base do - it "returns empty diagnostics" do + it 'returns empty diagnostics' do reporter = Solargraph::Diagnostics::Base.new expect(reporter.diagnose(nil, nil)).to be_empty end diff --git a/spec/diagnostics/require_not_found_spec.rb b/spec/diagnostics/require_not_found_spec.rb index 6ecfdcae9..53b375d7e 100644 --- a/spec/diagnostics/require_not_found_spec.rb +++ b/spec/diagnostics/require_not_found_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Diagnostics::RequireNotFound do - before :each do + before do @source = Solargraph::Source.new(%( require 'rexml/document' require 'not_valid' @@ -11,7 +11,7 @@ @api_map.catalog Solargraph::Bench.new(source_maps: [@source_map], external_requires: ['not_valid']) end - it "reports unresolved requires" do + it 'reports unresolved requires' do reporter = Solargraph::Diagnostics::RequireNotFound.new result = reporter.diagnose(@source, @api_map) expect(result.length).to eq(1) diff --git a/spec/diagnostics/rubocop_helpers_spec.rb b/spec/diagnostics/rubocop_helpers_spec.rb index b1c78726c..111fef850 100644 --- a/spec/diagnostics/rubocop_helpers_spec.rb +++ b/spec/diagnostics/rubocop_helpers_spec.rb @@ -2,7 +2,7 @@ context 'with custom version' do around do |example| old_gem_path = Gem.paths.path - custom_gem_path = File.absolute_path('spec/fixtures/rubocop-custom-version').gsub(/\\/, '/') + custom_gem_path = File.absolute_path('spec/fixtures/rubocop-custom-version').gsub('\\', '/') # Remove a post_reset hook set by bundler to restore cached specs # Source: https://github.com/ruby/ruby/blob/master/lib/bundler/rubygems_integration.rb#L487-L489 old_post_reset_hooks = Gem.post_reset_hooks.dup @@ -18,7 +18,7 @@ let(:custom_version) { '0.0.0' } - it "requires the specified version of rubocop" do + it 'requires the specified version of rubocop' do input = custom_version Solargraph::Diagnostics::RubocopHelpers.require_rubocop(input) output = RuboCop::Version::STRING @@ -29,7 +29,7 @@ context 'with real version' do let(:default_version) { Gem::Specification.find_by_name('rubocop').full_gem_path[/[^-]+$/] } - it "requires the default version of rubocop" do + it 'requires the default version of rubocop' do input = nil Solargraph::Diagnostics::RubocopHelpers.require_rubocop(input) output = RuboCop::Version::STRING @@ -37,13 +37,13 @@ end end - it "converts lower-case drive letters to upper-case" do + it 'converts lower-case drive letters to upper-case' do input = 'c:/one/two' output = Solargraph::Diagnostics::RubocopHelpers.fix_drive_letter(input) expect(output).to eq('C:/one/two') end - it "ignores paths without drive letters" do + it 'ignores paths without drive letters' do input = 'one/two' output = Solargraph::Diagnostics::RubocopHelpers.fix_drive_letter(input) expect(output).to eq('one/two') diff --git a/spec/diagnostics/rubocop_spec.rb b/spec/diagnostics/rubocop_spec.rb index aa279f66c..4926a458f 100644 --- a/spec/diagnostics/rubocop_spec.rb +++ b/spec/diagnostics/rubocop_spec.rb @@ -15,17 +15,17 @@ def bar expect(result).to be_a(Array) end - context "with validation error" do + context 'with validation error' do let(:fixture_path) do - File.absolute_path('spec/fixtures/rubocop-validation-error').gsub(/\\/, '/') + File.absolute_path('spec/fixtures/rubocop-validation-error').gsub('\\', '/') end around do |example| config_file = File.join(fixture_path, '.rubocop.yml') File.write(config_file, <<~YAML) - inherit_from: - - file_not_found.yml - YAML + inherit_from: + - file_not_found.yml + YAML example.run ensure File.delete(config_file) if File.exist?(config_file) @@ -35,9 +35,9 @@ def bar file = File.realpath(File.join(fixture_path, 'app.rb')) source = Solargraph::Source.load(file) rubocop = Solargraph::Diagnostics::Rubocop.new - expect { + expect do rubocop.diagnose(source, nil) - }.to raise_error(Solargraph::DiagnosticsError) + end.to raise_error(Solargraph::DiagnosticsError) end end diff --git a/spec/diagnostics/type_check_spec.rb b/spec/diagnostics/type_check_spec.rb index e343d6598..c140f9593 100644 --- a/spec/diagnostics/type_check_spec.rb +++ b/spec/diagnostics/type_check_spec.rb @@ -1,7 +1,7 @@ describe Solargraph::Diagnostics::TypeCheck do let(:api_map) { Solargraph::ApiMap.new } - it "detects defined return types" do + it 'detects defined return types' do source = Solargraph::Source.load_string(%( # @return [String] def foo @@ -12,7 +12,7 @@ def foo expect(result).to be_empty end - it "detects missing return types" do + it 'detects missing return types' do source = Solargraph::Source.load_string(%( def foo end @@ -23,7 +23,7 @@ def foo expect(result[0][:message]).to include('foo') end - it "detects defined parameter types" do + it 'detects defined parameter types' do source = Solargraph::Source.load_string(%( # @param bar [String] # @return [String] @@ -35,7 +35,7 @@ def foo(bar) expect(result).to be_empty end - it "detects missing parameter types" do + it 'detects missing parameter types' do source = Solargraph::Source.load_string(%( # @return [String] def foo(bar) @@ -48,7 +48,7 @@ def foo(bar) expect(result[0][:message]).to include('bar') end - it "detects return types from superclasses" do + it 'detects return types from superclasses' do source = Solargraph::Source.load_string(%( class First # @return [String] @@ -65,7 +65,7 @@ def foo expect(result).to be_empty end - it "detects parameter types from superclasses" do + it 'detects parameter types from superclasses' do source = Solargraph::Source.load_string(%( class First # @param bar [String] @@ -83,7 +83,7 @@ def foo bar expect(result).to be_empty end - it "works with optional and keyword arguments" do + it 'works with optional and keyword arguments' do source = Solargraph::Source.load_string(%( # @param bar [String] # @param baz [String] diff --git a/spec/diagnostics/update_errors_spec.rb b/spec/diagnostics/update_errors_spec.rb index 11b8c6f8f..1a05f93d5 100644 --- a/spec/diagnostics/update_errors_spec.rb +++ b/spec/diagnostics/update_errors_spec.rb @@ -1,16 +1,16 @@ describe Solargraph::Diagnostics::UpdateErrors do - it "detects repaired lines" do + it 'detects repaired lines' do api_map = Solargraph::ApiMap.new orig = Solargraph::Source.load_string('foo', 'test.rb') diagnoser = Solargraph::Diagnostics::UpdateErrors.new result = diagnoser.diagnose(orig, api_map) expect(result.length).to eq(0) updater = Solargraph::Source::Updater.new('test.rb', 2, [ - Solargraph::Source::Change.new( - Solargraph::Range.from_to(0, 3, 0, 3), - '.' - ) - ]) + Solargraph::Source::Change.new( + Solargraph::Range.from_to(0, 3, 0, 3), + '.' + ) + ]) source = orig.synchronize(updater) diagnoser = Solargraph::Diagnostics::UpdateErrors.new result = diagnoser.diagnose(source, api_map) diff --git a/spec/diagnostics_spec.rb b/spec/diagnostics_spec.rb index ba689692d..628341f2a 100644 --- a/spec/diagnostics_spec.rb +++ b/spec/diagnostics_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Diagnostics do - it "registers reporters" do + it 'registers reporters' do Solargraph::Diagnostics.register 'base', Solargraph::Diagnostics::Base expect(Solargraph::Diagnostics.reporters).to include('base') expect(Solargraph::Diagnostics.reporter('base')).to be(Solargraph::Diagnostics::Base) diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 6f28689a4..c323095ec 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -52,15 +52,15 @@ 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', - 'actionpack', - 'activerecord', - 'shoulda-matchers', - 'rspec-sidekiq', - 'airborne', - 'activesupport' + unprovided_solargraph_rspec_requires = %w[ + rspec-rails + actionmailer + actionpack + activerecord + shoulda-matchers + rspec-sidekiq + airborne + activesupport ] expect(doc_map.unresolved_requires - unprovided_solargraph_rspec_requires) .to eq(['not_a_gem']) @@ -164,9 +164,9 @@ it 'includes convention requires from environ' do dummy_convention = Class.new(Solargraph::Convention::Base) do - def global(doc_map) + def global doc_map Solargraph::Environ.new( - requires: ['convention_gem1', 'convention_gem2'] + requires: %w[convention_gem1 convention_gem2] ) end end diff --git a/spec/language_server/host/diagnoser_spec.rb b/spec/language_server/host/diagnoser_spec.rb index 4e9a7208f..c290bf03b 100644 --- a/spec/language_server/host/diagnoser_spec.rb +++ b/spec/language_server/host/diagnoser_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::LanguageServer::Host::Diagnoser do - it "diagnoses on ticks" do + it 'diagnoses on ticks' do host = instance_double(Solargraph::LanguageServer::Host, options: { 'diagnostics' => true }, synchronizing?: false) allow(host).to receive(:diagnose) diagnoser = Solargraph::LanguageServer::Host::Diagnoser.new(host) diff --git a/spec/language_server/host/dispatch_spec.rb b/spec/language_server/host/dispatch_spec.rb index 9b314c403..1ecd49c9b 100644 --- a/spec/language_server/host/dispatch_spec.rb +++ b/spec/language_server/host/dispatch_spec.rb @@ -5,12 +5,12 @@ @dispatch.extend Solargraph::LanguageServer::Host::Dispatch end - after :each do + after do @dispatch.libraries.clear @dispatch.sources.clear end - it "finds an explicit library" do + it 'finds an explicit library' do @dispatch.libraries.push Solargraph::Library.load('*') src = @dispatch.sources.open('file:///file.rb', 'a=b', 0) @dispatch.libraries.first.merge src @@ -18,7 +18,7 @@ expect(lib).to be(@dispatch.libraries.first) end - it "finds an implicit library" do + it 'finds an implicit library' do dir = File.realpath(File.join('spec', 'fixtures', 'workspace')) file = File.join(dir, 'new.rb') uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) @@ -28,7 +28,7 @@ expect(lib).to be(@dispatch.libraries.first) end - it "finds a generic library" do + it 'finds a generic library' do dir = File.realpath(File.join('spec', 'fixtures', 'workspace')) file = '/external.rb' uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) diff --git a/spec/language_server/host/message_worker_spec.rb b/spec/language_server/host/message_worker_spec.rb index d5ce91da6..e2f83d952 100644 --- a/spec/language_server/host/message_worker_spec.rb +++ b/spec/language_server/host/message_worker_spec.rb @@ -1,7 +1,7 @@ describe Solargraph::LanguageServer::Host::MessageWorker do - it "handle requests on queue" do + it 'handle requests on queue' do host = instance_double(Solargraph::LanguageServer::Host) - message = {'method' => '$/example'} + message = { 'method' => '$/example' } allow(host).to receive(:receive).with(message).and_return(nil) worker = Solargraph::LanguageServer::Host::MessageWorker.new(host) diff --git a/spec/language_server/host_spec.rb b/spec/language_server/host_spec.rb index 6700b41cb..95d5ee3bb 100644 --- a/spec/language_server/host_spec.rb +++ b/spec/language_server/host_spec.rb @@ -1,15 +1,15 @@ require 'tmpdir' describe Solargraph::LanguageServer::Host do - it "prepares a workspace" do + it 'prepares a workspace' do host = Solargraph::LanguageServer::Host.new Dir.mktmpdir do |dir| - host.prepare (dir) - expect(host.libraries.first).not_to be(nil) + host.prepare(dir) + expect(host.libraries.first).not_to be_nil end end - it "processes responses to message requests" do + it 'processes responses to message requests' do host = Solargraph::LanguageServer::Host.new done_somethings = 0 host.send_request 'window/showMessageRequest', { @@ -20,13 +20,13 @@ end expect(host.pending_requests.length).to eq(1) host.receive({ - 'id' => host.pending_requests.first, - 'result' => 'Do something' - }) + 'id' => host.pending_requests.first, + 'result' => 'Do something' + }) expect(done_somethings).to eq(1) end - it "creates files from disk" do + it 'creates files from disk' do Dir.mktmpdir do |dir| host = Solargraph::LanguageServer::Host.new host.prepare dir @@ -38,26 +38,26 @@ end end - it "deletes files" do + it 'deletes files' do Dir.mktmpdir do |dir| - expect { + expect do host = Solargraph::LanguageServer::Host.new file = File.join(dir, 'test.rb') File.write(file, "foo = 'foo'") host.prepare dir uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) host.delete(uri) - }.not_to raise_error + end.not_to raise_error end end - it "cancels requests" do + it 'cancels requests' do host = Solargraph::LanguageServer::Host.new host.cancel 1 expect(host.cancel?(1)).to be(true) end - it "runs diagnostics on opened files" do + it 'runs diagnostics on opened files' do Dir.mktmpdir do |dir| host = Solargraph::LanguageServer::Host.new host.configure({ 'diagnostics' => true }) @@ -65,7 +65,7 @@ File.write(file, "foo = 'foo'") host.start host.prepare dir - uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) + Solargraph::LanguageServer::UriHelpers.file_to_uri(file) host.open(file, File.read(file), 1) buffer = host.flush times = 0 @@ -81,7 +81,7 @@ end end - it "handles DiagnosticsErrors" do + it 'handles DiagnosticsErrors' do host = Solargraph::LanguageServer::Host.new library = instance_double(Solargraph::Library) allow(library).to receive(:diagnose).and_raise(Solargraph::DiagnosticsError) @@ -94,17 +94,17 @@ # @todo Smelly instance variable access host.instance_variable_set(:@libraries, [library]) host.open('file:///test.rb', '', 0) - expect { + expect do host.diagnose 'file:///test.rb' - }.not_to raise_error + end.not_to raise_error result = host.flush expect(result).to include('Error in diagnostics') end - it "opens multiple folders" do + it 'opens multiple folders' do host = Solargraph::LanguageServer::Host.new - app1_folder = File.absolute_path('spec/fixtures/workspace_folders/folder1').gsub(/\\/, '/') - app2_folder = File.absolute_path('spec/fixtures/workspace_folders/folder2').gsub(/\\/, '/') + app1_folder = File.absolute_path('spec/fixtures/workspace_folders/folder1').gsub('\\', '/') + app2_folder = File.absolute_path('spec/fixtures/workspace_folders/folder2').gsub('\\', '/') host.prepare(app1_folder) host.prepare(app2_folder) file1_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri("#{app1_folder}/app.rb") @@ -119,13 +119,13 @@ expect(app2_map).not_to include('Folder1App') end - it "stops" do + it 'stops' do host = Solargraph::LanguageServer::Host.new host.stop expect(host.stopped?).to be(true) end - it "retains orphaned sources" do + it 'retains orphaned sources' do dir = File.absolute_path('spec/fixtures/workspace') file = File.join(dir, 'lib', 'thing.rb') file_uri = Solargraph::LanguageServer::UriHelpers.uri_to_file(file) @@ -133,12 +133,12 @@ host.prepare(dir) host.open(file_uri, File.read(file), 1) host.remove(dir) - expect{ + expect do host.document_symbols(file_uri) - }.not_to raise_error + end.not_to raise_error end - it "responds with empty diagnostics for unopened files" do + it 'responds with empty diagnostics for unopened files' do host = Solargraph::LanguageServer::Host.new host.diagnose 'file:///file.rb' response = host.flush @@ -147,28 +147,28 @@ expect(json['params']['diagnostics']).to be_empty end - it "rescues runtime errors from messages" do + it 'rescues runtime errors from messages' do host = Solargraph::LanguageServer::Host.new message_class = Class.new(Solargraph::LanguageServer::Message::Base) do def process - raise RuntimeError, 'Always raise an error from this message' + raise 'Always raise an error from this message' end end Solargraph::LanguageServer::Message.register('raiseRuntimeError', message_class) - expect { + expect do host.receive({ - 'id' => 1, - 'method' => 'raiseRuntimeError', - 'params' => {} - }) - }.not_to raise_error + 'id' => 1, + 'method' => 'raiseRuntimeError', + 'params' => {} + }) + end.not_to raise_error end - it "ignores invalid messages" do + it 'ignores invalid messages' do host = Solargraph::LanguageServer::Host.new - expect { + expect do host.receive({ 'bad' => 'message' }) - }.not_to raise_error + end.not_to raise_error end it 'repairs simple breaking changes without incremental sync' do @@ -179,16 +179,16 @@ def process host.open uri, 'Foo::Bar', 1 sleep 0.1 until host.libraries.all?(&:mapped?) host.change({ - "textDocument" => { - "uri" => uri, - 'version' => 2 - }, - "contentChanges" => [ - { - "text" => "Foo::Bar." - } - ] - }) + 'textDocument' => { + 'uri' => uri, + 'version' => 2 + }, + 'contentChanges' => [ + { + 'text' => 'Foo::Bar.' + } + ] + }) source = host.sources.find(uri) # @todo Smelly private method access expect(source.send(:repaired)).to eq('Foo::Bar ') @@ -211,23 +211,23 @@ def initialize(foo); end host.open uri, code, 1 sleep 0.1 until host.libraries.all?(&:mapped?) result = host.locate_pins({ - "data" => { - "uri" => uri, - "location" => { - "range" => { - "start" => { - "line" => 5, - "character" => 12 - }, - "end" => { - "line" => 5, - "character" => 15 - } - } - }, - "path" => "Example.new" - } - }) + 'data' => { + 'uri' => uri, + 'location' => { + 'range' => { + 'start' => { + 'line' => 5, + 'character' => 12 + }, + 'end' => { + 'line' => 5, + 'character' => 15 + } + } + }, + 'path' => 'Example.new' + } + }) expect(result.map(&:path)).to include('Example.new') end end @@ -260,22 +260,22 @@ def initialize(foo); end end end - describe "Workspace variations" do - before :each do + describe 'Workspace variations' do + before do @host = Solargraph::LanguageServer::Host.new end - after :each do + after do @host.stop end - it "creates a library for a file without a workspace" do + it 'creates a library for a file without a workspace' do @host.open('file:///file.rb', 'class Foo; end', 1) symbols = @host.document_symbols('file:///file.rb') expect(symbols).not_to be_empty end - it "opens a file outside of prepared libraries" do + it 'opens a file outside of prepared libraries' 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/completion_item/resolve_spec.rb b/spec/language_server/message/completion_item/resolve_spec.rb index e10f17672..f7f347f16 100644 --- a/spec/language_server/message/completion_item/resolve_spec.rb +++ b/spec/language_server/message/completion_item/resolve_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::LanguageServer::Message::CompletionItem::Resolve do - it "returns MarkupContent for documentation" do + it 'returns MarkupContent for documentation' do pin = Solargraph::Pin::Method.new( location: nil, closure: Solargraph::Pin::Namespace.new(name: 'Foo'), @@ -11,14 +11,14 @@ ) host = instance_double(Solargraph::LanguageServer::Host, locate_pins: [pin], options: { 'enablePages' => true }) resolve = Solargraph::LanguageServer::Message::CompletionItem::Resolve.new(host, { - 'params' => pin.completion_item - }) + 'params' => pin.completion_item + }) resolve.process expect(resolve.result[:documentation][:kind]).to eq('markdown') expect(resolve.result[:documentation][:value]).to include('A method') end - it "returns nil documentation for empty strings" do + it 'returns nil documentation for empty strings' do pin = Solargraph::Pin::InstanceVariable.new( location: nil, closure: Solargraph::Pin::Namespace.new(name: 'Foo'), @@ -27,8 +27,8 @@ ) host = instance_double(Solargraph::LanguageServer::Host, locate_pins: [pin]) resolve = Solargraph::LanguageServer::Message::CompletionItem::Resolve.new(host, { - 'params' => pin.completion_item - }) + 'params' => pin.completion_item + }) resolve.process expect(resolve.result[:documentation]).to be_nil end diff --git a/spec/language_server/message/extended/check_gem_version_spec.rb b/spec/language_server/message/extended/check_gem_version_spec.rb index fc3e46a8e..3f5dd6850 100644 --- a/spec/language_server/message/extended/check_gem_version_spec.rb +++ b/spec/language_server/message/extended/check_gem_version_spec.rb @@ -1,39 +1,39 @@ describe Solargraph::LanguageServer::Message::Extended::CheckGemVersion do - before :each do + before do version = instance_double(Gem::Version, version: Gem::Version.new('1.0.0')) Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = instance_double(Gem::SpecFetcher, search_for_dependency: [version]) end - after :each do + after do Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = nil end - it "checks the gem source" do + it 'checks the gem source' do host = Solargraph::LanguageServer::Host.new message = described_class.new(host, {}) expect { message.process }.not_to raise_error end - it "performs a verbose check" do + it 'performs a verbose check' do host = Solargraph::LanguageServer::Host.new message = described_class.new(host, { 'params' => { 'verbose' => true } }) expect { message.process }.not_to raise_error end - it "detects available updates" do + it 'detects available updates' do host = Solargraph::LanguageServer::Host.new message = described_class.new(host, {}, current: Gem::Version.new('0.0.1')) expect { message.process }.not_to raise_error end - it "performs a verbose check with an available update" do + it 'performs a verbose check with an available update' do host = Solargraph::LanguageServer::Host.new message = described_class.new(host, { 'params' => { 'verbose' => true } }, current: Gem::Version.new('0.0.1')) expect { message.process }.not_to raise_error end - it "responds to update actions" do + it 'responds to update actions' do host = Solargraph::LanguageServer::Host.new message = Solargraph::LanguageServer::Message::Extended::CheckGemVersion.new(host, {}, current: Gem::Version.new('0.0.1')) message.process @@ -43,18 +43,18 @@ response = data end reader.receive host.flush - expect { + expect do action = { - "id" => response['id'], - "result" => response['params']['actions'].first + 'id' => response['id'], + 'result' => response['params']['actions'].first } host.receive action - }.not_to raise_error + end.not_to raise_error end it 'uses bundler' do host = Solargraph::LanguageServer::Host.new - host.configure({'useBundler' => true}) + host.configure({ 'useBundler' => true }) message = Solargraph::LanguageServer::Message::Extended::CheckGemVersion.new(host, {}, current: Gem::Version.new('0.0.1')) message.process response = nil @@ -63,12 +63,12 @@ response = data end reader.receive host.flush - expect { + expect do action = { - "id" => response['id'], - "result" => response['params']['actions'].first + 'id' => response['id'], + 'result' => response['params']['actions'].first } host.receive action - }.not_to raise_error + end.not_to raise_error end end diff --git a/spec/language_server/message/initialize_spec.rb b/spec/language_server/message/initialize_spec.rb index 3ab54e2de..877add68f 100644 --- a/spec/language_server/message/initialize_spec.rb +++ b/spec/language_server/message/initialize_spec.rb @@ -1,56 +1,56 @@ describe Solargraph::LanguageServer::Message::Initialize do - it "prepares workspace folders" do + it 'prepares workspace folders' do host = Solargraph::LanguageServer::Host.new dir = File.realpath(File.join('spec', 'fixtures', 'workspace')) init = Solargraph::LanguageServer::Message::Initialize.new(host, { - 'params' => { - 'capabilities' => { - 'workspace' => { - 'workspaceFolders' => true - } - }, - 'workspaceFolders' => [ - { - 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(dir), - 'name' => 'workspace' - } - ] - } - }) + 'params' => { + 'capabilities' => { + 'workspace' => { + 'workspaceFolders' => true + } + }, + 'workspaceFolders' => [ + { + 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(dir), + 'name' => 'workspace' + } + ] + } + }) init.process expect(host.folders.length).to eq(1) end - it "prepares rootUri as a workspace" do + it 'prepares rootUri as a workspace' do host = Solargraph::LanguageServer::Host.new dir = File.realpath(File.join('spec', 'fixtures', 'workspace')) init = Solargraph::LanguageServer::Message::Initialize.new(host, { - 'params' => { - 'capabilities' => { - 'workspace' => { - 'workspaceFolders' => true - } - }, - 'rootUri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(dir) - } - }) + 'params' => { + 'capabilities' => { + 'workspace' => { + 'workspaceFolders' => true + } + }, + 'rootUri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(dir) + } + }) init.process expect(host.folders.length).to eq(1) end - it "prepares rootPath as a workspace" do + it 'prepares rootPath as a workspace' do host = Solargraph::LanguageServer::Host.new dir = File.realpath(File.join('spec', 'fixtures', 'workspace')) init = Solargraph::LanguageServer::Message::Initialize.new(host, { - 'params' => { - 'capabilities' => { - 'workspace' => { - 'workspaceFolders' => true - } - }, - 'rootPath' => dir - } - }) + 'params' => { + 'capabilities' => { + 'workspace' => { + 'workspaceFolders' => true + } + }, + 'rootPath' => dir + } + }) init.process expect(host.folders.length).to eq(1) end @@ -62,33 +62,35 @@ result = init.result expect(result).to include(:capabilities) expect(result[:capabilities]).to eq({ - textDocumentSync: 2, - workspace: { workspaceFolders: { supported: true, changeNotifications: true } }, - completionProvider: { resolveProvider: true, triggerCharacters: ['.', ':', '@'] }, - signatureHelpProvider: { triggerCharacters: ['(', ','] }, - hoverProvider: true, - documentSymbolProvider: true, - definitionProvider: true, - typeDefinitionProvider: true, - renameProvider: { prepareProvider: true }, - referencesProvider: true, - workspaceSymbolProvider: true, - foldingRangeProvider: true, - documentHighlightProvider: true - }) + textDocumentSync: 2, + workspace: { workspaceFolders: { supported: true, + changeNotifications: true } }, + completionProvider: { resolveProvider: true, + triggerCharacters: ['.', ':', '@'] }, + signatureHelpProvider: { triggerCharacters: ['(', ','] }, + hoverProvider: true, + documentSymbolProvider: true, + definitionProvider: true, + typeDefinitionProvider: true, + renameProvider: { prepareProvider: true }, + referencesProvider: true, + workspaceSymbolProvider: true, + foldingRangeProvider: true, + documentHighlightProvider: true + }) end it 'returns all capabilities when all options are enabled' do host = Solargraph::LanguageServer::Host.new init = Solargraph::LanguageServer::Message::Initialize.new(host, { - 'params' => { - 'initializationOptions' => { - 'completion' => true, - 'autoformat' => true, - 'formatting' => true - } - } - }) + 'params' => { + 'initializationOptions' => { + 'completion' => true, + 'autoformat' => true, + 'formatting' => true + } + } + }) init.process result = init.result diff --git a/spec/language_server/message/text_document/definition_spec.rb b/spec/language_server/message/text_document/definition_spec.rb index b6c98b99b..1266e4aa0 100644 --- a/spec/language_server/message/text_document/definition_spec.rb +++ b/spec/language_server/message/text_document/definition_spec.rb @@ -36,16 +36,16 @@ file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/other.rb')) other_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/thing.rb')) message = Solargraph::LanguageServer::Message::TextDocument::Definition.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => file_uri - }, - 'position' => { - 'line' => 4, - 'character' => 10 - } - } - }) + 'params' => { + 'textDocument' => { + 'uri' => file_uri + }, + 'position' => { + 'line' => 4, + 'character' => 10 + } + } + }) message.process expect(message.result.first[:uri]).to eq(other_uri) end @@ -57,17 +57,20 @@ sleep 0.1 until host.libraries.all?(&:mapped?) host.catalog message = Solargraph::LanguageServer::Message::TextDocument::Definition.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(File.join(path, 'lib', 'other.rb')) - }, - 'position' => { - 'line' => 0, - 'character' => 10 - } - } - }) + 'params' => { + 'textDocument' => { + 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(File.join( + path, 'lib', 'other.rb' + )) + }, + 'position' => { + 'line' => 0, + 'character' => 10 + } + } + }) message.process - expect(message.result.first[:uri]).to eq(Solargraph::LanguageServer::UriHelpers.file_to_uri(File.join(path, 'lib', 'thing.rb'))) + expect(message.result.first[:uri]).to eq(Solargraph::LanguageServer::UriHelpers.file_to_uri(File.join(path, 'lib', + 'thing.rb'))) end end diff --git a/spec/language_server/message/text_document/formatting_spec.rb b/spec/language_server/message/text_document/formatting_spec.rb index 778046005..cf68f3cf3 100644 --- a/spec/language_server/message/text_document/formatting_spec.rb +++ b/spec/language_server/message/text_document/formatting_spec.rb @@ -3,7 +3,7 @@ host = instance_double(Solargraph::LanguageServer::Host, read_text: '', formatter_config: {}) request = { 'params' => { - 'textDocument' => { + 'textDocument' => { 'uri' => 'test.rb' } } diff --git a/spec/language_server/message/text_document/hover_spec.rb b/spec/language_server/message/text_document/hover_spec.rb index 74eaee597..93f05c127 100644 --- a/spec/language_server/message/text_document/hover_spec.rb +++ b/spec/language_server/message/text_document/hover_spec.rb @@ -5,16 +5,16 @@ sleep 0.1 until host.libraries.all?(&:mapped?) host.catalog message = Solargraph::LanguageServer::Message::TextDocument::Hover.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => 'file://spec/fixtures/workspace/lib/other.rb' - }, - 'position' => { - 'line' => 5, - 'character' => 0 - } - } - }) + 'params' => { + 'textDocument' => { + 'uri' => 'file://spec/fixtures/workspace/lib/other.rb' + }, + 'position' => { + 'line' => 5, + 'character' => 0 + } + } + }) message.process expect(message.result).to be_nil end @@ -30,16 +30,16 @@ def foo host.open('file:///test.rb', code, 1) host.catalog message = Solargraph::LanguageServer::Message::TextDocument::Hover.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => 'file:///test.rb' - }, - 'position' => { - 'line' => 4, - 'character' => 6 - } - } - }) + 'params' => { + 'textDocument' => { + 'uri' => 'file:///test.rb' + }, + 'position' => { + 'line' => 4, + 'character' => 6 + } + } + }) message.process expect(message.result[:contents][:value]).to eq("x\n\n`=~ String`") end diff --git a/spec/language_server/message/text_document/rename_spec.rb b/spec/language_server/message/text_document/rename_spec.rb index b16110455..900752e63 100644 --- a/spec/language_server/message/text_document/rename_spec.rb +++ b/spec/language_server/message/text_document/rename_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe Solargraph::LanguageServer::Message::TextDocument::Rename do - it "renames a symbol" do + it 'renames a symbol' do host = Solargraph::LanguageServer::Host.new host.start host.open('file:///file.rb', %( @@ -11,24 +11,24 @@ class Foo ), 1) sleep 0.01 until host.libraries.all?(&:mapped?) rename = Solargraph::LanguageServer::Message::TextDocument::Rename.new(host, { - 'id' => 1, - 'method' => 'textDocument/rename', - 'params' => { - 'textDocument' => { - 'uri' => 'file:///file.rb' - }, - 'position' => { - 'line' => 1, - 'character' => 12 - }, - 'newName' => 'Bar' - } - }) + 'id' => 1, + 'method' => 'textDocument/rename', + 'params' => { + 'textDocument' => { + 'uri' => 'file:///file.rb' + }, + 'position' => { + 'line' => 1, + 'character' => 12 + }, + 'newName' => 'Bar' + } + }) rename.process expect(rename.result[:changes]['file:///file.rb'].length).to eq(2) end - it "renames an argument symbol from method signature" do + it 'renames an argument symbol from method signature' do host = Solargraph::LanguageServer::Host.new host.start host.open('file:///file.rb', %( @@ -41,24 +41,24 @@ def foo(bar) ), 1) rename = Solargraph::LanguageServer::Message::TextDocument::Rename.new(host, { - 'id' => 1, - 'method' => 'textDocument/rename', - 'params' => { - 'textDocument' => { - 'uri' => 'file:///file.rb' - }, - 'position' => { - 'line' => 2, - 'character' => 14 - }, - 'newName' => 'baz' - } - }) + 'id' => 1, + 'method' => 'textDocument/rename', + 'params' => { + 'textDocument' => { + 'uri' => 'file:///file.rb' + }, + 'position' => { + 'line' => 2, + 'character' => 14 + }, + 'newName' => 'baz' + } + }) rename.process expect(rename.result[:changes]['file:///file.rb'].length).to eq(3) end - it "renames an argument symbol from method body" do + it 'renames an argument symbol from method body' do host = Solargraph::LanguageServer::Host.new host.start host.open('file:///file.rb', %( @@ -70,24 +70,24 @@ def foo(bar) end ), 1) rename = Solargraph::LanguageServer::Message::TextDocument::Rename.new(host, { - 'id' => 1, - 'method' => 'textDocument/rename', - 'params' => { - 'textDocument' => { - 'uri' => 'file:///file.rb' - }, - 'position' => { - 'line' => 3, - 'character' => 6 - }, - 'newName' => 'baz' - } - }) + 'id' => 1, + 'method' => 'textDocument/rename', + 'params' => { + 'textDocument' => { + 'uri' => 'file:///file.rb' + }, + 'position' => { + 'line' => 3, + 'character' => 6 + }, + 'newName' => 'baz' + } + }) rename.process expect(rename.result[:changes]['file:///file.rb'].length).to eq(3) end - it "renames namespace symbol with proper range" do + it 'renames namespace symbol with proper range' do host = Solargraph::LanguageServer::Host.new host.start host.open('file:///file.rb', %( @@ -97,19 +97,19 @@ class Namespace::ExampleClass obj = Namespace::ExampleClass.new ), 1) rename = Solargraph::LanguageServer::Message::TextDocument::Rename.new(host, { - 'id' => 1, - 'method' => 'textDocument/rename', - 'params' => { - 'textDocument' => { - 'uri' => 'file:///file.rb' - }, - 'position' => { - 'line' => 2, - 'character' => 12 - }, - 'newName' => 'Nameplace' - } - }) + 'id' => 1, + 'method' => 'textDocument/rename', + 'params' => { + 'textDocument' => { + 'uri' => 'file:///file.rb' + }, + 'position' => { + 'line' => 2, + 'character' => 12 + }, + 'newName' => 'Nameplace' + } + }) rename.process changes = rename.result[:changes]['file:///file.rb'] expect(changes.length).to eq(3) diff --git a/spec/language_server/message/text_document/type_definition_spec.rb b/spec/language_server/message/text_document/type_definition_spec.rb index 2f7ec3668..7fdd9ce6f 100644 --- a/spec/language_server/message/text_document/type_definition_spec.rb +++ b/spec/language_server/message/text_document/type_definition_spec.rb @@ -7,16 +7,16 @@ file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/other.rb')) something_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/something.rb')) message = Solargraph::LanguageServer::Message::TextDocument::TypeDefinition.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => file_uri - }, - 'position' => { - 'line' => 4, - 'character' => 10 - } - } - }) + 'params' => { + 'textDocument' => { + 'uri' => file_uri + }, + 'position' => { + 'line' => 4, + 'character' => 10 + } + } + }) message.process expect(message.result.first[:uri]).to eq(something_uri) end diff --git a/spec/language_server/message/workspace/did_change_watched_files_spec.rb b/spec/language_server/message/workspace/did_change_watched_files_spec.rb index 034e66726..52e224533 100644 --- a/spec/language_server/message/workspace/did_change_watched_files_spec.rb +++ b/spec/language_server/message/workspace/did_change_watched_files_spec.rb @@ -9,16 +9,16 @@ File.write file, 'class Foo; end' uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) changed = Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles.new(host, { - 'method' => 'workspace/didChangeWatchedFiles', - 'params' => { - 'changes' => [ - { - 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::CREATED, - 'uri' => uri - } - ] - } - }) + 'method' => 'workspace/didChangeWatchedFiles', + 'params' => { + 'changes' => [ + { + 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::CREATED, + 'uri' => uri + } + ] + } + }) changed.process expect(host.synchronizing?).to be(false) expect(host.library_for(uri)).to be_a(Solargraph::Library) @@ -34,21 +34,21 @@ host = Solargraph::LanguageServer::Host.new host.prepare dir changed = Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles.new(host, { - 'method' => 'workspace/didChangeWatchedFiles', - 'params' => { - 'changes' => [ - { - 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::DELETED, - 'uri' => uri - } - ] - } - }) + 'method' => 'workspace/didChangeWatchedFiles', + 'params' => { + 'changes' => [ + { + 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::DELETED, + 'uri' => uri + } + ] + } + }) changed.process expect(host.synchronizing?).to be(false) - expect { + expect do host.library_for(uri) - }.to raise_error(Solargraph::FileNotFoundError) + end.to raise_error(Solargraph::FileNotFoundError) end end @@ -61,16 +61,16 @@ host.prepare dir File.write file, 'class FooBar; end' changed = Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles.new(host, { - 'method' => 'workspace/didChangeWatchedFiles', - 'params' => { - 'changes' => [ - { - 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::CHANGED, - 'uri' => uri - } - ] - } - }) + 'method' => 'workspace/didChangeWatchedFiles', + 'params' => { + 'changes' => [ + { + 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::CHANGED, + 'uri' => uri + } + ] + } + }) changed.process expect(host.synchronizing?).to be(false) library = host.library_for(uri) @@ -85,16 +85,16 @@ allow(host).to receive(:create) allow(host).to receive(:delete) changed = Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles.new(host, { - 'method' => 'workspace/didChangeWatchedFiles', - 'params' => { - 'changes' => [ - { - 'type' => -1, - 'uri' => 'file:///foo.rb' - } - ] - } - }) + 'method' => 'workspace/didChangeWatchedFiles', + 'params' => { + 'changes' => [ + { + 'type' => -1, + 'uri' => 'file:///foo.rb' + } + ] + } + }) changed.process expect(changed.error).not_to be_nil end diff --git a/spec/language_server/message_spec.rb b/spec/language_server/message_spec.rb index 9a8c26328..4dca484d5 100644 --- a/spec/language_server/message_spec.rb +++ b/spec/language_server/message_spec.rb @@ -1,10 +1,10 @@ describe Solargraph::LanguageServer::Message do - it "returns MethodNotFound for unregistered methods" do + it 'returns MethodNotFound for unregistered methods' do msg = Solargraph::LanguageServer::Message.select 'notARealMethod' expect(msg).to be(Solargraph::LanguageServer::Message::MethodNotFound) end - it "returns MethodNotImplemented for unregistered $ methods" do + it 'returns MethodNotImplemented for unregistered $ methods' do msg = Solargraph::LanguageServer::Message.select '$/notARealMethod' expect(msg).to be(Solargraph::LanguageServer::Message::MethodNotImplemented) end diff --git a/spec/language_server/protocol_spec.rb b/spec/language_server/protocol_spec.rb index 1d7d98637..49f9bdea8 100644 --- a/spec/language_server/protocol_spec.rb +++ b/spec/language_server/protocol_spec.rb @@ -1,5 +1,3 @@ -require 'thread' - class Protocol attr_reader :response @@ -43,17 +41,17 @@ def stop @protocol.stop end - before :each do + before do version = instance_double(Gem::Version, version: Gem::Version.new('1.0.0')) Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = instance_double(Gem::SpecFetcher, search_for_dependency: [version]) end - after :each do + after do Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = nil end - it "handles initialize" do + it 'handles initialize' do @protocol.request 'initialize', { 'capabilities' => { 'textDocument' => { @@ -70,27 +68,27 @@ def stop expect(response['result'].keys).to include('capabilities') end - it "is not stopped after initialization" do + it 'is not stopped after initialization' do expect(@protocol.host.stopped?).to be(false) end - it "configured dynamic registration capabilities from initialize" do + it 'configured dynamic registration capabilities from initialize' do expect(@protocol.host.can_register?('textDocument/completion')).to be(true) expect(@protocol.host.can_register?('textDocument/hover')).to be(false) expect(@protocol.host.can_register?('workspace/symbol')).to be(false) end - it "handles initialized" do + it 'handles initialized' do @protocol.request 'initialized', nil response = @protocol.response expect(response['error']).to be_nil end - it "configured default dynamic registration capabilities from initialized" do + it 'configured default dynamic registration capabilities from initialized' do expect(@protocol.host.registered?('textDocument/completion')).to be(true) end - it "handles textDocument/didOpen" do + it 'handles textDocument/didOpen' do @protocol.request 'textDocument/didOpen', { 'textDocument' => { 'uri' => 'file:///file.rb', @@ -108,7 +106,7 @@ def bar baz 'version' => 0 } } - response = @protocol.response + @protocol.response expect(@protocol.host.open?('file:///file.rb')).to be(true) end @@ -127,7 +125,7 @@ def bar baz expect(response['result'].length).to eq(2) end - it "handles textDocument/didChange" do + it 'handles textDocument/didChange' do @protocol.request 'textDocument/didChange', { 'textDocument' => { 'uri' => 'file:///file.rb', @@ -153,7 +151,7 @@ def bar baz expect(response).not_to be_nil end - it "handles textDocument/completion" do + it 'handles textDocument/completion' do @protocol.request 'textDocument/completion', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -168,10 +166,10 @@ def bar baz expect(response['result']['items'].length > 0).to be(true) end - it "handles completionItem/resolve" do + it 'handles completionItem/resolve' do # Reuse the response from textDocument/completion response = @protocol.response - item = response['result']['items'].select{|h| h['label'] == 'bar'}.first + item = response['result']['items'].select { |h| h['label'] == 'bar' }.first @protocol.request 'completionItem/resolve', item response = @protocol.response expect(response['result']['documentation']['value']).to include('bar method') @@ -190,7 +188,7 @@ def bar baz expect(@protocol.response['error']).to be_nil end - it "documents YARD pins" do + it 'documents YARD pins' do @protocol.request 'textDocument/completion', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -201,14 +199,14 @@ def bar baz } } response = @protocol.response - item = response['result']['items'].select{|i| i['data']['path'] == 'File.absolute_path'}.first + item = response['result']['items'].select { |i| i['data']['path'] == 'File.absolute_path' }.first expect(item).not_to be_nil @protocol.request 'completionItem/resolve', item response = @protocol.response expect(response['result']['documentation']).not_to be_empty end - it "handles textDocument/definition" do + it 'handles textDocument/definition' do sleep 0.5 # HACK: Give the Host::Sources thread time to work @protocol.request 'textDocument/definition', { 'textDocument' => { @@ -224,7 +222,7 @@ def bar baz expect(response['result']).not_to be_empty end - it "handles textDocument/definition on undefined symbols" do + it 'handles textDocument/definition on undefined symbols' do @protocol.request 'textDocument/definition', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -239,7 +237,7 @@ def bar baz expect(response['result']).to be_empty end - it "handles textDocument/documentSymbol" do + it 'handles textDocument/documentSymbol' do @protocol.request 'textDocument/documentSymbol', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -249,7 +247,7 @@ def bar baz expect(response['error']).to be_nil end - it "handles textDocument/hover" do + it 'handles textDocument/hover' do @protocol.request 'textDocument/hover', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -278,7 +276,7 @@ def bar baz expect(@protocol.response['error']).to be_nil end - it "handles textDocument/signatureHelp" do + it 'handles textDocument/signatureHelp' do @protocol.request 'textDocument/signatureHelp', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -293,7 +291,7 @@ def bar baz expect(response['result']['signatures']).not_to be_empty end - it "handles workspace/symbol" do + it 'handles workspace/symbol' do @protocol.request 'workspace/symbol', { 'query' => 'Foo' } @@ -302,7 +300,7 @@ def bar baz expect(response['result']).not_to be_empty end - it "handles textDocument/references for namespaces" do + it 'handles textDocument/references for namespaces' do @protocol.request 'textDocument/references', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -317,7 +315,7 @@ def bar baz expect(response['result']).not_to be_empty end - it "handles textDocument/references for methods" do + it 'handles textDocument/references for methods' do @protocol.request 'textDocument/references', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -332,7 +330,7 @@ def bar baz expect(response['result']).not_to be_empty end - it "handles textDocument/rename" do + it 'handles textDocument/rename' do @protocol.request 'textDocument/rename', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -348,7 +346,7 @@ def bar baz expect(response['result']['changes']['file:///file.rb']).to be_a(Array) end - it "handles textDocument/prepareRename" do + it 'handles textDocument/prepareRename' do @protocol.request 'textDocument/prepareRename', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -364,7 +362,7 @@ def bar baz expect(response['result']).to be_a(Hash) end - it "handles textDocument/foldingRange" do + it 'handles textDocument/foldingRange' do @protocol.request 'textDocument/foldingRange', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -375,17 +373,17 @@ def bar baz expect(response['result'].length).not_to be_zero end - it "handles textDocument/didClose" do + it 'handles textDocument/didClose' do @protocol.request 'textDocument/didClose', { 'textDocument' => { 'uri' => 'file:///file.rb' } } - response = @protocol.response + @protocol.response expect(@protocol.host.open?('file:///file.rb')).to be(false) end - it "handles $/solargraph/search" do + it 'handles $/solargraph/search' do @protocol.request '$/solargraph/search', { 'query' => 'Foo#bar' } @@ -394,7 +392,7 @@ def bar baz expect(response['result']['content']).not_to be_empty end - it "handles $/solargraph/document" do + it 'handles $/solargraph/document' do @protocol.request '$/solargraph/document', { 'query' => 'String' } @@ -403,7 +401,7 @@ def bar baz expect(response['result']['content']).not_to be_empty end - it "handles workspace/didChangeConfiguration" do + it 'handles workspace/didChangeConfiguration' do @protocol.request 'workspace/didChangeConfiguration', { 'settings' => { 'solargraph' => { @@ -416,7 +414,7 @@ def bar baz expect(@protocol.host.registered?('textDocument/completion')).to be(false) end - it "handles $/solargraph/checkGemVersion" do + it 'handles $/solargraph/checkGemVersion' do @protocol.request '$/solargraph/checkGemVersion', { verbose: false } response = @protocol.response expect(response['error']).to be_nil @@ -424,13 +422,13 @@ def bar baz expect(response['result']['available']).to be_a(String) end - it "handles $/solargraph/documentGems" do + it 'handles $/solargraph/documentGems' do @protocol.request '$/solargraph/documentGems', {} response = @protocol.response expect(response['error']).to be_nil end - it "handles textDocument/formatting" do + it 'handles textDocument/formatting' do @protocol.request 'textDocument/didOpen', { 'textDocument' => { 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(File.realpath('spec/fixtures/formattable.rb')), @@ -448,7 +446,7 @@ def bar baz expect(response['result'].first['newText']).to be_a(String) end - it "can format file without file extension" do + it 'can format file without file extension' do @protocol.request 'textDocument/didOpen', { 'textDocument' => { 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(File.realpath('spec/fixtures/formattable')), @@ -467,13 +465,13 @@ def bar baz # expect(response['result'].first['newText']).to include('def barbaz(parameter); end') end - it "handles MethodNotFound errors" do + it 'handles MethodNotFound errors' do @protocol.request 'notamethod', {} response = @protocol.response expect(response['error']['code']).to be(Solargraph::LanguageServer::ErrorCodes::METHOD_NOT_FOUND) end - it "handles didChangeWatchedFiles for created files" do + it 'handles didChangeWatchedFiles for created files' do @protocol.request 'workspace/didChangeWatchedFiles', { 'changes' => [ { @@ -486,7 +484,7 @@ def bar baz expect(response['error']).to be_nil end - it "handles didChangeWatchedFiles for changed files" do + it 'handles didChangeWatchedFiles for changed files' do @protocol.request 'workspace/didChangeWatchedFiles', { 'changes' => [ { @@ -499,7 +497,7 @@ def bar baz expect(response['error']).to be_nil end - it "handles didChangeWatchedFiles for deleted files" do + it 'handles didChangeWatchedFiles for deleted files' do @protocol.request 'workspace/didChangeWatchedFiles', { 'changes' => [ { @@ -512,11 +510,11 @@ def bar baz expect(response['error']).to be_nil end - it "handles didChangeWatchedFiles for invalid change types" do + it 'handles didChangeWatchedFiles for invalid change types' do @protocol.request 'workspace/didChangeWatchedFiles', { 'changes' => [ { - 'type' => -99999, + 'type' => -99_999, 'uri' => 'file:///watched-file.rb' } ] @@ -525,7 +523,7 @@ def bar baz expect(response['error']).not_to be_nil end - it "adds folders to the workspace" do + it 'adds folders to the workspace' do dir = File.absolute_path('spec/fixtures/workspace_folders/folder1') uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(dir) @protocol.request 'workspace/didChangeWorkspaceFolders', { @@ -542,7 +540,7 @@ def bar baz expect(@protocol.host.folders).to include(dir) end - it "removes folders from the workspace" do + it 'removes folders from the workspace' do dir = File.absolute_path('spec/fixtures/workspace_folders/folder1') uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(dir) @protocol.request 'workspace/didChangeWorkspaceFolders', { @@ -559,27 +557,27 @@ def bar baz expect(@protocol.host.folders).not_to include(dir) end - it "handles $/cancelRequest" do - expect { + it 'handles $/cancelRequest' do + expect do @protocol.request '$/cancelRequest', { 'id' => 0 } - }.not_to raise_error + end.not_to raise_error end - it "handles $/solargraph/environment" do + it 'handles $/solargraph/environment' do @protocol.request '$/solargraph/environment', {} response = @protocol.response expect(response['result']['content']).not_to be_nil end - it "handles shutdown" do + it 'handles shutdown' do @protocol.request 'shutdown', {} response = @protocol.response expect(response['error']).to be_nil end - it "handles exit" do + it 'handles exit' do @protocol.request 'exit', {} response = @protocol.response expect(response['error']).to be_nil diff --git a/spec/language_server/transport/adapter_spec.rb b/spec/language_server/transport/adapter_spec.rb index 3d30dd4e6..6ec360395 100644 --- a/spec/language_server/transport/adapter_spec.rb +++ b/spec/language_server/transport/adapter_spec.rb @@ -15,21 +15,21 @@ def flush end describe Solargraph::LanguageServer::Transport::Adapter do - it "creates a host on open" do + it 'creates a host on open' do tester = AdapterTester.new tester.opening expect(tester.host).to be_a(Solargraph::LanguageServer::Host) expect(tester.host).not_to be_stopped end - it "stops a host on close" do + it 'stops a host on close' do tester = AdapterTester.new tester.opening tester.closing expect(tester.host).to be_stopped end - it "stops Backport when the host stops" do + it 'stops Backport when the host stops' do tester = AdapterTester.new Backport.run do tester.opening @@ -40,13 +40,13 @@ def flush expect(tester.host).to be_stopped end - it "processes sent data" do + it 'processes sent data' do tester = AdapterTester.new tester.opening message = '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}' - expect { + expect do tester.receiving "Content-Length: #{message.length}\r\n\r\n#{message}" - }.not_to raise_error + end.not_to raise_error tester.closing end end diff --git a/spec/language_server/transport/data_reader_spec.rb b/spec/language_server/transport/data_reader_spec.rb index c035d2b2c..bf9bbcc15 100644 --- a/spec/language_server/transport/data_reader_spec.rb +++ b/spec/language_server/transport/data_reader_spec.rb @@ -1,8 +1,8 @@ describe Solargraph::LanguageServer::Transport::DataReader do - it "rescues exceptions for invalid JSON" do + it 'rescues exceptions for invalid JSON' do reader = Solargraph::LanguageServer::Transport::DataReader.new handled = 0 - reader.set_message_handler do |msg| + reader.set_message_handler do |_msg| handled += 1 end msg = { @@ -10,9 +10,9 @@ method: 'test' }.to_json msg += '}' - expect { + expect do reader.receive "Content-Length:#{msg.bytesize}\r\n\r\n#{msg}" - }.not_to raise_error + end.not_to raise_error expect(handled).to eq(0) end end diff --git a/spec/library_spec.rb b/spec/library_spec.rb index 2e1017502..60e1c9316 100644 --- a/spec/library_spec.rb +++ b/spec/library_spec.rb @@ -2,7 +2,7 @@ require 'yard' describe Solargraph::Library do - it "does not open created files in the workspace" do + it 'does not open created files in the workspace' do Dir.mktmpdir do |temp_dir_path| # Ensure we resolve any symlinks to their real path workspace_path = File.realpath(temp_dir_path) @@ -15,7 +15,7 @@ end end - it "returns a Completion" do + it 'returns a Completion' do library = Solargraph::Library.new library.attach Solargraph::Source.load_string(%( x = 1 @@ -31,7 +31,7 @@ Solargraph::Shell.new.uncache('backport') end - it "returns a Completion", time_limit_seconds: 50 do + 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(%( @@ -42,7 +42,6 @@ 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 @@ -57,7 +56,7 @@ def foo(adapter) Solargraph::Shell.new.gems('backport') end - it "returns a Completion" do + it 'returns a Completion' do library = Solargraph::Library.new(Solargraph::Workspace.new(Dir.pwd, Solargraph::Workspace::Config.new)) library.attach Solargraph::Source.load_string(%( @@ -74,7 +73,7 @@ def foo(adapter) end end - it "gets definitions from a file" do + it 'gets definitions from a file' do library = Solargraph::Library.new src = Solargraph::Source.load_string %( class Foo @@ -87,7 +86,7 @@ def bar expect(paths).to include('Foo#bar') end - it "gets type definitions from a file" do + it 'gets type definitions from a file' do library = Solargraph::Library.new src = Solargraph::Source.load_string %( class Bar; end @@ -103,7 +102,7 @@ def self.bar expect(paths).to include('Bar') end - it "signifies method arguments" do + it 'signifies method arguments' do library = Solargraph::Library.new src = Solargraph::Source.load_string %( class Foo @@ -118,14 +117,14 @@ def bar baz, key: '' expect(pins.first.path).to eq('Foo#bar') end - it "ignores invalid filenames in create_from_disk" do + it 'ignores invalid filenames in create_from_disk' do library = Solargraph::Library.new filename = 'not_a_real_file.rb' expect(library.create_from_disk(filename)).to be(false) expect(library.contain?(filename)).to be(false) end - it "adds mergeable files to the workspace in create_from_disk" do + it 'adds mergeable files to the workspace in create_from_disk' do Dir.mktmpdir do |temp_dir_path| # Ensure we resolve any symlinks to their real path workspace_path = File.realpath(temp_dir_path) @@ -137,7 +136,7 @@ def bar baz, key: '' end end - it "ignores non-mergeable files in create_from_disk" do + it 'ignores non-mergeable files in create_from_disk' do Dir.mktmpdir do |dir| library = Solargraph::Library.load(dir) filename = File.join(dir, 'created.txt') @@ -147,7 +146,7 @@ def bar baz, key: '' end end - it "diagnoses files" do + it 'diagnoses files' do library = Solargraph::Library.new src = Solargraph::Source.load_string(%( puts 'hello' @@ -172,7 +171,7 @@ def bar baz, key: '' expect(result.to_s).to include('rubocop') end - it "documents symbols" do + it 'documents symbols' do library = Solargraph::Library.new src = Solargraph::Source.load_string(%( class Foo @@ -188,7 +187,7 @@ def bar end describe '#references_from' do - it "collects references to a new method on a constant from assignment of Class.new" 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(%( @@ -202,10 +201,10 @@ def bar 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]]) + .to eq([['file1.rb', 1]]) end - it "collects references to a new method to a constant from assignment" do + 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(%( @@ -221,10 +220,10 @@ class Foo 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]]) + .to eq([['file1.rb', 1], ['file2.rb', 3]]) end - it "collects references to an instance method symbol" do + it 'collects references to an instance method symbol' do workspace = Solargraph::Workspace.new('*') library = Solargraph::Library.new(workspace) src1 = Solargraph::Source.load_string(%( @@ -248,10 +247,10 @@ def bar; end 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 + 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 + it 'collects references to a class method symbol' do workspace = Solargraph::Workspace.new('*') library = Solargraph::Library.new(workspace) src1 = Solargraph::Source.load_string(%( @@ -281,12 +280,12 @@ def bar; end 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 + 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 + it 'collects stripped references to constant symbols' do workspace = Solargraph::Workspace.new('*') library = Solargraph::Library.new(workspace) src1 = Solargraph::Source.load_string(%( @@ -311,7 +310,7 @@ class Other 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') + expect(code[o1..o2 - 1]).to eq('Foo') end end @@ -334,20 +333,20 @@ def bar 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 + 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 + it 'returns YARD documentation from the core' do library = Solargraph::Library.new - api_map, result = library.document('String') + _, 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 + it 'returns YARD documentation from sources' do library = Solargraph::Library.new src = Solargraph::Source.load_string(%( class Foo @@ -356,12 +355,12 @@ def bar; end end ), 'test.rb', 0) library.attach src - api_map, result = library.document('Foo#bar') + _, 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 + it 'synchronizes sources from updaters' do library = Solargraph::Library.new src = Solargraph::Source.load_string(%( class Foo @@ -382,7 +381,7 @@ def bar; end expect(library.current.code).to eq(repl) end - it "finds unique references" do + it 'finds unique references' do library = Solargraph::Library.new(Solargraph::Workspace.new('*')) src1 = Solargraph::Source.load_string(%( class Foo @@ -398,7 +397,7 @@ class Foo expect(refs.length).to eq(2) end - it "includes method parameters in references" do + it 'includes method parameters in references' do library = Solargraph::Library.new(Solargraph::Workspace.new('*')) source = Solargraph::Source.load_string(%( class Foo @@ -426,7 +425,7 @@ def 🤦🏻foo♀️; 123; end expect(from_def.first.range.start.column).to eq(14) end - it "tells the truth about names when client can handle the truth" do + 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 @@ -438,7 +437,7 @@ def 🤦🏻foo♀️; 123; end expect(from_def.first.range.start.column).to eq(12) end - it "includes block parameters in references" do + it 'includes block parameters in references' do library = Solargraph::Library.new(Solargraph::Workspace.new('*')) source = Solargraph::Source.load_string(%( 100.times do |foo| @@ -630,29 +629,29 @@ def bar; end describe 'Library#completions_at' do it 'gracefully handles unmapped sources' do - expect { + expect do library.completions_at(good_file, 0, 0) - }.not_to raise_error + end.not_to raise_error end it 'raises errors for nonexistent sources' do - expect { + expect do library.completions_at(bad_file, 0, 0) - }.to raise_error(Solargraph::FileNotFoundError) + end.to raise_error(Solargraph::FileNotFoundError) end end describe 'Library#definitions_at' do it 'gracefully handles unmapped sources' do - expect { + expect do library.definitions_at(good_file, 0, 0) - }.not_to raise_error + end.not_to raise_error end it 'raises errors for nonexistent sources' do - expect { + expect do library.definitions_at(bad_file, 0, 0) - }.to raise_error(Solargraph::FileNotFoundError) + end.to raise_error(Solargraph::FileNotFoundError) end end end diff --git a/spec/logging_spec.rb b/spec/logging_spec.rb index 7d38c087e..0c200594b 100644 --- a/spec/logging_spec.rb +++ b/spec/logging_spec.rb @@ -1,10 +1,10 @@ require 'tempfile' describe Solargraph::Logging do - it "logs messages with levels" do + it 'logs messages with levels' do file = Tempfile.new('log') Solargraph::Logging.logger.reopen file - Solargraph::Logging.logger.warn "Test" + Solargraph::Logging.logger.warn 'Test' file.rewind msg = file.read file.close diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 10c9c0849..cee6afef1 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -979,7 +979,6 @@ def foo a expect(clip.infer.to_s).to eq('Integer') end - it 'supports !@x.nil && @x.y' do source = Solargraph::Source.load_string(%( class Bar diff --git a/spec/parser/node_chainer_spec.rb b/spec/parser/node_chainer_spec.rb index 5f3865b13..1c1213564 100644 --- a/spec/parser/node_chainer_spec.rb +++ b/spec/parser/node_chainer_spec.rb @@ -3,31 +3,31 @@ def chain_string str Solargraph::Parser.chain_string(str, 'file.rb', 0) end - it "recognizes self keywords" do + it 'recognizes self keywords' do chain = chain_string('self.foo') expect(chain.links.first.word).to eq('self') expect(chain.links.first).to be_a(Solargraph::Source::Chain::Head) end - it "recognizes super keywords" do + it 'recognizes super keywords' do chain = chain_string('super.foo') expect(chain.links.first.word).to eq('super') expect(chain.links.first).to be_a(Solargraph::Source::Chain::ZSuper) end - it "recognizes constants" do + it 'recognizes constants' do chain = chain_string('Foo::Bar') expect(chain.links.length).to eq(1) expect(chain.links.first).to be_a(Solargraph::Source::Chain::Constant) expect(chain.links.map(&:word)).to eq(['Foo::Bar']) end - it "splits method calls with arguments and blocks" do + it 'splits method calls with arguments and blocks' do chain = chain_string('var.meth1(1, 2).meth2 do; end') - expect(chain.links.map(&:word)).to eq(['var', 'meth1', 'meth2']) + expect(chain.links.map(&:word)).to eq(%w[var meth1 meth2]) end - it "recognizes literals" do + it 'recognizes literals' do chain = chain_string('"string"') expect(chain).to be_literal chain = chain_string('100') @@ -38,22 +38,22 @@ def chain_string str expect(chain).to be_literal end - it "recognizes instance variables" do + it 'recognizes instance variables' do chain = chain_string('@foo') expect(chain.links.first).to be_a(Solargraph::Source::Chain::InstanceVariable) end - it "recognizes class variables" do + it 'recognizes class variables' do chain = chain_string('@@foo') expect(chain.links.first).to be_a(Solargraph::Source::Chain::ClassVariable) end - it "recognizes global variables" do + it 'recognizes global variables' do chain = chain_string('$foo') expect(chain.links.first).to be_a(Solargraph::Source::Chain::GlobalVariable) end - it "operates on nodes" do + it 'operates on nodes' do source = Solargraph::Source.load_string(%( class Foo Bar.meth1(1, 2).meth2{} @@ -61,7 +61,7 @@ class Foo )) node = source.node_at(2, 26) chain = Solargraph::Parser.chain(node) - expect(chain.links.map(&:word)).to eq(['Bar', 'meth1', 'meth2']) + expect(chain.links.map(&:word)).to eq(%w[Bar meth1 meth2]) end it 'chains and/or nodes' do diff --git a/spec/parser/node_methods_spec.rb b/spec/parser/node_methods_spec.rb index 0c4c5f325..63dd2bfa7 100644 --- a/spec/parser/node_methods_spec.rb +++ b/spec/parser/node_methods_spec.rb @@ -5,64 +5,64 @@ def parse source Solargraph::Parser.parse(source, 'test.rb', 0) end - it "unpacks constant nodes into strings" do - ast = parse("Foo::Bar") - expect(Solargraph::Parser::NodeMethods.unpack_name(ast)).to eq "Foo::Bar" + it 'unpacks constant nodes into strings' do + ast = parse('Foo::Bar') + expect(Solargraph::Parser::NodeMethods.unpack_name(ast)).to eq 'Foo::Bar' end - it "infers literal strings" do + it 'infers literal strings' do ast = parse("x = 'string'") expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::String' end - it "infers literal hashes" do - ast = parse("x = {}") + it 'infers literal hashes' do + ast = parse('x = {}') expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Hash' end - it "infers literal arrays" do - ast = parse("x = []") + it 'infers literal arrays' do + ast = parse('x = []') expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Array' end - it "infers literal integers" do - ast = parse("x = 100") + it 'infers literal integers' do + ast = parse('x = 100') expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Integer' end - it "infers literal floats" do - ast = parse("x = 10.1") + it 'infers literal floats' do + ast = parse('x = 10.1') expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Float' end - it "infers literal symbols" do - ast = parse(":symbol") + it 'infers literal symbols' do + ast = parse(':symbol') expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' end - it "infers double quoted symbols" do + it 'infers double quoted symbols' do ast = parse(':"symbol"') expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' end - it "infers interpolated double quoted symbols" do + it 'infers interpolated double quoted symbols' do ast = parse(':"#{Object}"') expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' end - it "infers single quoted symbols" do + it 'infers single quoted symbols' do ast = parse(":'symbol'") expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' end it 'infers literal booleans' do - true_ast = parse("true") + true_ast = parse('true') expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(true_ast)).to eq '::Boolean' - false_ast = parse("false") + false_ast = parse('false') expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(false_ast)).to eq '::Boolean' end - it "handles empty return nodes with implicit nil values" do + it 'handles empty return nodes with implicit nil values' do node = parse(%( return if true )) @@ -74,7 +74,7 @@ def parse source expect(rets.length).to eq(2) end - it "handles local return nodes with implicit nil values" do + it 'handles local return nodes with implicit nil values' do node = parse(%( return bla if true )) @@ -132,7 +132,7 @@ def parse source expect(returns.length).to eq(2) end - it "handles return nodes in reduceable (begin) nodes" do + it 'handles return nodes in reduceable (begin) nodes' do pending('Temporarily disabled. Result is 3 nodes instead of 2.') node = parse(%( @@ -144,7 +144,7 @@ def parse source expect(rets.length).to eq(2) end - it "handles conditional returns with following code" do + it 'handles conditional returns with following code' do node = parse(%( x = 1 return x if foo @@ -176,7 +176,7 @@ def parse source end )) rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:block, :lvar]) + expect(rets.map(&:type)).to eq(%i[block lvar]) end it 'finds correct return node line in begin expressions' do @@ -200,18 +200,18 @@ def parse source nil )) rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:lvar, :nil]) + expect(rets.map(&:type)).to eq(%i[lvar nil]) end - it "handles return nodes with implicit nil values" do + it 'handles return nodes with implicit nil values' do node = parse(%( return bla if true )) rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:send, :nil]) + expect(rets.map(&:type)).to eq(%i[send nil]) end - it "handles return nodes after other nodes" do + it 'handles return nodes after other nodes' do node = parse(%( x = 1 return x @@ -220,7 +220,7 @@ def parse source expect(rets.map(&:type)).to eq([:lvar]) end - it "handles return nodes with unreachable code" do + it 'handles return nodes with unreachable code' do node = parse(%( x = 1 return x @@ -230,7 +230,7 @@ def parse source expect(rets.length).to eq(1) end - it "short-circuits return node finding after a raise statement in a begin expression" do + it 'short-circuits return node finding after a raise statement in a begin expression' do pending('case being handled') node = parse(%( @@ -241,7 +241,7 @@ def parse source expect(rets.length).to eq(0) end - it "does not short circuit return node finding after a raise statement in a conditional" do + it 'does not short circuit return node finding after a raise statement in a conditional' do node = parse(%( x = 1 raise "Error" if foo @@ -251,7 +251,7 @@ def parse source expect(rets.length).to eq(1) end - it "does not short circuit return node finding after a return statement in a conditional" do + it 'does not short circuit return node finding after a return statement in a conditional' do node = parse(%( x = 1 return "Error" if foo @@ -261,7 +261,7 @@ def parse source expect(rets.length).to eq(2) end - it "handles return nodes with reduceable code" do + it 'handles return nodes with reduceable code' do node = parse(%( return begin x if foo @@ -293,7 +293,7 @@ def parse source it "handles nested 'or' nodes from return" do node = parse('return 1 || "2"') rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:int, :str]) + expect(rets.map(&:type)).to eq(%i[int str]) end it 'handles return nodes from case statements' do @@ -305,7 +305,7 @@ def parse source end )) rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:str, :str]) + expect(rets.map(&:type)).to eq(%i[str str]) end it 'handles String return nodes from case statements without else' do @@ -316,7 +316,7 @@ def parse source end )) rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:str, :nil]) + expect(rets.map(&:type)).to eq(%i[str nil]) end it 'handles return nodes from case statements with super' do @@ -329,7 +329,7 @@ def parse source end )) rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:send, :zsuper]) + expect(rets.map(&:type)).to eq(%i[send zsuper]) end describe 'convert_hash' do diff --git a/spec/parser/node_processor_spec.rb b/spec/parser/node_processor_spec.rb index 2033e21ca..6785b9a09 100644 --- a/spec/parser/node_processor_spec.rb +++ b/spec/parser/node_processor_spec.rb @@ -9,9 +9,9 @@ class Foo private_constant end )) - expect { + expect do Solargraph::Parser::NodeProcessor.process(node) - }.not_to raise_error + end.not_to raise_error end it 'orders optional args correctly' do diff --git a/spec/parser_spec.rb b/spec/parser_spec.rb index 3c1e3cca0..1757bb90d 100644 --- a/spec/parser_spec.rb +++ b/spec/parser_spec.rb @@ -3,7 +3,7 @@ def parse source Solargraph::Parser.parse(source, 'file.rb', 0) end - it "parses nodes" do + it 'parses nodes' do node = parse('class Foo; end') expect(Solargraph::Parser.is_ast_node?(node)).to be(true) end diff --git a/spec/pin/base_spec.rb b/spec/pin/base_spec.rb index 1a6cfd1e8..97de966df 100644 --- a/spec/pin/base_spec.rb +++ b/spec/pin/base_spec.rb @@ -2,7 +2,7 @@ let(:zero_location) { Solargraph::Location.new('test.rb', Solargraph::Range.from_to(0, 0, 0, 0)) } let(:one_location) { Solargraph::Location.new('test.rb', Solargraph::Range.from_to(0, 0, 1, 0)) } - it "will not combine pins with directive changes" do + it 'does not combine pins with directive changes' do pin1 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: 'A Foo class', source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) pin2 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@!macro my_macro', @@ -14,7 +14,7 @@ end end - it "will not combine pins with different directives" do + it 'does not combine pins with different directives' do pin1 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@!macro my_macro', source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) pin2 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@!macro other', @@ -25,26 +25,26 @@ end end - it "sees tag differences as not near or equal" do + it 'sees tag differences as not near or equal' do pin1 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@return [Foo]') pin2 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@return [Bar]') expect(pin1.nearly?(pin2)).to be(false) expect(pin1 == pin2).to be(false) end - it "sees comment differences as nearly but not equal" do + it 'sees comment differences as nearly but not equal' do pin1 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: 'A Foo class') pin2 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: 'A different Foo') expect(pin1.nearly?(pin2)).to be(true) expect(pin1 == pin2).to be(false) end - it "recognizes deprecated tags" do + it 'recognizes deprecated tags' do pin = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@deprecated Use Bar instead.') expect(pin).to be_deprecated end - it "does not link documentation for undefined return types" do + it 'does not link documentation for undefined return types' do pin = Solargraph::Pin::Base.new(name: 'Foo', comments: '@return [undefined]') expect(pin.link_documentation).to eq('Foo') end @@ -56,8 +56,8 @@ expect(pins.length).to eq(1) parser_method_pin = pins.first 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(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 diff --git a/spec/pin/base_variable_spec.rb b/spec/pin/base_variable_spec.rb index 2a49ac9f1..f5d60c03a 100644 --- a/spec/pin/base_variable_spec.rb +++ b/spec/pin/base_variable_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Pin::BaseVariable do - it "checks assignments for equality" do + it 'checks assignments for equality' do smap = Solargraph::SourceMap.load_string('foo = "foo"') pin1 = smap.locals.first smap = Solargraph::SourceMap.load_string('foo = "foo"') diff --git a/spec/pin/block_spec.rb b/spec/pin/block_spec.rb index be62e8acd..d6fc051e9 100644 --- a/spec/pin/block_spec.rb +++ b/spec/pin/block_spec.rb @@ -5,6 +5,6 @@ it 'strips prefixes from parameter names' do pin = Solargraph::Pin::Block.new(args: [foo, bar, block]) - expect(pin.parameter_names).to eq(['foo', 'bar', 'block']) + expect(pin.parameter_names).to eq(%w[foo bar block]) end end diff --git a/spec/pin/constant_spec.rb b/spec/pin/constant_spec.rb index 7a7fdb0c9..124c36174 100644 --- a/spec/pin/constant_spec.rb +++ b/spec/pin/constant_spec.rb @@ -1,23 +1,23 @@ describe Solargraph::Pin::Constant do - it "resolves constant paths" do + it 'resolves constant paths' do source = Solargraph::Source.new(%( class Foo BAR = 'bar' end )) map = Solargraph::SourceMap.map(source) - pin = map.pins.select{|pin| pin.name == 'BAR'}.first + pin = map.pins.select { |pin| pin.name == 'BAR' }.first expect(pin.path).to eq('Foo::BAR') end - it "is a constant kind" do + it 'is a constant kind' do source = Solargraph::Source.new(%( class Foo BAR = 'bar' end )) map = Solargraph::SourceMap.map(source) - pin = map.pins.select{|pin| pin.name == 'BAR'}.first + pin = map.pins.select { |pin| pin.name == 'BAR' }.first expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::CONSTANT) expect(pin.symbol_kind).to eq(Solargraph::LanguageServer::SymbolKinds::CONSTANT) end diff --git a/spec/pin/documenting_spec.rb b/spec/pin/documenting_spec.rb index 1fdf9b8de..42da36dd1 100644 --- a/spec/pin/documenting_spec.rb +++ b/spec/pin/documenting_spec.rb @@ -1,11 +1,11 @@ describe Solargraph::Pin::Documenting do - let(:object) { + let(:object) do Class.new do include Solargraph::Pin::Documenting attr_accessor :docstring end.new - } + end it 'parses indented code blocks' do object.docstring = YARD::Docstring.new(%(Method overview diff --git a/spec/pin/instance_variable_spec.rb b/spec/pin/instance_variable_spec.rb index fd6203398..c9fd036d7 100644 --- a/spec/pin/instance_variable_spec.rb +++ b/spec/pin/instance_variable_spec.rb @@ -1,13 +1,13 @@ describe Solargraph::Pin::InstanceVariable do - it "is a kind of variable" do + it 'is a kind of variable' do source = Solargraph::Source.load_string("@foo = 'foo'", 'file.rb') map = Solargraph::SourceMap.map(source) - pin = map.pins.select{ |p| p.is_a?(Solargraph::Pin::InstanceVariable) }.first + pin = map.pins.select { |p| p.is_a?(Solargraph::Pin::InstanceVariable) }.first expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::VARIABLE) expect(pin.symbol_kind).to eq(Solargraph::LanguageServer::SymbolKinds::VARIABLE) end - it "does not link documentation for undefined return types" do + it 'does not link documentation for undefined return types' do pin = Solargraph::Pin::InstanceVariable.new(name: '@bar') expect(pin.link_documentation).to eq('@bar') end diff --git a/spec/pin/keyword_spec.rb b/spec/pin/keyword_spec.rb index 82bb373ef..99e51f287 100644 --- a/spec/pin/keyword_spec.rb +++ b/spec/pin/keyword_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Pin::Keyword do - it "is a kind of keyword" do + it 'is a kind of keyword' do pin = Solargraph::Pin::Keyword.new('foo') expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end diff --git a/spec/pin/local_variable_spec.rb b/spec/pin/local_variable_spec.rb index 478056f41..28481bf4d 100644 --- a/spec/pin/local_variable_spec.rb +++ b/spec/pin/local_variable_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Pin::LocalVariable do - it "merges presence changes so that [not currently used]" do + it 'merges presence changes so that [not currently used]' do pending 'but not sure why' map1 = Solargraph::SourceMap.load_string(%( @@ -28,7 +28,6 @@ class Foo expect { pin1.combine_with(pin2) }.to raise_error(RuntimeError, /Inconsistent :closure name/) end - expect(combined.source).to eq(:combined) # no choice behavior defined yet - if/when this is to be used, we # should indicate which one should override in the range situation @@ -42,7 +41,7 @@ class Foo ), 'test.rb') api_map = Solargraph::ApiMap.new api_map.map source - clip = api_map.clip_at('test.rb', [2, 0]) + api_map.clip_at('test.rb', [2, 0]) object_pin = api_map.source_map('test.rb').locals.find { |p| p.name == 'object' } expect(object_pin).not_to be_nil location = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(2, 0, 2, 0)) @@ -87,14 +86,14 @@ class Foo; end expect(x_pin.visible_at?(block_pin, location)).to be true end - it "understands local lookup in root scope" do + it 'understands local lookup in root scope' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( # @type [Array] arr = [] - ), "test.rb") + ), 'test.rb') api_map.map source arr_pin = api_map.source_map('test.rb').locals.find { |p| p.name == 'arr' } expect(arr_pin).not_to be_nil @@ -144,7 +143,7 @@ def bar end it 'is visible within each block scope inside function' do - source = Solargraph::Source.load_string(%( + source = Solargraph::Source.load_string(%( class Foo def bar x = 1 @@ -154,17 +153,17 @@ def bar end end ), 'test.rb') - api_map = Solargraph::ApiMap.new - api_map.map source - x = api_map.source_map('test.rb').locals.find { |p| p.name == 'x' } - bar_method = api_map.get_path_pins('Foo#bar').first - each_block_pin = api_map.get_block_pins.find do |b| - b.location.range.start.line == 4 - end - expect(each_block_pin).not_to be_nil - range = Solargraph::Range.from_to(5, 24, 5, 25) - location = Solargraph::Location.new('test.rb', range) - expect(x.visible_at?(each_block_pin, location)).to be true + api_map = Solargraph::ApiMap.new + api_map.map source + x = api_map.source_map('test.rb').locals.find { |p| p.name == 'x' } + api_map.get_path_pins('Foo#bar').first + each_block_pin = api_map.get_block_pins.find do |b| + b.location.range.start.line == 4 + end + expect(each_block_pin).not_to be_nil + range = Solargraph::Range.from_to(5, 24, 5, 25) + location = Solargraph::Location.new('test.rb', range) + expect(x.visible_at?(each_block_pin, location)).to be true end it 'sees block parameter inside block' do diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index b255a53fa..1469d1c3d 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -116,14 +116,14 @@ def bazzle; end expect(pin.return_type).to be_undefined end - it 'will not merge with changes in parameters' do + it 'does not merge with changes in parameters' do # @todo Method pin parameters are pins now pin1 = Solargraph::Pin::Method.new(name: 'bar', parameters: %w[one two]) pin2 = Solargraph::Pin::Method.new(name: 'bar', parameters: ['three']) expect(pin1.nearly?(pin2)).to be(false) end - it 'will not merge with changes in YARD return types' do + it 'does not merge with changes in YARD return types' do pin1 = Solargraph::Pin::Method.new(name: 'foo', comments: '@return [String]') pin2 = Solargraph::Pin::Method.new(name: 'foo', comments: '@return [Integer]') expect(pin1.nearly?(pin2)).to be(false) @@ -479,8 +479,8 @@ def bar param1, param2 api_map.map source pin = api_map.get_path_pins('Example#bar').first pin.resolve_ref_tag(api_map) - expect(pin.docstring.tags(:param).map(&:name)).to eq(['param1', 'param2']) - expect(pin.docstring.tags(:param).map(&:type)).to eq(['String', 'Integer']) + expect(pin.docstring.tags(:param).map(&:name)).to eq(%w[param1 param2]) + expect(pin.docstring.tags(:param).map(&:type)).to eq(%w[String Integer]) end it 'resolves ref tags with namespaces' do @@ -502,8 +502,8 @@ def bar param1, param2 api_map.map source pin = api_map.get_path_pins('Example2#bar').first pin.resolve_ref_tag(api_map) - expect(pin.docstring.tags(:param).map(&:name)).to eq(['param1', 'param2']) - expect(pin.docstring.tags(:param).map(&:type)).to eq(['String', 'Integer']) + expect(pin.docstring.tags(:param).map(&:name)).to eq(%w[param1 param2]) + expect(pin.docstring.tags(:param).map(&:type)).to eq(%w[String Integer]) end context 'when attr_reader is used' do diff --git a/spec/pin/namespace_spec.rb b/spec/pin/namespace_spec.rb index 3c94a526e..0421def05 100644 --- a/spec/pin/namespace_spec.rb +++ b/spec/pin/namespace_spec.rb @@ -1,11 +1,11 @@ describe Solargraph::Pin::Namespace do - it "handles long namespaces" do + it 'handles long namespaces' do pin = Solargraph::Pin::Namespace.new(closure: Solargraph::Pin::Namespace.new(name: 'Foo'), name: 'Bar') expect(pin.path).to eq('Foo::Bar') end - it "has class scope" do - source = Solargraph::Source.load_string(%( + it 'has class scope' do + Solargraph::Source.load_string(%( class Foo end )) @@ -13,7 +13,7 @@ class Foo expect(pin.context.scope).to eq(:class) end - it "is a kind of namespace/class/module" do + it 'is a kind of namespace/class/module' do pin1 = Solargraph::Pin::Namespace.new(name: 'Foo') expect(pin1.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::CLASS) pin2 = Solargraph::Pin::Namespace.new(name: 'Foo', type: :module) diff --git a/spec/pin/symbol_spec.rb b/spec/pin/symbol_spec.rb index f83a528cf..ce841a399 100644 --- a/spec/pin/symbol_spec.rb +++ b/spec/pin/symbol_spec.rb @@ -1,53 +1,52 @@ describe Solargraph::Pin::Symbol do - context "when an unquoted literal" do - it "is a kind of keyword to the LSP" do + context 'when an unquoted literal' 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 + 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 + it 'has a Symbol return type' do pin = Solargraph::Pin::Symbol.new(nil, ':symbol') expect(pin.return_type.tag).to eq('Symbol') end end - context "when a double quoted literal" do - it "is a kind of keyword" do + context 'when a double quoted literal' do + it 'is a kind of keyword' do pin = Solargraph::Pin::Symbol.new(nil, ':"symbol"') expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end - it "has a Symbol return type" do + it 'has a Symbol return type' do pin = Solargraph::Pin::Symbol.new(nil, ':"symbol"') expect(pin.return_type.tag).to eq('Symbol') end end - context "when a double quoted interpolated literal" do - it "is a kind of keyword" do + context 'when a double quoted interpolated literal' do + it 'is a kind of keyword' do pin = Solargraph::Pin::Symbol.new(nil, ':"symbol #{variable}"') expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end - it "has a Symbol return type" do + it 'has a Symbol return type' do pin = Solargraph::Pin::Symbol.new(nil, ':"symbol #{variable}"') expect(pin.return_type.tag).to eq('Symbol') end end - - context "when a single quoted literal" do - it "is a kind of keyword" do + context 'when a single quoted literal' do + it 'is a kind of keyword' do pin = Solargraph::Pin::Symbol.new(nil, ":'symbol'") expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end - it "has a Symbol return type" do + it 'has a Symbol return type' do pin = Solargraph::Pin::Symbol.new(nil, ":'symbol'") expect(pin.return_type.tag).to eq('Symbol') end diff --git a/spec/pin_cache_spec.rb b/spec/pin_cache_spec.rb index 0f766117a..83cf7a3b7 100644 --- a/spec/pin_cache_spec.rb +++ b/spec/pin_cache_spec.rb @@ -150,7 +150,8 @@ 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/.*/rubocop-yard-.*-export.ser$}, any_args, mode: 'wb').once + expect(File).to have_received(:write).with(%r{combined/.*/rubocop-yard-.*-export.ser$}, any_args, + mode: 'wb').once end end end diff --git a/spec/position_spec.rb b/spec/position_spec.rb index e8dab1960..5612e8a5e 100644 --- a/spec/position_spec.rb +++ b/spec/position_spec.rb @@ -1,12 +1,12 @@ describe Solargraph::Position do - it "normalizes arrays into positions" do + it 'normalizes arrays into positions' do pos = Solargraph::Position.normalize([0, 1]) expect(pos).to be_a(Solargraph::Position) expect(pos.line).to eq(0) expect(pos.column).to eq(1) end - it "returns original positions when normalizing" do + it 'returns original positions when normalizing' do orig = Solargraph::Position.new(0, 1) norm = Solargraph::Position.normalize(orig) expect(orig).to be(norm) @@ -29,10 +29,10 @@ expect(Solargraph::Position.from_offset(text, 44)).to eq(Solargraph::Position.new(2, 27)) end - it "raises an error for objects that cannot be normalized" do - expect { + it 'raises an error for objects that cannot be normalized' do + expect do Solargraph::Position.normalize('0, 1') - }.to raise_error(ArgumentError) + end.to raise_error(ArgumentError) end it 'avoids fencepost errors' do diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 61771ac10..ca01d1ae1 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -3,7 +3,7 @@ # 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 diff --git a/spec/rbs_map/core_map_spec.rb b/spec/rbs_map/core_map_spec.rb index e2d9b41d5..56e13a8d3 100644 --- a/spec/rbs_map/core_map_spec.rb +++ b/spec/rbs_map/core_map_spec.rb @@ -16,12 +16,12 @@ store = Solargraph::ApiMap::Store.new(map.pins) # The core RBS contains: # class Mutex = Thread::Mutex - thread_mutex_pin = store.get_path_pins("Thread::Mutex").first + thread_mutex_pin = store.get_path_pins('Thread::Mutex').first expect(thread_mutex_pin).to be_a(Solargraph::Pin::Namespace) - mutex_pin = store.get_path_pins("Mutex").first + mutex_pin = store.get_path_pins('Mutex').first expect(mutex_pin).to be_a(Solargraph::Pin::Constant) - expect(mutex_pin.return_type.to_s).to eq("Class") + expect(mutex_pin.return_type.to_s).to eq('Class') end it 'understands RBS global variables' do @@ -39,10 +39,10 @@ it 'understands implied Enumerator#each method' do api_map = Solargraph::ApiMap.new methods = api_map.get_methods('Enumerable') - each_pins = methods.select{|pin| pin.path.end_with?('#each')} + each_pins = methods.select { |pin| pin.path.end_with?('#each') } # expect this to come from the _Each implied interface ("self # type") defined in the RBS - expect(each_pins.map(&:path)).to eq(["_Each#each"]) + expect(each_pins.map(&:path)).to eq(['_Each#each']) expect(each_pins.map(&:class)).to eq([Solargraph::Pin::Method]) each_pin = each_pins.first expect(each_pin.signatures.length).to eq(1) @@ -53,7 +53,7 @@ it 'populates types in block parameters from generics' do api_map = Solargraph::ApiMap.new methods = api_map.get_methods('Enumerable') - each_pins = methods.select{|pin| pin.path.end_with?('#each')} + each_pins = methods.select { |pin| pin.path.end_with?('#each') } each_pin = each_pins.first signature = each_pin.signatures.first expect(signature.block.parameters.map(&:return_type).map(&:to_s)).to eq(['String']) @@ -69,7 +69,7 @@ # api_map = Solargraph::ApiMap.new methods = api_map.get_methods('Enumerable') - each_pins = methods.select{|pin| pin.path.end_with?('#each')} + each_pins = methods.select { |pin| pin.path.end_with?('#each') } each_pin = each_pins.first signature = each_pin.signatures.first expect(signature.return_type.to_s).to eq('Enumerable') diff --git a/spec/rbs_map/stdlib_map_spec.rb b/spec/rbs_map/stdlib_map_spec.rb index 77d99a426..bb1e33177 100644 --- a/spec/rbs_map/stdlib_map_spec.rb +++ b/spec/rbs_map/stdlib_map_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::RbsMap::StdlibMap do - it "finds stdlib require paths" do + it 'finds stdlib require paths' do rbs_map = Solargraph::RbsMap::StdlibMap.load('fileutils') pin = rbs_map.path_pin('FileUtils#chdir') expect(pin).not_to be_nil diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index aca90eb7e..e37b3d9b6 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -10,10 +10,10 @@ 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 "source 'https://rubygems.org'" + file.puts "gem 'solargraph', path: '#{File.expand_path('..', __dir__)}'" end - output, status = Open3.capture2e("bundle install", chdir: temp_dir) + output, status = Open3.capture2e('bundle install', chdir: temp_dir) raise "Failure installing bundle: #{output}" unless status.success? end diff --git a/spec/source/chain/array_spec.rb b/spec/source/chain/array_spec.rb index b8ef9db23..34eeafe01 100644 --- a/spec/source/chain/array_spec.rb +++ b/spec/source/chain/array_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Source::Chain::Array do - it "resolves an instance of an array" do + it 'resolves an instance of an array' do literal = described_class.new([], nil) pin = literal.resolve(nil, nil, nil).first expect(pin.return_type.tag).to eq('Array') diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index 67aa1398c..2cdca3de5 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Source::Chain::Call do - it "recognizes core methods that return subtypes" do + it 'recognizes core methods that return subtypes' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( # @type [Array] @@ -12,7 +12,7 @@ expect(type.tag).to eq('String') end - it "recognizes core methods that return self" do + it 'recognizes core methods that return self' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( arr = [] @@ -24,7 +24,7 @@ expect(type.tag).to eq('Array') end - it "handles super calls to same method" do + it 'handles super calls to same method' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( class Foo @@ -44,7 +44,7 @@ def my_method expect(type.tag).to eq('Integer') end - it "infers return types based on yield call and @yieldreturn" do + it 'infers return types based on yield call and @yieldreturn' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( class Foo @@ -61,7 +61,7 @@ def my_method(&block) expect(type.tag).to eq('Integer') end - it "infers return types based only on yield call and @yieldreturn" do + it 'infers return types based only on yield call and @yieldreturn' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( class Foo @@ -78,7 +78,7 @@ def my_method(&block) expect(type.tag).to eq('Integer') end - it "adds virtual constructors for .new calls with conflicting return types" do + it 'adds virtual constructors for .new calls with conflicting return types' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( class Foo @@ -93,7 +93,7 @@ def self.new; end expect(type.tag).to eq('String') end - it "infers types from macros" do + it 'infers types from macros' do source = Solargraph::Source.load_string(%( class Foo # @!macro @@ -575,7 +575,7 @@ def d expect(type.rooted_tags).not_to eq('::A::C') end - it 'qualifies types in a second Array#+ ' do + it 'qualifies types in a second Array#+' do source = Solargraph::Source.load_string(%( module A1 class B1 diff --git a/spec/source/chain/class_variable_spec.rb b/spec/source/chain/class_variable_spec.rb index 05e7876f5..2b01007e7 100644 --- a/spec/source/chain/class_variable_spec.rb +++ b/spec/source/chain/class_variable_spec.rb @@ -1,8 +1,8 @@ describe Solargraph::Source::Chain::ClassVariable do - it "resolves class variable pins" do + it 'resolves class variable pins' do foo_pin = Solargraph::Pin::ClassVariable.new(name: '@@foo') bar_pin = Solargraph::Pin::ClassVariable.new(name: '@@bar') - api_map = instance_double(Solargraph::ApiMap, :get_class_variable_pins => [foo_pin, bar_pin]) + api_map = instance_double(Solargraph::ApiMap, get_class_variable_pins: [foo_pin, bar_pin]) link = Solargraph::Source::Chain::ClassVariable.new('@@bar') pins = link.resolve(api_map, Solargraph::Pin::ROOT_PIN, []) expect(pins.length).to eq(1) diff --git a/spec/source/chain/constant_spec.rb b/spec/source/chain/constant_spec.rb index 4376650b3..f809783ef 100644 --- a/spec/source/chain/constant_spec.rb +++ b/spec/source/chain/constant_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Source::Chain::Constant do - it "resolves constants in the current context" do + it 'resolves constants in the current context' do foo_pin = Solargraph::Pin::Constant.new(name: 'Foo', closure: Solargraph::Pin::ROOT_PIN) api_map = Solargraph::ApiMap.new api_map.index [foo_pin] diff --git a/spec/source/chain/global_variable_spec.rb b/spec/source/chain/global_variable_spec.rb index 4c5bd6bce..4d0ed4056 100644 --- a/spec/source/chain/global_variable_spec.rb +++ b/spec/source/chain/global_variable_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Source::Chain::GlobalVariable do - it "resolves instance variable pins" do + it 'resolves instance variable pins' do closure = Solargraph::Pin::Namespace.new(name: 'Foo') foo_pin = Solargraph::Pin::GlobalVariable.new(closure: closure, name: '$foo') not_pin = Solargraph::Pin::InstanceVariable.new(closure: closure, name: '@bar') diff --git a/spec/source/chain/head_spec.rb b/spec/source/chain/head_spec.rb index cc63a54a3..7d4e5d9ea 100644 --- a/spec/source/chain/head_spec.rb +++ b/spec/source/chain/head_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Source::Chain::Head do - it "returns self pins" do + it 'returns self pins' do head = Solargraph::Source::Chain::Head.new('self') npin = Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.parse('Foo')) ipin = head.resolve(nil, npin, []).first diff --git a/spec/source/chain/instance_variable_spec.rb b/spec/source/chain/instance_variable_spec.rb index ee4604f91..fd956f770 100644 --- a/spec/source/chain/instance_variable_spec.rb +++ b/spec/source/chain/instance_variable_spec.rb @@ -1,7 +1,8 @@ describe Solargraph::Source::Chain::InstanceVariable do - it "resolves instance variable pins" do + it 'resolves instance variable pins' do closure = Solargraph::Pin::Namespace.new(name: 'Foo', - location: Solargraph::Location.new('test.rb', Solargraph::Range.from_to(1, 1, 9, 0)), + location: Solargraph::Location.new('test.rb', + Solargraph::Range.from_to(1, 1, 9, 0)), source: :closure) methpin = Solargraph::Pin::Method.new(closure: closure, name: 'imeth', scope: :instance, location: Solargraph::Location.new('test.rb', Solargraph::Range.from_to(1, 1, 3, 0)), @@ -17,7 +18,8 @@ api_map = Solargraph::ApiMap.new api_map.index [closure, methpin, foo_pin, bar_pin] - link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil, Solargraph::Location.new('test.rb', Solargraph::Range.from_to(2, 2, 2, 3))) + link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil, + Solargraph::Location.new('test.rb', Solargraph::Range.from_to(2, 2, 2, 3))) pins = link.resolve(api_map, methpin, []) expect(pins.length).to eq(1) @@ -27,7 +29,8 @@ name_pin = Solargraph::Pin::ProxyType.anonymous(closure.binder, # Closure is the class closure: closure) - link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil, Solargraph::Location.new('test.rb', Solargraph::Range.from_to(5, 1, 5, 2))) + link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil, + Solargraph::Location.new('test.rb', Solargraph::Range.from_to(5, 1, 5, 2))) pins = link.resolve(api_map, name_pin, []) expect(pins.length).to eq(1) expect(pins.first.name).to eq('@foo') diff --git a/spec/source/chain/link_spec.rb b/spec/source/chain/link_spec.rb index 39143dbbd..c492d0ba3 100644 --- a/spec/source/chain/link_spec.rb +++ b/spec/source/chain/link_spec.rb @@ -1,26 +1,26 @@ describe Solargraph::Source::Chain::Link do - it "is undefined by default" do + it 'is undefined by default' do link = described_class.new expect(link).to be_undefined end - it "is not a constant by default" do + it 'is not a constant by default' do link = described_class.new expect(link).not_to be_constant end - it "resolves empty arrays by default" do + it 'resolves empty arrays by default' do link = described_class.new expect(link.resolve(nil, nil, nil)).to be_empty end - it "recognizes equivalent links" do + it 'recognizes equivalent links' do l1 = described_class.new('foo') l2 = described_class.new('foo') expect(l1).to eq(l2) end - it "recognizes inequivalent links" do + it 'recognizes inequivalent links' do l1 = described_class.new('foo') l2 = described_class.new('bar') expect(l1).not_to eq(l2) diff --git a/spec/source/chain/literal_spec.rb b/spec/source/chain/literal_spec.rb index a1431ae07..d3f759ea0 100644 --- a/spec/source/chain/literal_spec.rb +++ b/spec/source/chain/literal_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Source::Chain::Literal do - it "resolves an instance of a literal" do + it 'resolves an instance of a literal' do literal = described_class.new('String', nil) api_map = Solargraph::ApiMap.new pin = literal.resolve(api_map, nil, nil).first diff --git a/spec/source/chain/z_super_spec.rb b/spec/source/chain/z_super_spec.rb index aa412dda6..4739ee195 100644 --- a/spec/source/chain/z_super_spec.rb +++ b/spec/source/chain/z_super_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Source::Chain::ZSuper do - it "resolves super" do + it 'resolves super' do head = Solargraph::Source::Chain::ZSuper.new('super') npin = Solargraph::Pin::Namespace.new(name: 'Substring') scpin = Solargraph::Pin::Reference::Superclass.new(closure: npin, name: 'String') diff --git a/spec/source/chain_spec.rb b/spec/source/chain_spec.rb index 6db1686d4..175e07946 100644 --- a/spec/source/chain_spec.rb +++ b/spec/source/chain_spec.rb @@ -1,25 +1,25 @@ describe Solargraph::Source::Chain do - it "gets empty definitions for undefined links" do + it 'gets empty definitions for undefined links' do chain = described_class.new([Solargraph::Source::Chain::Link.new]) expect(chain.define(nil, nil, nil)).to be_empty end - it "infers undefined types for undefined links" do + it 'infers undefined types for undefined links' do chain = described_class.new([Solargraph::Source::Chain::Link.new]) expect(chain.infer(nil, nil, nil)).to be_undefined end - it "calls itself undefined if any of its links are undefined" do + it 'calls itself undefined if any of its links are undefined' do chain = described_class.new([Solargraph::Source::Chain::Link.new]) expect(chain).to be_undefined end - it "returns undefined bases for single links" do + it 'returns undefined bases for single links' do chain = described_class.new([Solargraph::Source::Chain::Link.new]) expect(chain.base).to be_undefined end - it "defines constants from core classes" do + it 'defines constants from core classes' do api_map = Solargraph::ApiMap.new chain = described_class.new([Solargraph::Source::Chain::Constant.new('String')]) pins = chain.define(api_map, Solargraph::Pin::ROOT_PIN, []) @@ -27,7 +27,7 @@ expect(pins.first.path).to eq('String') end - it "infers types from core classes" do + it 'infers types from core classes' do api_map = Solargraph::ApiMap.new chain = described_class.new([Solargraph::Source::Chain::Constant.new('String')]) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, []) @@ -35,25 +35,26 @@ expect(type.scope).to eq(:class) end - it "infers types from core methods" do + it 'infers types from core methods' do api_map = Solargraph::ApiMap.new - chain = described_class.new([Solargraph::Source::Chain::Constant.new('String'), Solargraph::Source::Chain::Call.new('new', nil)]) + chain = described_class.new([Solargraph::Source::Chain::Constant.new('String'), + Solargraph::Source::Chain::Call.new('new', nil)]) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, []) expect(type.namespace).to eq('String') expect(type.scope).to eq(:instance) end - it "recognizes literals" do + it 'recognizes literals' do chain = described_class.new([Solargraph::Source::Chain::Literal.new('String', nil)]) expect(chain.literal?).to be(true) end - it "recognizes constants" do + it 'recognizes constants' do chain = described_class.new([Solargraph::Source::Chain::Constant.new('String')]) expect(chain.constant?).to be(true) end - it "recognizes unfinished constants" do + it 'recognizes unfinished constants' do chain = described_class.new([Solargraph::Source::Chain::Constant.new('String'), Solargraph::Source::Chain::Constant.new('')]) expect(chain.constant?).to be(true) expect(chain.base.constant?).to be(true) @@ -61,7 +62,7 @@ expect(chain.base.undefined?).to be(false) end - it "infers types from new subclass calls without a subclass initialize method" do + it 'infers types from new subclass calls without a subclass initialize method' do code = %( class Sup def initialize; end @@ -80,7 +81,7 @@ def meth; end expect(type.name).to eq('Sub') end - it "follows constant chains" do + it 'follows constant chains' do source = Solargraph::Source.load_string(%( module Mixin; end module Container @@ -95,7 +96,7 @@ class Foo; end expect(pins).to be_empty end - it "rebases inner constants chains" do + it 'rebases inner constants chains' do source = Solargraph::Source.load_string(%( class Foo class Bar; end @@ -105,11 +106,12 @@ class Bar; end api_map = Solargraph::ApiMap.new api_map.map source chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(3, 16)) - pins = chain.define(api_map, Solargraph::Pin::ProxyType.new(closure: Solargraph::Pin::Namespace.new(name: 'Foo'), return_type: Solargraph::ComplexType.parse('Class')), []) + pins = chain.define(api_map, + Solargraph::Pin::ProxyType.new(closure: Solargraph::Pin::Namespace.new(name: 'Foo'), return_type: Solargraph::ComplexType.parse('Class')), []) expect(pins.first.path).to eq('Foo::Bar') end - it "resolves relative constant paths" do + it 'resolves relative constant paths' do source = Solargraph::Source.load_string(%( class Foo class Bar @@ -123,11 +125,12 @@ module Other api_map = Solargraph::ApiMap.new api_map.map source chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(6, 16)) - pins = chain.define(api_map, Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.parse('Class')), []) + pins = chain.define(api_map, + Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.parse('Class')), []) expect(pins.first.path).to eq('Foo::Bar::Baz') end - it "avoids recursive variable assignments" do + it 'avoids recursive variable assignments' do source = Solargraph::Source.load_string(%( @foo = @bar @bar = @foo.quz @@ -135,12 +138,12 @@ module Other api_map = Solargraph::ApiMap.new api_map.map source chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(2, 18)) - expect { + expect do chain.define(api_map, Solargraph::Pin::ROOT_PIN, []) - }.not_to raise_error + end.not_to raise_error end - it "pulls types from multiple lines of code" do + it 'pulls types from multiple lines of code' do source = Solargraph::Source.load_string(%( 123 'abc' @@ -152,7 +155,7 @@ module Other expect(type.simple_tags).to eq('String') end - it "uses last line of a begin expression as return type" do + it 'uses last line of a begin expression as return type' do source = Solargraph::Source.load_string(%( begin 123 @@ -166,7 +169,7 @@ module Other expect(type.simple_tags).to eq('String') end - it "matches constants on complete symbols" do + it 'matches constants on complete symbols' do source = Solargraph::Source.load_string(%( class Correct; end class NotCorrect; end diff --git a/spec/source/change_spec.rb b/spec/source/change_spec.rb index 247c1e204..7ef02984a 100644 --- a/spec/source/change_spec.rb +++ b/spec/source/change_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Source::Change do - it "inserts a character" do + it 'inserts a character' do text = 'var' range = Solargraph::Range.from_to(0, 3, 0, 3) new_text = '.' @@ -8,7 +8,7 @@ expect(updated).to eq('var.') end - it "repairs nullable characters" do + it 'repairs nullable characters' do text = 'var' range = Solargraph::Range.from_to(0, 3, 0, 3) new_text = '.' @@ -17,7 +17,7 @@ expect(updated).to eq('var ') end - it "repairs entire changes" do + it 'repairs entire changes' do text = 'var' range = Solargraph::Range.from_to(0, 3, 0, 3) new_text = '._(!' @@ -26,14 +26,14 @@ expect(updated).to eq('var ') end - it "repairs nil ranges" do + it 'repairs nil ranges' do text = 'original' change = Solargraph::Source::Change.new(nil, '...') updated = change.repair(text) expect(updated).to eq(' ') end - it "overwrites nil ranges" do + it 'overwrites nil ranges' do text = 'foo' new_text = 'bar' change = Solargraph::Source::Change.new(nil, new_text) @@ -41,7 +41,7 @@ expect(updated).to eq('bar') end - it "blanks single colons in nullable changes" do + it 'blanks single colons in nullable changes' do text = 'bar' new_text = ':' range = Solargraph::Range.from_to(0, 3, 0, 3) @@ -50,7 +50,7 @@ expect(updated).to eq('bar ') end - it "blanks double colons in nullable changes" do + it 'blanks double colons in nullable changes' do text = 'bar:' new_text = ':' range = Solargraph::Range.from_to(0, 4, 0, 4) @@ -59,7 +59,7 @@ expect(updated).to eq('bar ') end - it "repairs preceding periods" do + it 'repairs preceding periods' do text = 'bar.' new_text = ' ' range = Solargraph::Range.from_to(0, 4, 0, 4) @@ -68,7 +68,7 @@ expect(updated).to eq('bar ') end - it "repairs preceding colons" do + it 'repairs preceding colons' do text = 'bar:' new_text = 'x' range = Solargraph::Range.from_to(0, 4, 0, 4) diff --git a/spec/source/cursor_spec.rb b/spec/source/cursor_spec.rb index b9cae07b3..b9539c57a 100644 --- a/spec/source/cursor_spec.rb +++ b/spec/source/cursor_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Source::Cursor do - it "detects cursors in strings" do + it 'detects cursors in strings' do source = Solargraph::Source.load_string('str = "string"') cursor = described_class.new(source, Solargraph::Position.new(0, 6)) expect(cursor).not_to be_string @@ -7,7 +7,7 @@ expect(cursor).to be_string end - it "detects cursors in comments" do + it 'detects cursors in comments' do source = Solargraph::Source.load_string(%( # @type [String] var = make_a_string @@ -20,48 +20,48 @@ expect(cursor).not_to be_comment end - it "detects arguments inside parentheses" do + it 'detects arguments inside parentheses' do source = Solargraph::Source.load_string('a(1); b') - cur = described_class.new(source, Solargraph::Position.new(0,2)) + cur = described_class.new(source, Solargraph::Position.new(0, 2)) expect(cur).to be_argument - cur = described_class.new(source, Solargraph::Position.new(0,3)) + cur = described_class.new(source, Solargraph::Position.new(0, 3)) expect(cur).to be_argument - cur = described_class.new(source, Solargraph::Position.new(0,4)) + cur = described_class.new(source, Solargraph::Position.new(0, 4)) expect(cur).not_to be_argument - cur = described_class.new(source, Solargraph::Position.new(0,5)) + cur = described_class.new(source, Solargraph::Position.new(0, 5)) expect(cur).not_to be_argument - cur = described_class.new(source, Solargraph::Position.new(0,7)) + cur = described_class.new(source, Solargraph::Position.new(0, 7)) expect(cur).not_to be_argument end it 'detects arguments at opening parentheses' do source = Solargraph::Source.load_string('String.new', 'test.rb') - change = Solargraph::Source::Change.new(Solargraph::Range.from_to(0, 10, 0, 10) ,'(') + change = Solargraph::Source::Change.new(Solargraph::Range.from_to(0, 10, 0, 10), '(') updater = Solargraph::Source::Updater.new('test.rb', 1, [change]) source = source.synchronize(updater) cursor = source.cursor_at([0, 11]) expect(cursor).to be_argument end - it "detects class variables" do - source = instance_double(Solargraph::Source, :code => '@@foo') + it 'detects class variables' do + source = instance_double(Solargraph::Source, code: '@@foo') cur = described_class.new(source, Solargraph::Position.new(0, 2)) expect(cur.word).to eq('@@foo') end - it "detects instance variables" do - source = instance_double(Solargraph::Source, :code => '@foo') + it 'detects instance variables' do + source = instance_double(Solargraph::Source, code: '@foo') cur = described_class.new(source, Solargraph::Position.new(0, 1)) expect(cur.word).to eq('@foo') end - it "detects global variables" do - source = instance_double(Solargraph::Source, :code => '$foo') + it 'detects global variables' do + source = instance_double(Solargraph::Source, code: '$foo') cur = described_class.new(source, Solargraph::Position.new(0, 1)) expect(cur.word).to eq('$foo') end - it "generates word ranges" do + it 'generates word ranges' do source = Solargraph::Source.load_string(%( foo = bar )) @@ -69,26 +69,26 @@ expect(source.at(cur.range)).to eq('bar') end - it "generates chains" do + it 'generates chains' do source = Solargraph::Source.load_string('foo.bar(1,2).baz{}') cur = described_class.new(source, Solargraph::Position.new(0, 18)) expect(cur.chain).to be_a(Solargraph::Source::Chain) - expect(cur.chain.links.map(&:word)).to eq(['foo', 'bar', 'baz']) + expect(cur.chain.links.map(&:word)).to eq(%w[foo bar baz]) end - it "detects constant words" do - source = instance_double(Solargraph::Source, :code => 'Foo::Bar') + it 'detects constant words' do + source = instance_double(Solargraph::Source, code: 'Foo::Bar') cur = described_class.new(source, Solargraph::Position.new(0, 5)) expect(cur.word).to eq('Bar') end - it "detects cursors in dynamic strings" do + it 'detects cursors in dynamic strings' do source = Solargraph::Source.load_string('"#{100}"') cursor = source.cursor_at(Solargraph::Position.new(0, 7)) expect(cursor).to be_string end - it "detects cursors in embedded strings" do + it 'detects cursors in embedded strings' do source = Solargraph::Source.load_string('"#{100}..."') cursor = source.cursor_at(Solargraph::Position.new(0, 10)) expect(cursor).to be_string @@ -118,8 +118,8 @@ class Foo; end "#{[]}" ', 'test.rb') updater = Solargraph::Source::Updater.new('test.rb', 1, [ - Solargraph::Source::Change.new(Solargraph::Range.from_to(1, 12, 1, 12), '.') - ]) + Solargraph::Source::Change.new(Solargraph::Range.from_to(1, 12, 1, 12), '.') + ]) updated = source.synchronize(updater) cursor = updated.cursor_at(Solargraph::Position.new(1, 13)) expect(cursor).to be_string diff --git a/spec/source/source_chainer_spec.rb b/spec/source/source_chainer_spec.rb index 8378dfd43..61359963f 100644 --- a/spec/source/source_chainer_spec.rb +++ b/spec/source/source_chainer_spec.rb @@ -1,12 +1,12 @@ describe Solargraph::Source::SourceChainer do - it "handles trailing colons that are not namespace separators" do + it 'handles trailing colons that are not namespace separators' do source = Solargraph::Source.load_string('Foo:') map = Solargraph::SourceMap.map(source) cursor = map.cursor_at(Solargraph::Position.new(0, 4)) expect(cursor.chain.links.first).to be_undefined end - it "recognizes literal strings" do + it 'recognizes literal strings' do map = Solargraph::SourceMap.load_string("'string'") cursor = map.cursor_at(Solargraph::Position.new(0, 0)) expect(cursor.chain).not_to be_a(Solargraph::Source::Chain::Literal) @@ -15,8 +15,8 @@ expect(cursor.chain.links.first.word).to eq('<::String>') end - it "recognizes literal integers" do - map = Solargraph::SourceMap.load_string("100") + it 'recognizes literal integers' do + map = Solargraph::SourceMap.load_string('100') cursor = map.cursor_at(Solargraph::Position.new(0, 0)) expect(cursor.chain).not_to be_a(Solargraph::Source::Chain::Literal) cursor = map.cursor_at(Solargraph::Position.new(0, 1)) @@ -24,42 +24,42 @@ expect(cursor.chain.links.first.word).to eq('<::Integer>') end - it "recognizes literal regexps" do - map = Solargraph::SourceMap.load_string("/[a-z]/") + it 'recognizes literal regexps' do + map = Solargraph::SourceMap.load_string('/[a-z]/') cursor = map.cursor_at(Solargraph::Position.new(0, 0)) expect(cursor.chain.links.first).to be_a(Solargraph::Source::Chain::Literal) expect(cursor.chain.links.first.word).to eq('<::Regexp>') end - it "recognizes class variables" do + it 'recognizes class variables' do map = Solargraph::SourceMap.load_string('@@foo') cursor = map.cursor_at(Solargraph::Position.new(0, 0)) expect(cursor.chain.links.first).to be_a(Solargraph::Source::Chain::ClassVariable) expect(cursor.chain.links.first.word).to eq('@@foo') end - it "recognizes instance variables" do + it 'recognizes instance variables' do map = Solargraph::SourceMap.load_string('@foo') cursor = map.cursor_at(Solargraph::Position.new(0, 0)) expect(cursor.chain.links.first).to be_a(Solargraph::Source::Chain::InstanceVariable) expect(cursor.chain.links.first.word).to eq('@foo') end - it "recognizes global variables" do + it 'recognizes global variables' do map = Solargraph::SourceMap.load_string('$foo') cursor = map.cursor_at(Solargraph::Position.new(0, 0)) expect(cursor.chain.links.first).to be_a(Solargraph::Source::Chain::GlobalVariable) expect(cursor.chain.links.first.word).to eq('$foo') end - it "recognizes constants" do + it 'recognizes constants' do map = Solargraph::SourceMap.load_string('Foo::Bar') cursor = map.cursor_at(Solargraph::Position.new(0, 6)) expect(cursor.chain).to be_constant expect(cursor.chain.links.map(&:word)).to eq(['Foo::Bar']) end - it "recognizes unfinished constants" do + it 'recognizes unfinished constants' do map = Solargraph::SourceMap.load_string('Foo:: $something') cursor = map.cursor_at(Solargraph::Position.new(0, 5)) expect(cursor.chain).to be_constant @@ -67,11 +67,11 @@ expect(cursor.chain).to be_undefined end - it "recognizes unfinished calls" do + it 'recognizes unfinished calls' do orig = Solargraph::Source.load_string('foo.bar') updater = Solargraph::Source::Updater.new(nil, 1, [ - Solargraph::Source::Change.new(Solargraph::Range.from_to(0, 7, 0, 7), '.') - ]) + Solargraph::Source::Change.new(Solargraph::Range.from_to(0, 7, 0, 7), '.') + ]) source = orig.synchronize(updater) map = Solargraph::SourceMap.map(source) cursor = map.cursor_at(Solargraph::Position.new(0, 8)) @@ -80,25 +80,25 @@ expect(cursor.chain).to be_undefined end - it "chains signatures with square brackets" do + it 'chains signatures with square brackets' do map = Solargraph::SourceMap.load_string('foo[0].bar') cursor = map.cursor_at(Solargraph::Position.new(0, 8)) expect(cursor.chain.links.map(&:word)).to eq(['foo', '[]', 'bar']) end - it "chains signatures with curly brackets" do + it 'chains signatures with curly brackets' do map = Solargraph::SourceMap.load_string('foo{|x| x == y}.bar') cursor = map.cursor_at(Solargraph::Position.new(0, 16)) - expect(cursor.chain.links.map(&:word)).to eq(['foo', 'bar']) + expect(cursor.chain.links.map(&:word)).to eq(%w[foo bar]) end - it "chains signatures with parentheses" do + it 'chains signatures with parentheses' do map = Solargraph::SourceMap.load_string('foo(x, y).bar') cursor = map.cursor_at(Solargraph::Position.new(0, 10)) - expect(cursor.chain.links.map(&:word)).to eq(['foo', 'bar']) + expect(cursor.chain.links.map(&:word)).to eq(%w[foo bar]) end - it "chains from repaired sources with literal strings" do + it 'chains from repaired sources with literal strings' do orig = Solargraph::Source.load_string("''") updater = Solargraph::Source::Updater.new( nil, @@ -116,8 +116,8 @@ expect(chain.links.length).to eq(2) end - it "chains incomplete constants" do - source = Solargraph::Source.load_string("Foo::") + it 'chains incomplete constants' do + source = Solargraph::Source.load_string('Foo::') chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(0, 5)) expect(chain.links.length).to eq(2) expect(chain.links.first).to be_a(Solargraph::Source::Chain::Constant) @@ -125,18 +125,18 @@ expect(chain.links.last).to be_undefined end - it "works when source error ranges contain a nil range" do + it 'works when source error ranges contain a nil range' do orig = Solargraph::Source.load_string("msg = 'msg'\nmsg", 'test.rb') updater = Solargraph::Source::Updater.new('test.rb', 1, [ - Solargraph::Source::Change.new(nil, "msg = 'msg'\nmsg.") - ]) + Solargraph::Source::Change.new(nil, "msg = 'msg'\nmsg.") + ]) source = orig.synchronize(updater) - expect { + expect do Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 4)) - }.not_to raise_error + end.not_to raise_error end - it "stops phrases at opening brackets" do + it 'stops phrases at opening brackets' do source = Solargraph::Source.load_string(%( (aa1, 2, 3) [bb2, 2, 3] @@ -150,63 +150,61 @@ expect(chain.links.first.word).to eq('cc3') end - it "chains instance variables from unsynchronized sources" do + it 'chains instance variables from unsynchronized sources' do source = instance_double(Solargraph::Source, - :synchronized? => false, - :code => '@foo.', - :filename => 'test.rb', - :string_at? => false, - :comment_at? => false, - :repaired? => false, - :parsed? => true, - :error_ranges => [], - :node_at => nil, - :tree_at => [] - ) + synchronized?: false, + code: '@foo.', + filename: 'test.rb', + string_at?: false, + comment_at?: false, + repaired?: false, + parsed?: true, + error_ranges: [], + node_at: nil, + tree_at: []) chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(0, 5)) expect(chain.links.first.word).to eq('@foo') expect(chain.links.last.word).to eq('') end - it "chains class variables from unsynchronized sources" do + it 'chains class variables from unsynchronized sources' do source = instance_double(Solargraph::Source, - :synchronized? => false, - :code => '@@foo.', - :filename => 'test.rb', - :string_at? => false, - :comment_at? => false, - :repaired? => false, - :parsed? => true, - :error_ranges => [], - :node_at => nil, - :tree_at => [] - ) + synchronized?: false, + code: '@@foo.', + filename: 'test.rb', + string_at?: false, + comment_at?: false, + repaired?: false, + parsed?: true, + error_ranges: [], + node_at: nil, + tree_at: []) chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(0, 6)) expect(chain.links.first.word).to eq('@@foo') expect(chain.links.last.word).to eq('') end - it "detects literals from chains in unsynchronized sources" do + it 'detects literals from chains in unsynchronized sources' do source1 = Solargraph::Source.load_string(%( '' )) source2 = source1.synchronize(Solargraph::Source::Updater.new( - nil, - 2, - [ - Solargraph::Source::Change.new( - Solargraph::Range.from_to(1, 8, 1, 8), - '.' - ) - ] - )) + nil, + 2, + [ + Solargraph::Source::Change.new( + Solargraph::Range.from_to(1, 8, 1, 8), + '.' + ) + ] + )) chain = Solargraph::Source::SourceChainer.chain(source2, Solargraph::Position.new(1, 9)) expect(chain.links.first).to be_a(Solargraph::Source::Chain::Literal) expect(chain.links.first.word).to eq('<::String>') expect(chain.links.last.word).to eq('') end - it "ignores ? and ! that are not method suffixes" do + it 'ignores ? and ! that are not method suffixes' do source = Solargraph::Source.load_string(%( if !t ), 'test.rb') @@ -215,14 +213,14 @@ expect(chain.links.first.word).to eq('t') end - it "chains from fixed phrases in repaired sources with missing nodes" do + it 'chains from fixed phrases in repaired sources with missing nodes' do source = Solargraph::Source.load_string(%( x = [] ), 'test.rb') updater = Solargraph::Source::Updater.new('test.rb', 1, [ - Solargraph::Source::Change.new(Solargraph::Range.from_to(2, 6, 2, 6), 'x.') - ]) + Solargraph::Source::Change.new(Solargraph::Range.from_to(2, 6, 2, 6), 'x.') + ]) updated = source.synchronize(updater) cursor = updated.cursor_at(Solargraph::Position.new(2, 8)) expect(cursor.chain.links.first.word).to eq('x') @@ -331,8 +329,8 @@ def strings; end end ), 'test.rb') updater = Solargraph::Source::Updater.new('test.rb', 1, [ - Solargraph::Source::Change.new(Solargraph::Range.from_to(5, 10, 5, 10), 'if s') - ]) + Solargraph::Source::Change.new(Solargraph::Range.from_to(5, 10, 5, 10), 'if s') + ]) updated = source.synchronize(updater) api_map = Solargraph::ApiMap.new api_map.map updated diff --git a/spec/source/updater_spec.rb b/spec/source/updater_spec.rb index 0c5f1f4c6..580d41521 100644 --- a/spec/source/updater_spec.rb +++ b/spec/source/updater_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Source::Updater do - it "applies changes" do + it 'applies changes' do text = 'foo' changes = [] range = Solargraph::Range.from_to(0, 3, 0, 3) @@ -13,7 +13,7 @@ expect(updated).to eq('foo.bar') end - it "applies repairs" do + it 'applies repairs' do text = 'foo' changes = [] range = Solargraph::Range.from_to(0, 3, 0, 3) @@ -27,7 +27,7 @@ expect(updated).to eq('foo ') end - it "handles nil ranges" do + it 'handles nil ranges' do text = 'foo' changes = [] range = nil diff --git a/spec/source_map/clip_spec.rb b/spec/source_map/clip_spec.rb index 64edbd0c6..9eac9aa25 100644 --- a/spec/source_map/clip_spec.rb +++ b/spec/source_map/clip_spec.rb @@ -93,7 +93,7 @@ class Bar api_map = Solargraph::ApiMap.new api_map.map source clip = api_map.clip_at('test.rb', [6, 12]) - expect(clip.complete.pins.map(&:name)).to eq (['@foo']) + expect(clip.complete.pins.map(&:name)).to eq(['@foo']) end it 'completes instance variables' do @@ -667,7 +667,7 @@ def initialize clip = api_map.clip_at('test.rb', [7, 8]) # @todo expect(clip.infer.tags).to eq('""') expect(clip.infer.tags).to eq('String') - expect(clip.infer.simple_tags).to eq("String") + expect(clip.infer.simple_tags).to eq('String') end it 'completes instance variable methods in rebound blocks' do @@ -1660,7 +1660,7 @@ def foo; end api_map = Solargraph::ApiMap.new.map(source) array_names = api_map.clip_at('test.rb', [5, 22]).complete.pins.map(&:name) - expect(array_names).to eq(["byteindex", "byterindex", "bytes", "bytesize", "byteslice", "bytesplice"]) + expect(array_names).to eq(%w[byteindex byterindex bytes bytesize byteslice bytesplice]) string_names = api_map.clip_at('test.rb', [6, 22]).complete.pins.map(&:name) # can be brought in by solargraph-rails @@ -2636,7 +2636,7 @@ def bar; end api_map = Solargraph::ApiMap.new.map(source) clip = api_map.clip_at('test.rb', [7, 6]) # The order of the types can vary between platforms - expect(clip.infer.items.map(&:to_s).sort).to eq(["123", ":foo", "String"]) + expect(clip.infer.items.map(&:to_s).sort).to eq(['123', ':foo', 'String']) end it 'does not map Module methods into an Object' do diff --git a/spec/source_map/mapper_spec.rb b/spec/source_map/mapper_spec.rb index c98499c6d..26c9f80b9 100644 --- a/spec/source_map/mapper_spec.rb +++ b/spec/source_map/mapper_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::SourceMap::Mapper do - it "ignores include calls that are not attached to the current namespace" do + it 'ignores include calls that are not attached to the current namespace' do source = Solargraph::Source.new(%( class Foo include Direct @@ -8,14 +8,14 @@ class Foo end )) map = Solargraph::SourceMap.map(source) - pins = map.pins.select{|pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.namespace == 'Foo'} + pins = map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.namespace == 'Foo' } names = pins.map(&:name) expect(names).to include('Direct') expect(names).not_to include('Indirect') expect(names).to include('Interior') end - it "ignores prepend calls that are not attached to the current namespace" do + it 'ignores prepend calls that are not attached to the current namespace' do source = Solargraph::Source.new(%( class Foo prepend Direct @@ -24,14 +24,14 @@ class Foo end )) map = Solargraph::SourceMap.map(source) - pins = map.pins.select{|pin| pin.is_a?(Solargraph::Pin::Reference::Prepend) && pin.namespace == 'Foo'} + pins = map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Prepend) && pin.namespace == 'Foo' } names = pins.map(&:name) expect(names).to include('Direct') expect(names).not_to include('Indirect') expect(names).to include('Interior') end - it "ignores extend calls that are not attached to the current namespace" do + it 'ignores extend calls that are not attached to the current namespace' do source = Solargraph::Source.new(%( class Foo extend Direct @@ -40,17 +40,17 @@ class Foo end )) map = Solargraph::SourceMap.map(source) - foo_pin = map.pins.select{|pin| pin.path == 'Foo'}.first + map.pins.select { |pin| pin.path == 'Foo' }.first # expect(foo_pin.extend_references.map(&:name)).to include('Direct') # expect(foo_pin.extend_references.map(&:name)).not_to include('Indirect') - pins = map.pins.select{|pin| pin.is_a?(Solargraph::Pin::Reference::Extend) && pin.namespace == 'Foo'} + pins = map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Extend) && pin.namespace == 'Foo' } names = pins.map(&:name) expect(names).to include('Direct') expect(names).not_to include('Indirect') expect(names).to include('Interior') end - it "sets scopes for attributes" do + it 'sets scopes for attributes' do source = Solargraph::Source.new(%( module Foo attr_reader :bar1 @@ -60,13 +60,13 @@ class << self end )) map = Solargraph::SourceMap.map(source) - bar1 = map.pins.select{|pin| pin.name == 'bar1'}.first + bar1 = map.pins.select { |pin| pin.name == 'bar1' }.first expect(bar1.scope).to eq(:instance) - bar2 = map.pins.select{|pin| pin.name == 'bar2'}.first + bar2 = map.pins.select { |pin| pin.name == 'bar2' }.first expect(bar2.scope).to eq(:class) end - it "sets attribute visibility" do + it 'sets attribute visibility' do map = Solargraph::SourceMap.load_string(%( module Foo attr_reader :default_public_method @@ -84,7 +84,7 @@ module Foo expect(map.first_pin('Foo#explicit_public_method').visibility).to eq(:public) end - it "processes method directives" do + it 'processes method directives' do map = Solargraph::SourceMap.load_string(%( class Foo # @!method bar(baz) @@ -119,7 +119,7 @@ class Foo expect(pin.scope).to eq(:class) end - it "processes attribute reader directives" do + it 'processes attribute reader directives' do map = Solargraph::SourceMap.load_string(%( class Foo # @!attribute [r] bar @@ -131,7 +131,7 @@ class Foo expect(pin.return_type.tag).to eq('String') end - it "processes attribute writer directives" do + it 'processes attribute writer directives' do map = Solargraph::SourceMap.load_string(%( class Foo # @!attribute [w] bar @@ -143,7 +143,7 @@ class Foo expect(pin.return_type.tag).to eq('String') end - it "processes attribute accessor directives" do + it 'processes attribute accessor directives' do map = Solargraph::SourceMap.load_string(%( class Foo # @!attribute [r,w] bar @@ -157,7 +157,7 @@ class Foo expect(pin.return_type.tag).to eq('String') end - it "processes default attribute directives" do + it 'processes default attribute directives' do map = Solargraph::SourceMap.load_string(%( class Foo # @!attribute bar @@ -171,7 +171,7 @@ class Foo expect(pin.return_type.tag).to eq('String') end - it "processes attribute directives attached to methods" do + it 'processes attribute directives attached to methods' do map = Solargraph::SourceMap.load_string(%( class Foo # @!attribute [r] bar @@ -184,7 +184,7 @@ def make_bar_attr expect(pin.return_type.tag).to eq('String') end - it "processes private visibility directives attached to methods" do + it 'processes private visibility directives attached to methods' do map = Solargraph::SourceMap.load_string(%( class Foo # @!visibility private @@ -195,7 +195,7 @@ def bar expect(map.first_pin('Foo#bar').visibility).to be(:private) end - it "processes protected visibility directives attached to methods" do + it 'processes protected visibility directives attached to methods' do map = Solargraph::SourceMap.load_string(%( class Foo # @!visibility protected @@ -206,7 +206,7 @@ def bar expect(map.first_pin('Foo#bar').visibility).to be(:protected) end - it "processes public visibility directives attached to methods" do + it 'processes public visibility directives attached to methods' do map = Solargraph::SourceMap.load_string(%( class Foo # @!visibility public @@ -217,7 +217,7 @@ def bar expect(map.first_pin('Foo#bar').visibility).to be(:public) end - it "does not process attached visibility directives on other methods" do + it 'does not process attached visibility directives on other methods' do map = Solargraph::SourceMap.load_string(%( class Example # @!visibility private @@ -232,7 +232,7 @@ def method2; end expect(method2.visibility).to be(:public) end - it "processes class-wide private visibility directives" do + it 'processes class-wide private visibility directives' do map = Solargraph::SourceMap.load_string(%( class Example # @!visibility private @@ -253,7 +253,7 @@ def method3; end expect(method3.visibility).to be(:public) end - it "processes attribute directives at class endings" do + it 'processes attribute directives at class endings' do map = Solargraph::SourceMap.load_string(%( class Foo # @!attribute [r] bar @@ -264,43 +264,43 @@ class Foo expect(pin.return_type.tag).to eq('String') end - it "finds assignment nodes for local variables using nil guards" do + it 'finds assignment nodes for local variables using nil guards' do map = Solargraph::SourceMap.load_string(%( x ||= [] )) pin = map.locals.first # @todo Dirty test - expect([:ZLIST, :ZARRAY, :array]).to include(pin.assignment.type) + expect(%i[ZLIST ZARRAY array]).to include(pin.assignment.type) end - it "finds assignment nodes for instance variables using nil guards" do + it 'finds assignment nodes for instance variables using nil guards' do map = Solargraph::SourceMap.load_string(%( @x ||= [] )) pin = map.pins.last # @todo Dirty test - expect([:ZLIST, :ZARRAY, :array]).to include(pin.assignment.type) + expect(%i[ZLIST ZARRAY array]).to include(pin.assignment.type) end - it "finds assignment nodes for class variables using nil guards" do + it 'finds assignment nodes for class variables using nil guards' do map = Solargraph::SourceMap.load_string(%( @@x ||= [] )) pin = map.pins.last # @todo Dirty test - expect([:ZLIST, :ZARRAY, :array]).to include(pin.assignment.type) + expect(%i[ZLIST ZARRAY array]).to include(pin.assignment.type) end - it "finds assignment nodes for global variables using nil guards" do + it 'finds assignment nodes for global variables using nil guards' do map = Solargraph::SourceMap.load_string(%( $x ||= [] )) pin = map.pins.last # @todo Dirty test - expect([:ZLIST, :ZARRAY, :array]).to include(pin.assignment.type) + expect(%i[ZLIST ZARRAY array]).to include(pin.assignment.type) end - it "requalifies namespace definitions with leading colons" do + it 'requalifies namespace definitions with leading colons' do map = Solargraph::SourceMap.load_string(%( class Foo class ::Bar; end @@ -311,7 +311,7 @@ class ::Bar; end expect(map.pins.map(&:path)).not_to include('Foo::Bar') end - it "maps method parameters" do + it 'maps method parameters' do map = Solargraph::SourceMap.load_string(%( class Foo def bar baz, boo = 'boo', key: 'value' @@ -319,16 +319,16 @@ def bar baz, boo = 'boo', key: 'value' end )) pin = map.first_pin('Foo#bar') - expect(pin.parameter_names).to eq(['baz', 'boo', 'key']) - pin = map.locals.select{|p| p.name == 'baz'}.first + expect(pin.parameter_names).to eq(%w[baz boo key]) + pin = map.locals.select { |p| p.name == 'baz' }.first expect(pin).to be_a(Solargraph::Pin::Parameter) - pin = map.locals.select{|p| p.name == 'boo'}.first + pin = map.locals.select { |p| p.name == 'boo' }.first expect(pin).to be_a(Solargraph::Pin::Parameter) - pin = map.locals.select{|p| p.name == 'key'}.first + pin = map.locals.select { |p| p.name == 'key' }.first expect(pin).to be_a(Solargraph::Pin::Parameter) end - it "maps method splat parameters" do + it 'maps method splat parameters' do map = Solargraph::SourceMap.load_string(%( class Foo def bar *baz @@ -340,7 +340,7 @@ def bar *baz expect(pin.parameters.first.name).to eq('baz') end - it "maps method block parameters" do + it 'maps method block parameters' do map = Solargraph::SourceMap.load_string(%( class Foo def bar &block @@ -352,18 +352,18 @@ def bar &block expect(pin.parameters.first.name).to eq('block') end - it "adds superclasses to class pins" do + it 'adds superclasses to class pins' do map = Solargraph::SourceMap.load_string(%( class Sub < Sup; end )) # pin = map.first_pin('Sub') # expect(pin.superclass_reference.name).to eq('Sup') - pin = map.pins.select{|p| p.is_a?(Solargraph::Pin::Reference::Superclass)}.first + pin = map.pins.select { |p| p.is_a?(Solargraph::Pin::Reference::Superclass) }.first expect(pin.namespace).to eq('Sub') expect(pin.name).to eq('Sup') end - it "modifies scope and visibility for module functions" do + it 'modifies scope and visibility for module functions' do map = Solargraph::SourceMap.load_string(%( module Functions module_function @@ -376,7 +376,7 @@ def foo; end expect(pin.visibility).to eq(:private) end - it "recognizes single module functions" do + it 'recognizes single module functions' do map = Solargraph::SourceMap.load_string(%( module Functions module_function def foo; end @@ -391,7 +391,7 @@ def bar; end expect(pin.visibility).to eq(:public) end - it "remaps methods for module_function symbol arguments" do + it 'remaps methods for module_function symbol arguments' do map = Solargraph::SourceMap.load_string(%( module Functions def foo @@ -409,13 +409,13 @@ def bar expect(pin.visibility).to eq(:private) pin = map.first_pin('Functions#bar') expect(pin.visibility).to eq(:public) - pin = map.pins.select{|p| p.name == '@foo' and p.context.scope == :class}.first + pin = map.pins.select { |p| p.name == '@foo' and p.context.scope == :class }.first expect(pin).to be_a(Solargraph::Pin::InstanceVariable) - pin = map.pins.select{|p| p.name == '@foo' and p.context.scope == :instance}.first + pin = map.pins.select { |p| p.name == '@foo' and p.context.scope == :instance }.first expect(pin).to be_a(Solargraph::Pin::InstanceVariable) end - it "modifies instance variables in module functions" do + it 'modifies instance variables in module functions' do map = Solargraph::SourceMap.load_string(%( module Functions module_function @@ -425,46 +425,46 @@ def foo end end )) - pin = map.pins.select{|p| p.name == '@foo' and p.context.scope == :class}.first + pin = map.pins.select { |p| p.name == '@foo' and p.context.scope == :class }.first expect(pin).to be_a(Solargraph::Pin::InstanceVariable) - pin = map.pins.select{|p| p.name == '@foo' and p.context.scope == :instance}.first + pin = map.pins.select { |p| p.name == '@foo' and p.context.scope == :instance }.first expect(pin).to be_a(Solargraph::Pin::InstanceVariable) - pin = map.pins.select{|p| p.name == '@bar' and p.context.scope == :class}.first + pin = map.pins.select { |p| p.name == '@bar' and p.context.scope == :class }.first expect(pin).to be_a(Solargraph::Pin::InstanceVariable) - pin = map.pins.select{|p| p.name == '@bar' and p.context.scope == :instance}.first + pin = map.pins.select { |p| p.name == '@bar' and p.context.scope == :instance }.first expect(pin).to be_a(Solargraph::Pin::InstanceVariable) end - it "maps class variables" do + it 'maps class variables' do map = Solargraph::SourceMap.load_string(%( class Foo @@bar = 'bar' @@baz ||= 'baz' end )) - pin = map.pins.select{|p| p.name == '@@bar'}.first + pin = map.pins.select { |p| p.name == '@@bar' }.first expect(pin).to be_a(Solargraph::Pin::ClassVariable) - pin = map.pins.select{|p| p.name == '@@baz'}.first + pin = map.pins.select { |p| p.name == '@@baz' }.first expect(pin).to be_a(Solargraph::Pin::ClassVariable) end - it "maps local variables" do + it 'maps local variables' do map = Solargraph::SourceMap.load_string(%( x = y )) - pin = map.locals.select{|p| p.name == 'x'}.first + pin = map.locals.select { |p| p.name == 'x' }.first expect(pin).to be_a(Solargraph::Pin::LocalVariable) end - it "maps global variables" do + it 'maps global variables' do map = Solargraph::SourceMap.load_string(%( $x = y )) - pin = map.pins.select{|p| p.name == '$x'}.first + pin = map.pins.select { |p| p.name == '$x' }.first expect(pin).to be_a(Solargraph::Pin::GlobalVariable) end - it "maps constants" do + it 'maps constants' do map = Solargraph::SourceMap.load_string(%( module Foo BAR = 'bar' @@ -474,7 +474,7 @@ module Foo expect(pin).to be_a(Solargraph::Pin::Constant) end - it "maps singleton methods" do + it 'maps singleton methods' do map = Solargraph::SourceMap.load_string(%( class Foo def self.bar; end @@ -485,7 +485,7 @@ def self.bar; end expect(pin.context.scope).to be(:class) end - it "maps requalified singleton methods" do + it 'maps requalified singleton methods' do map = Solargraph::SourceMap.load_string(%( class Foo; end class Bar @@ -505,7 +505,7 @@ def boo; end expect(pin.context.scope).to be(:instance) end - it "maps private class methods" do + it 'maps private class methods' do map = Solargraph::SourceMap.load_string(%( class Foo def self.bar; end @@ -517,7 +517,7 @@ def self.bar; end expect(pin.visibility).to be(:private) end - it "maps singly defined private class methods" do + it 'maps singly defined private class methods' do map = Solargraph::SourceMap.load_string(%( class Foo private_class_method def bar; end @@ -528,7 +528,7 @@ class Foo expect(pin.visibility).to be(:private) end - it "maps private constants" do + it 'maps private constants' do map = Solargraph::SourceMap.load_string(%( class Foo BAR = 'bar' @@ -540,7 +540,7 @@ class Foo expect(pin.visibility).to be(:private) end - it "maps private namespaces" do + it 'maps private namespaces' do map = Solargraph::SourceMap.load_string(%( class Foo class Bar; end @@ -552,7 +552,7 @@ class Bar; end expect(pin.visibility).to be(:private) end - it "maps attribute writers" do + it 'maps attribute writers' do map = Solargraph::SourceMap.load_string(%( class Foo attr_writer :bar @@ -562,7 +562,7 @@ class Foo expect(map.pins.map(&:path)).not_to include('Foo#bar') end - it "maps attribute accessors" do + it 'maps attribute accessors' do map = Solargraph::SourceMap.load_string(%( class Foo attr_accessor :bar @@ -572,29 +572,29 @@ class Foo expect(map.pins.map(&:path)).to include('Foo#bar') end - it "maps extend self" do + it 'maps extend self' do map = Solargraph::SourceMap.load_string(%( class Foo extend self def bar; end end )) - pin = map.first_pin('Foo') + map.first_pin('Foo') # expect(pin.extend_references.map(&:name)).to include('Foo') - pin = map.pins.select{|p| p.is_a?(Solargraph::Pin::Reference::Extend)}.first + pin = map.pins.select { |p| p.is_a?(Solargraph::Pin::Reference::Extend) }.first expect(pin.namespace).to eq('Foo') expect(pin.name).to eq('Foo') end - it "maps require calls" do + it 'maps require calls' do map = Solargraph::SourceMap.load_string(%( require 'set' )) - pin = map.pins.select{|p| p.is_a?(Solargraph::Pin::Reference::Require)}.first + pin = map.pins.select { |p| p.is_a?(Solargraph::Pin::Reference::Require) }.first expect(pin.name).to eq('set') end - it "ignores dynamic require calls" do + it 'ignores dynamic require calls' do map = Solargraph::SourceMap.load_string(%( path = 'solargraph' require path @@ -602,16 +602,16 @@ def bar; end expect(map.requires.length).to eq(0) end - it "maps block parameters" do + it 'maps block parameters' do map = Solargraph::SourceMap.load_string(%( x.each do |y| end )) - pin = map.locals.select{|p| p.name == 'y'}.first + pin = map.locals.select { |p| p.name == 'y' }.first expect(pin).to be_a(Solargraph::Pin::Parameter) end - it "forces initialize methods to be private" do + it 'forces initialize methods to be private' do map = Solargraph::SourceMap.load_string(' class Foo def initialize name @@ -622,7 +622,7 @@ def initialize name expect(pin.visibility).to be(:private) end - it "maps top-level methods" do + it 'maps top-level methods' do map = Solargraph::SourceMap.load_string(%( def foo(bar, baz) end @@ -632,18 +632,18 @@ def foo(bar, baz) expect(pin).to be_a(Solargraph::Pin::Method) end - it "maps root blocks to class scope" do + it 'maps root blocks to class scope' do smap = Solargraph::SourceMap.load_string(%( @a = some_array @a.each do |b| b end ), 'test.rb') - pin = smap.pins.select{|p| p.is_a?(Solargraph::Pin::Block)}.first + pin = smap.pins.select { |p| p.is_a?(Solargraph::Pin::Block) }.first expect(pin.context.scope).to eq(:class) end - it "maps class method blocks to class scope" do + it 'maps class method blocks to class scope' do smap = Solargraph::SourceMap.load_string(%( class Foo def self.bar @@ -654,11 +654,11 @@ def self.bar end end )) - pin = smap.pins.select{|p| p.is_a?(Solargraph::Pin::Block)}.first + pin = smap.pins.select { |p| p.is_a?(Solargraph::Pin::Block) }.first expect(pin.context.scope).to eq(:class) end - it "maps instance method blocks to instance scope" do + it 'maps instance method blocks to instance scope' do smap = Solargraph::SourceMap.load_string(%( class Foo def bar @@ -669,11 +669,11 @@ def bar end end )) - pin = smap.pins.select{|p| p.is_a?(Solargraph::Pin::Block)}.first + pin = smap.pins.select { |p| p.is_a?(Solargraph::Pin::Block) }.first expect(pin.context.scope).to eq(:instance) end - it "maps rebased namespaces without leading colons" do + it 'maps rebased namespaces without leading colons' do smap = Solargraph::SourceMap.load_string(%( class Foo class ::Bar @@ -686,79 +686,79 @@ def baz; end expect(smap.first_pin('Bar#baz')).to be_a(Solargraph::Pin::Method) end - it "maps contexts of constants" do + it 'maps contexts of constants' do var = 'BAR' smap = Solargraph::SourceMap.load_string("#{var} = nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) smap = Solargraph::SourceMap.load_string("#{var} ||= nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) end - it "maps contexts of instance variables" do + it 'maps contexts of instance variables' do var = '@bar' smap = Solargraph::SourceMap.load_string("#{var} = nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) smap = Solargraph::SourceMap.load_string("#{var} ||= nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) end - it "maps contexts of class variables" do + it 'maps contexts of class variables' do var = '@@bar' smap = Solargraph::SourceMap.load_string("#{var} = nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) smap = Solargraph::SourceMap.load_string("#{var} ||= nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) end - it "maps contexts of global variables" do + it 'maps contexts of global variables' do var = '$bar' smap = Solargraph::SourceMap.load_string("#{var} = nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) smap = Solargraph::SourceMap.load_string("#{var} ||= nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) end - it "maps contexts of local variables" do + it 'maps contexts of local variables' do var = 'bar' smap = Solargraph::SourceMap.load_string("#{var} = nil") - pin = smap.locals.select{|p| p.name == var}.first + pin = smap.locals.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) smap = Solargraph::SourceMap.load_string("#{var} ||= nil") - pin = smap.locals.select{|p| p.name == var}.first + pin = smap.locals.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) end - it "maps method aliases" do + it 'maps method aliases' do smap = Solargraph::SourceMap.load_string(%( class Foo def bar; end alias baz bar end )) - pin = smap.pins.select{|p| p.path == 'Foo#baz'}.first + pin = smap.pins.select { |p| p.path == 'Foo#baz' }.first expect(pin).to be_a(Solargraph::Pin::MethodAlias) end - it "maps attribute aliases" do + it 'maps attribute aliases' do smap = Solargraph::SourceMap.load_string(%( class Foo attr_accessor :bar alias baz bar end )) - pin = smap.pins.select{|p| p.path == 'Foo#baz'}.first + pin = smap.pins.select { |p| p.path == 'Foo#baz' }.first expect(pin).to be_a(Solargraph::Pin::MethodAlias) end - it "maps class method aliases" do + it 'maps class method aliases' do smap = Solargraph::SourceMap.load_string(%( class Foo class << self @@ -767,12 +767,12 @@ def bar; end end end )) - pin = smap.pins.select{|p| p.path == 'Foo.baz'}.first + pin = smap.pins.select { |p| p.path == 'Foo.baz' }.first expect(pin).to be_a(Solargraph::Pin::MethodAlias) expect(pin.location.range.start.line).to eq(4) end - it "maps method macros" do + it 'maps method macros' do smap = Solargraph::SourceMap.load_string(%( class Foo # @!macro @@ -780,23 +780,23 @@ class Foo def make klass; end end ), 'test.rb') - pin = smap.pins.select{|p| p.path == 'Foo#make'}.first + pin = smap.pins.select { |p| p.path == 'Foo#make' }.first expect(pin.macros).not_to be_empty end - it "maps method directives" do + it 'maps method directives' do smap = Solargraph::SourceMap.load_string(%( class Foo # @!method bar(baz) # @return [String] end ), 'test.rb') - pin = smap.pins.select{|p| p.path == 'Foo#bar'}.first + pin = smap.pins.select { |p| p.path == 'Foo#bar' }.first expect(pin.return_type.tag).to eq('String') expect(pin.location.filename).to eq('test.rb') end - it "maps aliases from alias_method" do + it 'maps aliases from alias_method' do smap = Solargraph::SourceMap.load_string(%( class Foo class << self @@ -805,22 +805,22 @@ def bar; end end end )) - pin = smap.pins.select{|p| p.path == 'Foo.baz'}.first + pin = smap.pins.select { |p| p.path == 'Foo.baz' }.first expect(pin).to be_a(Solargraph::Pin::MethodAlias) expect(pin.location.range.start.line).to eq(4) end - it "maps aliases with unknown bases" do + it 'maps aliases with unknown bases' do smap = Solargraph::SourceMap.load_string(%( class Foo alias bar baz end )) - pin = smap.pins.select{|p| p.path == 'Foo#bar'}.first + pin = smap.pins.select { |p| p.path == 'Foo#bar' }.first expect(pin).to be_a(Solargraph::Pin::MethodAlias) end - it "maps aliases to superclass methods" do + it 'maps aliases to superclass methods' do smap = Solargraph::SourceMap.load_string(%( class Sup # My foo method @@ -830,33 +830,33 @@ class Sub < Sup alias bar foo end )) - pin = smap.pins.select{|p| p.path == 'Sub#bar'}.first + pin = smap.pins.select { |p| p.path == 'Sub#bar' }.first expect(pin).to be_a(Solargraph::Pin::MethodAlias) end - it "uses nodes for method parameter assignments" do + it 'uses nodes for method parameter assignments' do smap = Solargraph::SourceMap.load_string(%( class Foo def bar(baz = quz) end end )) - pin = smap.locals.select{|p| p.name == 'baz'}.first + pin = smap.locals.select { |p| p.name == 'baz' }.first # expect(pin.assignment).to be_a(Parser::AST::Node) expect(Solargraph::Parser.is_ast_node?(pin.assignment)).to be(true) end - it "defers resolution of distant alias_method aliases" do + it 'defers resolution of distant alias_method aliases' do smap = Solargraph::SourceMap.load_string(%( class MyClass alias_method :foo, :bar end )) - pin = smap.pins.select{|p| p.is_a?(Solargraph::Pin::MethodAlias)}.first + pin = smap.pins.select { |p| p.is_a?(Solargraph::Pin::MethodAlias) }.first expect(pin).not_to be_nil end - it "maps explicit begin nodes" do + it 'maps explicit begin nodes' do smap = Solargraph::SourceMap.load_string(%( def foo begin @@ -864,11 +864,11 @@ def foo end end )) - pin = smap.pins.select{|p| p.name == '@x'}.first + pin = smap.pins.select { |p| p.name == '@x' }.first expect(pin).not_to be_nil end - it "maps rescue nodes" do + it 'maps rescue nodes' do smap = Solargraph::SourceMap.load_string(%( def foo @x = make_x @@ -876,13 +876,13 @@ def foo @y = y end )) - err_pin = smap.locals{|p| p.name == 'err'}.first + err_pin = smap.locals { |p| p.name == 'err' }.first expect(err_pin).not_to be_nil - var_pin = smap.pins.select{|p| p.name == '@y'}.first + var_pin = smap.pins.select { |p| p.name == '@y' }.first expect(var_pin).not_to be_nil end - it "maps begin/rescue nodes" do + it 'maps begin/rescue nodes' do smap = Solargraph::SourceMap.load_string(%( def foo begin @@ -892,70 +892,70 @@ def foo end end )) - err_pin = smap.locals{|p| p.name == 'err'}.first + err_pin = smap.locals { |p| p.name == 'err' }.first expect(err_pin).not_to be_nil - var_pin = smap.pins.select{|p| p.name == '@y'}.first + var_pin = smap.pins.select { |p| p.name == '@y' }.first expect(var_pin).not_to be_nil end - it "maps classes with long namespaces" do + it 'maps classes with long namespaces' do smap = Solargraph::SourceMap.load_string(%( class Foo::Bar end ), 'test.rb') - pin = smap.pins.select{|p| p.path == 'Foo::Bar'}.first + pin = smap.pins.select { |p| p.path == 'Foo::Bar' }.first expect(pin).not_to be_nil expect(pin.namespace).to eq('Foo') expect(pin.name).to eq('Bar') expect(pin.path).to eq('Foo::Bar') end - it "ignores aliases that do not map to methods or attributes" do - expect { - smap = Solargraph::SourceMap.load_string(%( + it 'ignores aliases that do not map to methods or attributes' do + expect do + Solargraph::SourceMap.load_string(%( class Foo xyz = String alias foo xyz alias_method :foo, :xyz end ), 'test.rb') - }.not_to raise_error + end.not_to raise_error end - it "ignores private_class_methods that do not map to methods or attributes" do - expect { - smap = Solargraph::SourceMap.load_string(%( + it 'ignores private_class_methods that do not map to methods or attributes' do + expect do + Solargraph::SourceMap.load_string(%( class Foo var = some_method private_class_method :var end ), 'test.rb') - }.not_to raise_error + end.not_to raise_error end - it "ignores private_constants that do not map to namespaces or constants" do - expect { - smap = Solargraph::SourceMap.load_string(%( + it 'ignores private_constants that do not map to namespaces or constants' do + expect do + Solargraph::SourceMap.load_string(%( class Foo var = some_method private_constant :var end ), 'test.rb') - }.not_to raise_error + end.not_to raise_error end - it "ignores module_functions that do not map to methods or attributes" do - expect { - smap = Solargraph::SourceMap.load_string(%( + it 'ignores module_functions that do not map to methods or attributes' do + expect do + Solargraph::SourceMap.load_string(%( class Foo var = some_method module_function :var end ), 'test.rb') - }.not_to raise_error + end.not_to raise_error end - it "handles parse directives" do + it 'handles parse directives' do smap = Solargraph::SourceMap.load_string(%( class Foo # @!parse @@ -965,18 +965,18 @@ class Foo expect(smap.pins.map(&:path)).to include('Foo::Bar') end - it "ignores syntax errors in parse directives" do - expect { + it 'ignores syntax errors in parse directives' do + expect do Solargraph::SourceMap.load_string(%( class Foo # @!parse # def end )) - }.not_to raise_error + end.not_to raise_error end - it "sets visibility for symbol parameters" do + it 'sets visibility for symbol parameters' do smap = Solargraph::SourceMap.load_string(%( class Foo def pub; end @@ -987,33 +987,33 @@ def pro; end protected 'pro' end )) - pub = smap.pins.select{|pin| pin.path == 'Foo#pub'}.first + pub = smap.pins.select { |pin| pin.path == 'Foo#pub' }.first expect(pub.visibility).to eq(:public) - bar = smap.pins.select{|pin| pin.path == 'Foo#bar'}.first + bar = smap.pins.select { |pin| pin.path == 'Foo#bar' }.first expect(bar.visibility).to eq(:private) - baz = smap.pins.select{|pin| pin.path == 'Foo#baz'}.first + baz = smap.pins.select { |pin| pin.path == 'Foo#baz' }.first expect(baz.visibility).to eq(:public) - pro = smap.pins.select{|pin| pin.path == 'Foo#pro'}.first + pro = smap.pins.select { |pin| pin.path == 'Foo#pro' }.first expect(pro.visibility).to eq(:protected) end - it "ignores errors in method directives" do - expect { + it 'ignores errors in method directives' do + expect do Solargraph::SourceMap.load_string(%[ class Foo # @!method bar( end ]) - }.not_to raise_error + end.not_to raise_error end - it "handles invalid utf8 sequences" do - expect { + it 'handles invalid utf8 sequences' do + expect do Solargraph::SourceMap.load(File.join('spec', 'fixtures', 'invalid_utf8.rb')) - }.not_to raise_error + end.not_to raise_error end - it "applies private_class_method to attributes" do + it 'applies private_class_method to attributes' do smap = Solargraph::SourceMap.load_string(%( module Foo class << self @@ -1022,7 +1022,7 @@ class << self private_class_method :bar end )) - pin = smap.pins.select{|pin| pin.path == 'Foo.bar'}.first + pin = smap.pins.select { |pin| pin.path == 'Foo.bar' }.first expect(pin.visibility).to eq(:private) end @@ -1352,9 +1352,9 @@ class Foo private_class_method end ) - expect { + expect do Solargraph::SourceMap.load_string(code, 'test.rb') - }.not_to raise_error + end.not_to raise_error end it 'positions method directive pins' do @@ -1431,7 +1431,7 @@ def foo bar:, **splat end it 'gracefully handles misunderstood macros' do - expect { + expect do Solargraph::SourceMap.load_string(%( module Foo # @!macro macro1 @@ -1442,7 +1442,7 @@ module Foo class Bar; end end )) - }.not_to raise_error + end.not_to raise_error end it 'maps autoload paths' do @@ -1570,15 +1570,15 @@ def barbaz; end end it 'handles invalid byte sequences' do - expect { + expect do Solargraph::SourceMap.load('spec/fixtures/invalid_byte.rb') - }.not_to raise_error + end.not_to raise_error end it 'handles invalid byte sequences in stringified node comments' do - expect { + expect do Solargraph::SourceMap.load('spec/fixtures/invalid_node_comment.rb') - }.not_to raise_error + end.not_to raise_error end it 'parses method directives that start with multiple hashes' do diff --git a/spec/source_map_spec.rb b/spec/source_map_spec.rb index 5d587e27c..8dba8c0b4 100644 --- a/spec/source_map_spec.rb +++ b/spec/source_map_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::SourceMap do - it "locates named path pins" do + it 'locates named path pins' do map = Solargraph::SourceMap.load_string(%( class Foo def bar; end @@ -9,15 +9,17 @@ def bar; end expect(pin.path).to eq('Foo#bar') end - it "queries symbols using fuzzy matching" do + it 'queries symbols using fuzzy matching' do map = Solargraph::SourceMap.load_string(%( class FooBar def baz_qux; end end )) - expect(map.query_symbols("foo")).to eq(map.document_symbols) - expect(map.query_symbols("foobar")).to eq(map.document_symbols) - expect(map.query_symbols("bazqux")).to eq(map.document_symbols.select{ |pin_namespace| pin_namespace.name == "baz_qux" }) + expect(map.query_symbols('foo')).to eq(map.document_symbols) + expect(map.query_symbols('foobar')).to eq(map.document_symbols) + expect(map.query_symbols('bazqux')).to eq(map.document_symbols.select { |pin_namespace| + pin_namespace.name == 'baz_qux' + }) end it 'returns all pins, except for references as document symbols' do @@ -37,7 +39,7 @@ def baz_qux; end it 'includes convention pins in document symbols' do dummy_convention = Class.new(Solargraph::Convention::Base) do - def local(source_map) + def local source_map source_map.document_symbols # call memoized method Solargraph::Environ.new( @@ -65,7 +67,7 @@ def baz_qux; end Solargraph::Convention.unregister dummy_convention end - it "locates block pins" do + it 'locates block pins' do map = Solargraph::SourceMap.load_string(%( class Foo 100.times do @@ -163,7 +165,7 @@ class Foo; end end ), 'test.rb') locals = map.locals_at(Solargraph::Location.new('test.rb', Solargraph::Range.from_to(5, 0, 5, 0))).map(&:name) - expect(locals).to eq(['x', 'foo']) + expect(locals).to eq(%w[x foo]) end it 'updates cached inference when the ApiMap changes' do diff --git a/spec/source_spec.rb b/spec/source_spec.rb index 93624313c..53c7ba4d3 100644 --- a/spec/source_spec.rb +++ b/spec/source_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Source do - it "parses code" do + it 'parses code' do code = 'class Foo;def bar;end;end' source = described_class.new(code) expect(source.code).to eq(code) @@ -7,7 +7,7 @@ expect(source).to be_parsed end - it "fixes invalid code" do + it 'fixes invalid code' do code = 'class Foo; def bar; x.' source = described_class.new(code) expect(source.code).to eq(code) @@ -17,7 +17,7 @@ expect(source).not_to be_parsed end - it "finds ranges" do + it 'finds ranges' do code = %( class Foo def bar @@ -29,7 +29,7 @@ def bar expect(source.at(range)).to eq('def bar') end - it "finds nodes" do + it 'finds nodes' do code = 'class Foo;def bar;end;end' source = described_class.new(code) node = source.node_at(0, 0) @@ -38,7 +38,7 @@ def bar expect(node.type).to eq(:def) end - it "synchronizes from incremental updates" do + it 'synchronizes from incremental updates' do code = 'class Foo;def bar;end;end' source = described_class.new(code) updater = Solargraph::Source::Updater.new( @@ -55,19 +55,19 @@ def bar expect(changed.node.children[0].children[1]).to eq(:Food) end - it "synchronizes from full updates" do + it 'synchronizes from full updates' do code1 = 'class Foo;end' code2 = 'class Bar;end' source = described_class.new(code1) updater = Solargraph::Source::Updater.new(nil, 0, [ - Solargraph::Source::Change.new(nil, code2) - ]) + Solargraph::Source::Change.new(nil, code2) + ]) changed = source.synchronize(updater) expect(changed.code).to eq(code2) expect(changed.node.children[0].children[1]).to eq(:Bar) end - it "repairs broken incremental updates" do + it 'repairs broken incremental updates' do code = %( class Foo def bar @@ -89,18 +89,18 @@ def bar expect(changed).to be_repaired end - it "flags irreparable updates" do + it 'flags irreparable updates' do code = 'class Foo;def bar;end;end' source = described_class.new(code) updater = Solargraph::Source::Updater.new(nil, 0, [ - Solargraph::Source::Change.new(nil, 'end;end') - ]) + Solargraph::Source::Change.new(nil, 'end;end') + ]) changed = source.synchronize(updater) expect(changed).to be_parsed expect(changed).to be_repaired end - it "finds references" do + it 'finds references' do source = Solargraph::Source.load_string(%( class Foo def bar @@ -113,17 +113,17 @@ def bar= 𐐀.bar = 1 )) foos = source.references('Foo') - foobacks = foos.map{|f| source.at(f.range)} - expect(foobacks).to eq(['Foo', 'Foo']) + foobacks = foos.map { |f| source.at(f.range) } + expect(foobacks).to eq(%w[Foo Foo]) bars = source.references('bar') - barbacks = bars.map{|b| source.at(b.range)} - expect(barbacks).to eq(['bar', 'bar']) + barbacks = bars.map { |b| source.at(b.range) } + expect(barbacks).to eq(%w[bar bar]) assign_bars = source.references('bar=') - assign_barbacks = assign_bars.map{|b| source.at(b.range)} + assign_barbacks = assign_bars.map { |b| source.at(b.range) } expect(assign_barbacks).to eq(['bar=', 'bar =']) end - it "allows escape sequences incompatible with UTF-8" do + it 'allows escape sequences incompatible with UTF-8' do source = Solargraph::Source.new(' x = " Un bUen café \x92" puts x @@ -131,19 +131,19 @@ def bar= expect(source.parsed?).to be(true) end - it "fixes invalid byte sequences in UTF-8 encoding" do - expect { + it 'fixes invalid byte sequences in UTF-8 encoding' do + expect do Solargraph::Source.load('spec/fixtures/invalid_byte.rb') - }.not_to raise_error + end.not_to raise_error end - it "loads files with Unicode characters" do - expect { + it 'loads files with Unicode characters' do + expect do Solargraph::Source.load('spec/fixtures/unicode.rb') - }.not_to raise_error + end.not_to raise_error end - it "updates itself when code does not change" do + it 'updates itself when code does not change' do original = Solargraph::Source.load_string('x = y', 'test.rb') updater = Solargraph::Source::Updater.new('test.rb', 1, []) updated = original.synchronize(updater) @@ -151,7 +151,7 @@ def bar= expect(updated.version).to eq(1) end - it "handles unparseable code" do + it 'handles unparseable code' do source = Solargraph::Source.load_string(%( 100.times do |num| )) @@ -161,7 +161,7 @@ def bar= expect(source.parsed?).to be(false) end - it "finds foldable ranges" do + it 'finds foldable ranges' do # Of the 7 possible ranges, 2 are too short to be foldable source = Solargraph::Source.load_string(%( =begin @@ -236,7 +236,7 @@ def range_2 expect(source.folding_ranges.first.start.line).to eq(4) end - it "finishes synchronizations for unbalanced lines" do + it 'finishes synchronizations for unbalanced lines' do source1 = Solargraph::Source.load_string('x = 1', 'test.rb') source2 = source1.synchronize Solargraph::Source::Updater.new( 'test.rb', @@ -252,7 +252,7 @@ def range_2 expect(source2).to be_synchronized end - it "handles comment arrays that overlap lines" do + it 'handles comment arrays that overlap lines' do # Fixes negative argument error (castwide/solargraph#141) source = Solargraph::Source.load_string(%( =begin @@ -260,12 +260,12 @@ def range_2 y = 1 #foo )) node = source.node_at(3, 0) - expect { + expect do source.comments_for(node) - }.not_to raise_error + end.not_to raise_error end - it "formats comments with multiple hash prefixes" do + it 'formats comments with multiple hash prefixes' do source = Solargraph::Source.load_string(%( ## # one @@ -274,7 +274,7 @@ class Foo; end )) node = source.node_at(4, 7) comments = source.comments_for(node) - expect(comments.lines.map(&:chomp)).to eq(['one', 'two']) + expect(comments.lines.map(&:chomp)).to eq(%w[one two]) end it 'does not include inner comments' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0a0c1dde4..4297bdc99 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -35,14 +35,14 @@ # @param name [String] # @param value [String] -def with_env_var(name, value) - old_value = ENV[name] # Store the old value - ENV[name] = value # Set to new value +def with_env_var name, value + old_value = ENV.fetch(name, nil) # Store the old value + ENV[name] = value # Set to new value begin - yield # Execute the block + yield # Execute the block ensure - ENV[name] = old_value # Restore the old value + ENV[name] = old_value # Restore the old value end end diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index 1cceb0211..aca95b9c3 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -136,7 +136,8 @@ def bar end )) - expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round on Integer, nil"]) + expect(checker.problems.map(&:message)).to eq(['Foo#bar return type could not be inferred', + 'Unresolved call to round on Integer, nil']) end it 'understands &. in return position' do diff --git a/spec/type_checker/levels/normal_spec.rb b/spec/type_checker/levels/normal_spec.rb index 15639130b..4ac006305 100644 --- a/spec/type_checker/levels/normal_spec.rb +++ b/spec/type_checker/levels/normal_spec.rb @@ -1,6 +1,6 @@ describe Solargraph::TypeChecker do context 'when checking at normal level' do - def type_checker(code) + def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :normal) end diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 7838f30bc..014b19c40 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -1,7 +1,7 @@ describe Solargraph::TypeChecker do context 'when at strict level' do # @return [Solargraph::TypeChecker] - def type_checker(code) + def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :strict) end @@ -757,7 +757,6 @@ def test(foo: nil) expect(checker.problems).to be_empty end - it 'validates parameters in function calls' do checker = type_checker(%( # @param bar [String] @@ -907,7 +906,7 @@ def foo *path, baz; end expect(checker.problems.map(&:message)).to eq([]) end - it "understands enough of define_method not to think the block is in class scope" do + it 'understands enough of define_method not to think the block is in class scope' do checker = type_checker(%( class Foo def initialize @@ -934,7 +933,7 @@ def bar expect(checker.problems.map(&:message)).to be_empty end - it "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(%( @@ -962,10 +961,10 @@ def foo end end )) - expect(checker.problems.map(&:message)).to eq(["Unresolved call to upcase!"]) + expect(checker.problems.map(&:message)).to eq(['Unresolved call to upcase!']) end - it "does not lose track of place and false alarm when using kwargs after a splat" do + it 'does not lose track of place and false alarm when using kwargs after a splat' do checker = type_checker(%( def foo(a, b, c); end def bar(*args, **kwargs, &blk) @@ -975,7 +974,7 @@ def bar(*args, **kwargs, &blk) expect(checker.problems.map(&:message)).to eq([]) end - it "understands Array#+ overloads" do + it 'understands Array#+ overloads' do checker = type_checker(%( c = ['a'] + ['a'] c @@ -983,7 +982,7 @@ def bar(*args, **kwargs, &blk) expect(checker.problems.map(&:message)).to eq([]) end - it "understands String#+ overloads" do + it 'understands String#+ overloads' do checker = type_checker(%( detail = '' detail += "foo" @@ -992,7 +991,7 @@ def bar(*args, **kwargs, &blk) expect(checker.problems.map(&:message)).to eq([]) end - it "understands Enumerable#each via _Each self type" do + it 'understands Enumerable#each via _Each self type' do checker = type_checker(%( class Blah # @param e [Enumerable] diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 4451a47f4..80ea8a371 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -1,6 +1,6 @@ describe Solargraph::TypeChecker do context 'with level set to strong' do - def type_checker(code) + def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end @@ -793,7 +793,8 @@ def bar end )) - expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round on Integer, nil"]) + expect(checker.problems.map(&:message)).to eq(['Foo#bar return type could not be inferred', + 'Unresolved call to round on Integer, nil']) end it 'performs simple flow-sensitive typing on lvars' do diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index 0b970b52a..eedc5a604 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -1,6 +1,6 @@ describe Solargraph::TypeChecker do context 'when level set to typed' do - def type_checker(code) + def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :typed) end diff --git a/spec/type_checker_spec.rb b/spec/type_checker_spec.rb index 33c2125e7..4597c6d75 100644 --- a/spec/type_checker_spec.rb +++ b/spec/type_checker_spec.rb @@ -2,12 +2,12 @@ describe Solargraph::TypeChecker do it 'does not raise errors checking unparsed sources' do - expect { + expect do checker = Solargraph::TypeChecker.load_string(%( foo{ )) checker.problems - }.not_to raise_error + end.not_to raise_error end it 'ignores tagged problems' do @@ -38,7 +38,7 @@ def documentation end ), nil, :strict) timed_out = true - Timeout::timeout(5) do # seconds + Timeout.timeout(5) do # seconds checker.problems timed_out = false end diff --git a/spec/workspace/config_spec.rb b/spec/workspace/config_spec.rb index 9b14d4337..4d8245d1b 100644 --- a/spec/workspace/config_spec.rb +++ b/spec/workspace/config_spec.rb @@ -2,17 +2,18 @@ require 'tmpdir' describe Solargraph::Workspace::Config do - let(:dir_path) { File.realpath(Dir.mktmpdir) } - after(:each) { FileUtils.remove_entry(dir_path) } + let(:dir_path) { File.realpath(Dir.mktmpdir) } - it "includes .rb files by default" do + after { FileUtils.remove_entry(dir_path) } + + it 'includes .rb files by default' do file = File.join(dir_path, 'file.rb') File.write(file, 'exit') config = Solargraph::Workspace::Config.new(dir_path) expect(config.calculated).to include(file) end - it "includes .rb files in subdirectories by default" do + it 'includes .rb files in subdirectories by default' do Dir.mkdir(File.join(dir_path, 'lib')) file = File.join(dir_path, 'lib', 'file.rb') File.write(file, 'exit') @@ -20,7 +21,7 @@ expect(config.calculated).to include(file) end - it "excludes test directories by default" do + it 'excludes test directories by default' do Dir.mkdir(File.join(dir_path, 'test')) file = File.join(dir_path, 'test', 'file.rb') File.write(file, 'exit') @@ -28,7 +29,7 @@ expect(config.calculated).not_to include(file) end - it "excludes spec directories by default" do + it 'excludes spec directories by default' do Dir.mkdir(File.join(dir_path, 'spec')) file = File.join(dir_path, 'spec', 'file.rb') File.write(file, 'exit') @@ -36,7 +37,7 @@ expect(config.calculated).not_to include(file) end - it "excludes vendor directories by default" do + it 'excludes vendor directories by default' do Dir.mkdir(File.join(dir_path, 'vendor')) file = File.join(dir_path, 'vendor', 'file.rb') File.write(file, 'exit') @@ -44,7 +45,7 @@ expect(config.calculated).not_to include(file) end - it "includes base reporters by default" do + it 'includes base reporters by default' do config = Solargraph::Workspace::Config.new(dir_path) expect(config.reporters).to include('rubocop') expect(config.reporters).to include('require_not_found') diff --git a/spec/workspace_spec.rb b/spec/workspace_spec.rb index 30b8d7fbc..23535ed87 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -6,15 +6,15 @@ let(:dir_path) { File.realpath(Dir.mktmpdir) } let(:file_path) { File.join(dir_path, 'file.rb') } - before(:each) { File.write(file_path, 'exit') } - after(:each) { FileUtils.remove_entry(dir_path) } + before { File.write(file_path, 'exit') } + after { FileUtils.remove_entry(dir_path) } - it "loads sources from a directory" do + it 'loads sources from a directory' do expect(workspace.filenames).to include(file_path) expect(workspace.has_file?(file_path)).to be(true) end - it "ignores non-Ruby files by default" do + it 'ignores non-Ruby files by default' do not_ruby = File.join(dir_path, 'not_ruby.txt') File.write not_ruby, 'text' @@ -22,14 +22,14 @@ expect(workspace.filenames).not_to include(not_ruby) end - it "does not merge non-workspace sources" do + it 'does not merge non-workspace sources' do source = Solargraph::Source.load_string('exit', 'not_ruby.txt') workspace.merge source expect(workspace.filenames).not_to include(source.filename) end - it "updates sources" do + it 'updates sources' do original = workspace.source(file_path) updated = Solargraph::Source.load_string('puts "updated"', file_path) workspace.merge updated @@ -39,7 +39,7 @@ expect(workspace.source(file_path)).to eq(updated) end - it "removes deleted sources" do + it 'removes deleted sources' do expect(workspace.filenames).to include(file_path) original = workspace.source(file_path) @@ -49,37 +49,39 @@ expect(workspace.filenames).not_to include(file_path) end - it "raises an exception for workspace size limits" do - config = instance_double(Solargraph::Workspace::Config, calculated: Array.new(Solargraph::Workspace::Config::MAX_FILES + 1), max_files: Solargraph::Workspace::Config::MAX_FILES) + it 'raises an exception for workspace size limits' do + config = instance_double(Solargraph::Workspace::Config, + calculated: Array.new(Solargraph::Workspace::Config::MAX_FILES + 1), max_files: Solargraph::Workspace::Config::MAX_FILES) - expect { + expect do Solargraph::Workspace.new('.', config) - }.to raise_error(Solargraph::WorkspaceTooLargeError) + end.to raise_error(Solargraph::WorkspaceTooLargeError) end - it "allows for unlimited files in config" do + it 'allows for unlimited files in config' do gemspec_file = File.join(dir_path, 'test.gemspec') File.write(gemspec_file, '') calculated = Array.new(Solargraph::Workspace::Config::MAX_FILES + 1) { gemspec_file } # @todo Mock reveals tight coupling - config = instance_double(Solargraph::Workspace::Config, calculated: calculated, max_files: 0, allow?: true, require_paths: [], plugins: []) - expect { + config = instance_double(Solargraph::Workspace::Config, calculated: calculated, max_files: 0, allow?: true, + require_paths: [], plugins: []) + expect do Solargraph::Workspace.new('.', config) - }.not_to raise_error + end.not_to raise_error end - it "detects gemspecs in workspaces" do + 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.gemspec_files).to eq([gemspec_file]) end - it "generates default require path" do + it 'generates default require path' do expect(workspace.require_paths).to eq([File.join(dir_path, 'lib')]) end - it "generates require paths from gemspecs" do + it 'generates require paths from gemspecs' do gemspec_file = File.join(dir_path, 'test.gemspec') File.write(gemspec_file, %( Gem::Specification.new do |s| @@ -93,7 +95,7 @@ expect(workspace.require_paths).to eq([File.join(dir_path, 'other_lib')]) end - it "rescues errors in gemspecs" do + it 'rescues errors in gemspecs' do gemspec_file = File.join(dir_path, 'test.gemspec') File.write(gemspec_file, %( raise 'Error' @@ -101,7 +103,7 @@ expect(workspace.require_paths).to eq([File.join(dir_path, 'lib')]) end - it "rescues syntax errors in gemspecs" do + it 'rescues syntax errors in gemspecs' do gemspec_file = File.join(dir_path, 'test.gemspec') File.write(gemspec_file, %( 123. @@ -109,7 +111,7 @@ expect(workspace.require_paths).to eq([File.join(dir_path, 'lib')]) end - it "detects locally required paths" do + it 'detects locally required paths' do required_file = File.join(dir_path, 'lib', 'test.rb') Dir.mkdir(File.join(dir_path, 'lib')) File.write(required_file, 'exit') @@ -122,7 +124,7 @@ expect(workspace.would_require?('emptydir')).to be(false) end - it "uses configured require paths" do + it 'uses configured require paths' do workspace = Solargraph::Workspace.new('spec/fixtures/workspace') expect(workspace.require_paths).to eq([File.absolute_path('spec/fixtures/workspace/lib'), File.absolute_path('spec/fixtures/workspace/ext')]) @@ -135,10 +137,11 @@ end it 'rescues errors loading files into sources' do - config = instance_double(Solargraph::Workspace::Config, directory: './path', calculated: ['./path/does_not_exist.rb'], max_files: 5000, require_paths: [], plugins: []) - expect { + config = instance_double(Solargraph::Workspace::Config, directory: './path', + calculated: ['./path/does_not_exist.rb'], max_files: 5000, require_paths: [], plugins: []) + expect do Solargraph::Workspace.new('./path', config) - }.not_to raise_error + end.not_to raise_error end describe '#cache_all_for_workspace!' do diff --git a/spec/yard_map/mapper/to_method_spec.rb b/spec/yard_map/mapper/to_method_spec.rb index c90fe75ed..e406f00a4 100644 --- a/spec/yard_map/mapper/to_method_spec.rb +++ b/spec/yard_map/mapper/to_method_spec.rb @@ -1,38 +1,38 @@ describe Solargraph::YardMap::Mapper::ToMethod do - let(:code_object) { + let(:code_object) do namespace = YARD::CodeObjects::ModuleObject.new(nil, 'Example') YARD::CodeObjects::MethodObject.new(namespace, 'foo') - } + end it 'parses args' do - code_object.parameters = [["bar", nil]] + code_object.parameters = [['bar', nil]] pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) param = pin.parameters.first expect(param.decl).to be(:arg) - expect(param.name).to eq("bar") - expect(param.full).to eq("bar") + expect(param.name).to eq('bar') + expect(param.full).to eq('bar') end it 'parses optargs' do - code_object.parameters = [["bar", "'baz'"]] + code_object.parameters = [['bar', "'baz'"]] pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) param = pin.parameters.first expect(param.decl).to be(:optarg) - expect(param.name).to eq("bar") + expect(param.name).to eq('bar') expect(param.full).to eq("bar = 'baz'") end it 'parses kwargs' do - code_object.parameters = [["bar:", nil]] + code_object.parameters = [['bar:', nil]] pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) param = pin.parameters.first expect(param.name).to eq('bar') expect(param.decl).to be(:kwarg) - expect(param.full).to eq("bar:") + expect(param.full).to eq('bar:') end it 'parses kwoptargs' do - code_object.parameters = [["bar:", "'baz'"]] + code_object.parameters = [['bar:', "'baz'"]] pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) param = pin.parameters.first expect(param.decl).to be(:kwoptarg) @@ -41,43 +41,43 @@ end it 'parses restargs' do - code_object.parameters = [["*bar", nil]] + code_object.parameters = [['*bar', nil]] pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) param = pin.parameters.first expect(param.decl).to be(:restarg) expect(param.name).to eq('bar') - expect(param.full).to eq("*bar") + expect(param.full).to eq('*bar') end it 'parses kwrestargs' do - code_object.parameters = [["**bar", nil]] + code_object.parameters = [['**bar', nil]] pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) param = pin.parameters.first expect(param.decl).to be(:kwrestarg) expect(param.name).to eq('bar') - expect(param.full).to eq("**bar") + expect(param.full).to eq('**bar') end it 'parses blockargs' do - code_object.parameters = [["&bar", nil]] + code_object.parameters = [['&bar', nil]] pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) param = pin.parameters.first expect(param.decl).to be(:blockarg) expect(param.name).to eq('bar') - expect(param.full).to eq("&bar") + expect(param.full).to eq('&bar') end it 'parses undeclared but typed blockargs' do pending('block args coming from YARD alone') code_object.parameters = [] - code_object.docstring = < Date: Sat, 31 Jan 2026 13:54:12 -0500 Subject: [PATCH 926/930] manual typechecking fixes --- lib/solargraph/parser/parser_gem/node_methods.rb | 1 + lib/solargraph/pin/base.rb | 9 +++++++-- lib/solargraph/pin/callable.rb | 2 +- lib/solargraph/pin/method.rb | 2 +- lib/solargraph/pin/parameter.rb | 2 +- lib/solargraph/position.rb | 1 - lib/solargraph/rbs_map.rb | 2 ++ lib/solargraph/rbs_map/conversions.rb | 7 +++---- lib/solargraph/source.rb | 1 - lib/solargraph/source/source_chainer.rb | 2 +- lib/solargraph/source_map.rb | 2 +- lib/solargraph/source_map/clip.rb | 2 +- lib/solargraph/source_map/mapper.rb | 6 ++---- lib/solargraph/workspace/gemspecs.rb | 1 - 14 files changed, 21 insertions(+), 19 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 69249a94e..f026055db 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -109,6 +109,7 @@ def convert_hash node return convert_hash(node.children[0]) if node.type == :kwsplat # @sg-ignore Translate to something flow sensitive typing understands if Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat + # @sg-ignore Translate to something flow sensitive typing understands return convert_hash(node.children[0]) end # @sg-ignore Translate to something flow sensitive typing understands diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index bd58cd47b..d809abfb9 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -245,6 +245,9 @@ def dodgy_return_type_source? def choose other, attr results = [self, other].map(&attr).compact # true and false are different classes and can't be sorted + + # @sg-ignore Wrong argument type for Array#include?: object + # expected Boolean, received Proc return true if results.any? { |r| [true, false].include?(r) } return results.first if results.any? { |r| r.is_a? AST::Node } results.min @@ -499,8 +502,10 @@ def nearly? other # @sg-ignore Translate to something flow sensitive typing understands (comments == other.comments || # @sg-ignore Translate to something flow sensitive typing understands - (((maybe_directives? == false && other.maybe_directives? == false) || compare_directives(directives, - other.directives)) && + (((maybe_directives? == false && other.maybe_directives? == false) || + compare_directives(directives, + # @sg-ignore Translate to something flow sensitive typing understands + other.directives)) && # @sg-ignore Translate to something flow sensitive typing understands compare_docstring_tags(docstring, other.docstring)) ) diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 05a72413b..4b3b238dd 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -256,9 +256,9 @@ def mandatory_positional_param_count # @return [String] def parameters_to_rbs - # @sg-ignore Need to add nil check here rbs_generics + '(' + parameters.map { |param| param.to_rbs + # @sg-ignore Need to add nil check here }.join(', ') + ') ' + (block.nil? ? '' : '{ ' + block.to_rbs + ' } ') end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 51588b88a..aa363b844 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -276,8 +276,8 @@ def method_name end def typify api_map - # @sg-ignore Need to add nil check here logger.debug do + # @sg-ignore Need to add nil check here "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context.rooted_tags}, return_type=#{return_type.rooted_tags}) - starting" end decl = super diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 4aa8f0e19..c8c47ca2c 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -281,9 +281,9 @@ def typify_method_param api_map if found.nil? and !index.nil? && params[index] && (params[index].name.nil? || params[index].name.empty?) found = params[index] end - # @sg-ignore Need to add nil check here unless found.nil? || found.types.nil? return ComplexType.try_parse(*found.types).qualify(api_map, + # @sg-ignore Need to add nil check here *meth.closure.gates) end end diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index e8092ea78..779ce03ce 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -67,7 +67,6 @@ def self.to_offset text, position line += 1 break if line == position.line - # @sg-ignore oflow sensitive typing should be able to handle redefinition last_line_index = newline_index end diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index fd366f8d5..bc0dcba28 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -201,7 +201,9 @@ def add_library loader, library, version, out: $stderr end # @return [String] + # @sg-ignore Need to add nil check here def short_name + # @sg-ignore Need to add nil check here self.class.name.split('::').last end end diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index bc6befe5b..b6295040d 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -77,8 +77,8 @@ def convert_decl_to_pin decl, closure when RBS::AST::Declarations::TypeAlias # @sg-ignore flow sensitive typing should support case/when unless closure.name == '' || decl.name.absolute? - # @sg-ignore flow sensitive typing should support case/when Solargraph.assert_or_log(:rbs_closure, + # @sg-ignore flow sensitive typing should support case/when "Ignoring closure #{closure.inspect} on alias type name #{decl.name}") end # @sg-ignore flow sensitive typing should support case/when @@ -86,8 +86,8 @@ def convert_decl_to_pin decl, closure when RBS::AST::Declarations::Module # @sg-ignore flow sensitive typing should support case/when unless closure.name == '' || decl.name.absolute? - # @sg-ignore flow sensitive typing should support case/when Solargraph.assert_or_log(:rbs_closure, + # @sg-ignore flow sensitive typing should support case/when "Ignoring closure #{closure.inspect} on alias type name #{decl.name}") end module_decl_to_pin decl @@ -149,10 +149,9 @@ def rooted_name type_name # fqns names are implicitly fully qualified - they are relative # to the root namespace and are not prefixed with :: # - # @param [RBS::TypeName] + # @param type_name [RBS::TypeName] # # @return [String] - # @param [Object] type_name def fqns type_name unless type_name.absolute? Solargraph.assert_or_log(:rbs_fqns, "Received unexpected unqualified type name: #{type_name}") diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 1451692a5..5e88fb78a 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -294,7 +294,6 @@ def inner_folding_ranges top, result = [], parent = nil range = Range.from_node(top) # @sg-ignore Need to add nil check here if (result.empty? || range.start.line > result.last.start.line) && !(range.ending.line - range.start.line < 2) - # @sg-ignore Need to add nil check here result.push range end end diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index 5c235058a..b5738341d 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -36,9 +36,9 @@ def chain return Chain.new([Chain::Literal.new('Integer', Integer(phrase[0..-2])), Chain::UNDEFINED_CALL]) end - # @sg-ignore Need to add nil check here if phrase.start_with?(':') && !phrase.start_with?('::') return Chain.new([Chain::Literal.new('Symbol', + # @sg-ignore Need to add nil check here phrase[1..].to_sym)]) end if end_of_phrase.strip == '::' && source.code[Position.to_offset( diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index b22a058f9..18f623993 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -210,9 +210,9 @@ def _locate_pin line, character, *klasses # @todo Attribute pins should not be treated like closures, but # there's probably a better way to handle it next if pin.is_a?(Pin::Method) && pin.attribute? - # @sg-ignore Need to add nil check here found = pin if (klasses.empty? || klasses.any? do |kls| pin.is_a?(kls) + # @sg-ignore Need to add nil check here end) && pin.location.range.contain?(position) # @sg-ignore Need to add nil check here break if pin.location.range.start.line > line diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index 2e48b0c89..d38dfdc36 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -23,9 +23,9 @@ def define return [] if cursor.comment? || cursor.chain.literal? result = cursor.chain.define(api_map, closure, locals) result.concat file_global_methods - # @sg-ignore Need to add nil check here if result.empty? result.concat((source_map.pins + source_map.locals).select do |p| + # @sg-ignore Need to add nil check here p.name == cursor.word && p.location.range.contain?(cursor.position) end) end diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index 5d8d66a9c..e1d005415 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -114,8 +114,7 @@ def process_directive source_position, comment_position, directive case directive.tag.tag_name when 'method' namespace = closure_at(source_position) || @pins.first - # @todo Missed nil violation - # @todo Need to add nil check here + # @sg-ignore Need to add nil check here namespace = closure_at(comment_position) if namespace.location.range.start.line < comment_position.line begin src = Solargraph::Source.load_string("def #{directive.tag.name};end", @source.filename) @@ -180,8 +179,7 @@ def process_directive source_position, comment_position, directive name = directive.tag.name closure = closure_at(source_position) || @pins.first - # @todo Missed nil violation - # @todo Need to add nil check here + # @sg-ignore Need to add nil check here closure = closure_at(comment_position) if closure.location.range.start.line < comment_position.line if closure.is_a?(Pin::Method) && no_empty_lines?(comment_position.line, source_position.line) # @todo Smelly instance variable access diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 2389c2d76..6395f44bd 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -188,7 +188,6 @@ def to_gem_specification specish # Specification specish end - # @sg-ignore Unresolved constant Gem::StubSpecification when Gem::StubSpecification # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' specish.to_spec From d2573cc76a64872726116dc8a65c90b470f11f82 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 31 Jan 2026 13:57:44 -0500 Subject: [PATCH 927/930] Fix indentation --- 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 d809abfb9..49a34728a 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -501,7 +501,7 @@ def nearly? other (closure == other.closure || (closure && closure.nearly?(other.closure))) && # @sg-ignore Translate to something flow sensitive typing understands (comments == other.comments || - # @sg-ignore Translate to something flow sensitive typing understands + # @sg-ignore Translate to something flow sensitive typing understands (((maybe_directives? == false && other.maybe_directives? == false) || compare_directives(directives, # @sg-ignore Translate to something flow sensitive typing understands From 7cb2fbabd320c302dac1fdc4f220125a057b7f3e Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 31 Jan 2026 14:06:37 -0500 Subject: [PATCH 928/930] Sync @sg-ignores with CI --- lib/solargraph/rbs_map.rb | 2 -- lib/solargraph/workspace/gemspecs.rb | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index bc0dcba28..fd366f8d5 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -201,9 +201,7 @@ def add_library loader, library, version, out: $stderr end # @return [String] - # @sg-ignore Need to add nil check here def short_name - # @sg-ignore Need to add nil check here self.class.name.split('::').last end end diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 6395f44bd..2389c2d76 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -188,6 +188,7 @@ def to_gem_specification specish # Specification specish end + # @sg-ignore Unresolved constant Gem::StubSpecification when Gem::StubSpecification # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' specish.to_spec From 7a71eedf004e1558fed5c462d19c286c6fb106e1 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 31 Jan 2026 15:32:51 -0500 Subject: [PATCH 929/930] rubocop -A --- .rubocop_todo.yml | 241 +++--------------- Gemfile | 2 + Rakefile | 2 + bin/solargraph | 1 + lib/solargraph/api_map.rb | 19 +- lib/solargraph/api_map/index.rb | 2 +- lib/solargraph/api_map/source_to_yard.rb | 2 +- lib/solargraph/api_map/store.rb | 2 +- lib/solargraph/bench.rb | 3 +- lib/solargraph/complex_type.rb | 20 +- lib/solargraph/complex_type/type_methods.rb | 4 +- lib/solargraph/complex_type/unique_type.rb | 18 +- .../data_definition/data_definition_node.rb | 2 +- lib/solargraph/convention/gemfile.rb | 2 +- lib/solargraph/convention/gemspec.rb | 2 +- lib/solargraph/convention/rakefile.rb | 2 +- lib/solargraph/converters/dd.rb | 2 + lib/solargraph/converters/dl.rb | 2 + lib/solargraph/converters/dt.rb | 2 + lib/solargraph/converters/misc.rb | 2 + lib/solargraph/diagnostics/rubocop.rb | 2 +- lib/solargraph/diagnostics/rubocop_helpers.rb | 2 +- lib/solargraph/diagnostics/update_errors.rb | 10 +- lib/solargraph/doc_map.rb | 4 +- lib/solargraph/gem_pins.rb | 4 +- lib/solargraph/language_server/host.rb | 2 +- lib/solargraph/language_server/message.rb | 2 +- .../language_server/message/base.rb | 2 +- .../message/completion_item/resolve.rb | 2 +- .../message/text_document/definition.rb | 76 +++--- .../text_document/document_highlight.rb | 28 +- .../message/text_document/document_symbol.rb | 50 ++-- .../message/text_document/hover.rb | 2 +- .../message/text_document/prepare_rename.rb | 20 +- .../message/text_document/references.rb | 28 +- .../message/text_document/rename.rb | 34 ++- .../message/text_document/signature_help.rb | 2 +- .../message/text_document/type_definition.rb | 44 ++-- .../workspace/did_change_configuration.rb | 62 +++-- .../workspace/did_change_watched_files.rb | 69 ++--- .../workspace/did_change_workspace_folders.rb | 40 +-- .../message/workspace/workspace_symbol.rb | 46 ++-- lib/solargraph/language_server/request.rb | 2 +- .../language_server/transport/data_reader.rb | 2 +- lib/solargraph/library.rb | 22 +- lib/solargraph/location.rb | 10 +- lib/solargraph/logging.rb | 6 +- lib/solargraph/page.rb | 2 +- lib/solargraph/parser.rb | 2 + lib/solargraph/parser/comment_ripper.rb | 6 +- .../parser/flow_sensitive_typing.rb | 2 + lib/solargraph/parser/parser_gem.rb | 2 + .../parser/parser_gem/node_chainer.rb | 9 +- .../parser/parser_gem/node_methods.rb | 34 +-- .../parser_gem/node_processors/sclass_node.rb | 2 +- .../parser_gem/node_processors/send_node.rb | 16 +- lib/solargraph/parser/snippet.rb | 2 + lib/solargraph/pin/base.rb | 20 +- lib/solargraph/pin/breakable.rb | 2 + lib/solargraph/pin/callable.rb | 7 +- lib/solargraph/pin/closure.rb | 2 +- lib/solargraph/pin/compound_statement.rb | 2 + lib/solargraph/pin/constant.rb | 4 +- lib/solargraph/pin/conversions.rb | 2 +- lib/solargraph/pin/delegated_method.rb | 2 +- lib/solargraph/pin/local_variable.rb | 4 +- lib/solargraph/pin/method.rb | 24 +- lib/solargraph/pin/namespace.rb | 4 +- lib/solargraph/pin/parameter.rb | 13 +- lib/solargraph/pin/search.rb | 2 +- lib/solargraph/pin/signature.rb | 6 +- lib/solargraph/pin_cache.rb | 8 +- lib/solargraph/position.rb | 14 +- lib/solargraph/range.rb | 12 +- lib/solargraph/rbs_map.rb | 4 +- lib/solargraph/rbs_map/core_fills.rb | 6 +- lib/solargraph/shell.rb | 10 +- lib/solargraph/source.rb | 10 +- lib/solargraph/source/chain.rb | 12 +- lib/solargraph/source/chain/array.rb | 6 +- lib/solargraph/source/chain/call.rb | 16 +- lib/solargraph/source/chain/constant.rb | 2 +- lib/solargraph/source/chain/hash.rb | 14 +- lib/solargraph/source/chain/if.rb | 14 +- lib/solargraph/source/chain/link.rb | 22 +- lib/solargraph/source/chain/q_call.rb | 2 + lib/solargraph/source/change.rb | 6 +- lib/solargraph/source/cursor.rb | 6 +- lib/solargraph/source/source_chainer.rb | 31 +-- lib/solargraph/source/updater.rb | 2 +- lib/solargraph/source_map/clip.rb | 2 +- lib/solargraph/source_map/mapper.rb | 8 +- lib/solargraph/type_checker.rb | 9 +- lib/solargraph/workspace.rb | 4 +- lib/solargraph/yard_map/helpers.rb | 2 + lib/solargraph/yard_map/mapper.rb | 9 +- lib/solargraph/yard_map/mapper/to_method.rb | 2 +- solargraph.gemspec | 4 +- spec/api_map/cache_spec.rb | 4 +- spec/api_map/config_spec.rb | 2 + spec/api_map/source_to_yard_spec.rb | 16 +- spec/api_map/store_spec.rb | 16 +- spec/api_map_method_spec.rb | 20 +- spec/api_map_spec.rb | 18 +- spec/complex_type_spec.rb | 8 +- spec/convention/struct_definition_spec.rb | 2 + spec/convention_spec.rb | 2 + spec/diagnostics/base_spec.rb | 4 +- spec/diagnostics/require_not_found_spec.rb | 4 +- spec/diagnostics/rubocop_helpers_spec.rb | 10 +- spec/diagnostics/rubocop_spec.rb | 8 +- spec/diagnostics/type_check_spec.rb | 16 +- spec/diagnostics/update_errors_spec.rb | 6 +- spec/diagnostics_spec.rb | 8 +- spec/doc_map_spec.rb | 4 +- spec/language_server/host/diagnoser_spec.rb | 4 +- spec/language_server/host/dispatch_spec.rb | 4 +- .../host/message_worker_spec.rb | 4 +- spec/language_server/host_spec.rb | 46 ++-- .../message/completion_item/resolve_spec.rb | 14 +- .../extended/check_gem_version_spec.rb | 10 +- .../message/initialize_spec.rb | 92 +++---- .../message/text_document/definition_spec.rb | 52 ++-- .../message/text_document/formatting_spec.rb | 4 +- .../message/text_document/hover_spec.rb | 46 ++-- .../message/text_document/rename_spec.rb | 112 ++++---- .../text_document/type_definition_spec.rb | 24 +- .../did_change_configuration_spec.rb | 8 +- .../did_change_watched_files_spec.rb | 90 +++---- spec/language_server/message_spec.rb | 6 +- spec/language_server/protocol_spec.rb | 6 +- .../language_server/transport/adapter_spec.rb | 2 + .../transport/data_reader_spec.rb | 4 +- spec/language_server/uri_helpers_spec.rb | 14 +- spec/library_spec.rb | 86 ++++--- spec/logging_spec.rb | 8 +- spec/parser/node_chainer_spec.rb | 2 + spec/parser/node_methods_spec.rb | 98 +++---- spec/parser/node_processor_spec.rb | 18 +- spec/parser_spec.rb | 4 +- spec/pin/base_spec.rb | 30 ++- spec/pin/base_variable_spec.rb | 2 + spec/pin/block_spec.rb | 4 +- spec/pin/class_variable_spec.rb | 2 + spec/pin/constant_spec.rb | 2 + spec/pin/delegated_method_spec.rb | 6 +- spec/pin/documenting_spec.rb | 2 + spec/pin/instance_variable_spec.rb | 6 +- spec/pin/keyword_spec.rb | 4 +- spec/pin/local_variable_spec.rb | 2 + spec/pin/method_spec.rb | 56 ++-- spec/pin/namespace_spec.rb | 14 +- spec/pin/parameter_spec.rb | 8 +- spec/pin/search_spec.rb | 8 +- spec/pin/symbol_spec.rb | 20 +- spec/position_spec.rb | 34 +-- spec/rbs_map/conversions_spec.rb | 4 +- spec/rbs_map/core_map_spec.rb | 10 +- spec/rbs_map/stdlib_map_spec.rb | 10 +- spec/rbs_map_spec.rb | 14 +- spec/source/chain/array_spec.rb | 2 + spec/source/chain/call_spec.rb | 2 + spec/source/chain/class_variable_spec.rb | 4 +- spec/source/chain/constant_spec.rb | 2 + spec/source/chain/global_variable_spec.rb | 4 +- spec/source/chain/head_spec.rb | 4 +- spec/source/chain/instance_variable_spec.rb | 10 +- spec/source/chain/link_spec.rb | 2 + spec/source/chain/literal_spec.rb | 2 + spec/source/chain/or_spec.rb | 2 + spec/source/chain/q_call_spec.rb | 2 + spec/source/chain/z_super_spec.rb | 4 +- spec/source/chain_spec.rb | 53 ++-- spec/source/change_spec.rb | 20 +- spec/source/source_chainer_spec.rb | 44 ++-- spec/source/updater_spec.rb | 8 +- spec/source_map/clip_spec.rb | 4 +- spec/source_map/mapper_spec.rb | 12 +- spec/source_map/node_processor_spec.rb | 2 + spec/source_map_spec.rb | 30 ++- spec/source_spec.rb | 38 +-- spec/spec_helper.rb | 2 + spec/type_checker/levels/normal_spec.rb | 12 +- spec/type_checker/levels/strict_spec.rb | 4 +- spec/type_checker/levels/strong_spec.rb | 2 + spec/type_checker/levels/typed_spec.rb | 2 + spec/type_checker/rules_spec.rb | 10 +- spec/type_checker_spec.rb | 8 +- spec/workspace/config_spec.rb | 14 +- spec/workspace_spec.rb | 12 +- spec/yard_map/mapper/to_method_spec.rb | 18 +- spec/yard_map/mapper_spec.rb | 4 +- 192 files changed, 1450 insertions(+), 1346 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 1ad3d1116..eea514d1c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -19,16 +19,18 @@ Gemspec/RequireMFA: - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' - 'spec/fixtures/rubocop-custom-version/specifications/rubocop-0.0.0.gemspec' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: RequireParenthesesForMethodChains. -Lint/AmbiguousRange: - Enabled: false +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyleAlignWith, Severity. +# SupportedStylesAlignWith: keyword, variable, start_of_line +Layout/EndAlignment: + Exclude: + - 'lib/solargraph/shell.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowSafeAssignment. -Lint/AssignmentInCondition: +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Width, AllowedPatterns. +Layout/IndentationWidth: Exclude: - - 'lib/solargraph/library.rb' + - 'lib/solargraph/shell.rb' Lint/BinaryOperatorWithIdenticalOperands: Exclude: @@ -56,25 +58,9 @@ Lint/DuplicateBranch: # This cop supports unsafe autocorrection (--autocorrect-all). Lint/InterpolationCheck: Exclude: - - 'spec/complex_type_spec.rb' - - 'spec/parser/node_methods_spec.rb' - 'spec/source/chain_spec.rb' - 'spec/source/cursor_spec.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -Lint/NonAtomicFileOperation: - Exclude: - - 'spec/diagnostics/rubocop_spec.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowedMethods, InferNonNilReceiver, AdditionalNilMethods. -# AllowedMethods: instance_of?, kind_of?, is_a?, eql?, respond_to?, equal? -# AdditionalNilMethods: present?, blank?, try, try! -Lint/RedundantSafeNavigation: - Exclude: - - 'lib/solargraph/api_map/source_to_yard.rb' - - 'lib/solargraph/rbs_map.rb' - # Configuration parameters: AllowKeywordBlockArguments. Lint/UnderscorePrefixedVariableName: Exclude: @@ -94,11 +80,6 @@ Lint/UselessAssignment: - 'spec/fixtures/rubocop-unused-variable-error/app.rb' - 'spec/fixtures/unicode.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -Lint/UselessMethodDefinition: - Exclude: - - 'lib/solargraph/pin/signature.rb' - # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max. Metrics/AbcSize: Exclude: @@ -166,12 +147,6 @@ Naming/HeredocDelimiterNaming: Exclude: - 'spec/yard_map/mapper/to_method_spec.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyleForLeadingUnderscores. -# SupportedStylesForLeadingUnderscores: disallowed, required, optional -Naming/MemoizedInstanceVariableName: - 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: @@ -200,12 +175,6 @@ Naming/PredicatePrefix: - 'lib/solargraph/pin_cache.rb' - 'lib/solargraph/workspace.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -RSpec/BeEq: - Exclude: - - 'spec/complex_type_spec.rb' - - 'spec/pin/method_spec.rb' - RSpec/BeforeAfterAll: Exclude: - '**/spec/spec_helper.rb' @@ -225,12 +194,6 @@ RSpec/DescribeClass: - '**/spec/views/**/*' - 'spec/complex_type_spec.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. -# SupportedStyles: described_class, explicit -RSpec/DescribedClass: - Enabled: false - # This cop supports safe autocorrection (--autocorrect). RSpec/ExpectActual: Exclude: @@ -253,19 +216,6 @@ RSpec/MultipleExpectations: RSpec/NestedGroups: Max: 4 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Strict, EnforcedStyle, AllowedExplicitMatchers. -# SupportedStyles: inflected, explicit -RSpec/PredicateMatcher: - Exclude: - - 'spec/language_server/message/workspace/did_change_configuration_spec.rb' - - 'spec/source_spec.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -RSpec/ReceiveMessages: - Exclude: - - 'spec/language_server/host_spec.rb' - RSpec/RemoveConst: Exclude: - 'spec/diagnostics/rubocop_helpers_spec.rb' @@ -278,13 +228,8 @@ Security/MarshalLoad: # Configuration parameters: EnforcedStyle, AllowModifiersOnSymbols, AllowModifiersOnAttrs, AllowModifiersOnAliasMethod. # SupportedStyles: inline, group Style/AccessModifierDeclarations: - Enabled: false - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: always, conditionals -Style/AndOr: - Enabled: false + Exclude: + - 'lib/solargraph/source/chain/literal.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowOnlyRestArgument, UseAnonymousForwarding, RedundantRestArgumentNames, RedundantKeywordRestArgumentNames, RedundantBlockArgumentNames. @@ -295,46 +240,15 @@ Style/ArgumentsForwarding: Exclude: - 'lib/solargraph/complex_type.rb' -# 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/pin/parameter.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/yard_map/mapper.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle, EnforcedStyleForClasses, EnforcedStyleForModules. -# SupportedStyles: nested, compact -# SupportedStylesForClasses: ~, nested, compact -# SupportedStylesForModules: ~, nested, compact -Style/ClassAndModuleChildren: - Enabled: false - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowedMethods, AllowedPatterns. -# AllowedMethods: ==, equal?, eql? -Style/ClassEqualityComparison: - Exclude: - - 'lib/solargraph/gem_pins.rb' - - 'lib/solargraph/pin/base.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowedReceivers. -Style/CollectionCompact: - Exclude: - - 'lib/solargraph/pin/constant.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/CombinableLoops: - Exclude: - - 'lib/solargraph/pin/parameter.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/ConcatArrayLiterals: +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. +# SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces +# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object +# FunctionalMethods: let, let!, subject, watch +# AllowedMethods: lambda, proc, it +Style/BlockDelimiters: Exclude: - - 'lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb' + - 'spec/source/chain_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. @@ -356,79 +270,22 @@ Style/EmptyMethod: - 'spec/fixtures/rdoc-lib/lib/example.rb' - 'spec/fixtures/workspace-with-gemfile/lib/thing.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: left_coerce, right_coerce, single_coerce, fdiv -Style/FloatDivision: - Exclude: - - 'lib/solargraph/library.rb' - - 'lib/solargraph/pin/search.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: always, always_true, never Style/FrozenStringLiteralComment: Enabled: false -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/GlobalStdStream: - Exclude: - - 'lib/solargraph/logging.rb' - - 'lib/solargraph/shell.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: Exclude: - 'lib/solargraph/source_map/clip.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowSplatArgument. -Style/HashConversion: - Exclude: - - 'lib/solargraph/doc_map.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowedReceivers. -# AllowedReceivers: Thread.current -Style/HashEachMethods: - Exclude: - - 'lib/solargraph/library.rb' - - 'lib/solargraph/source.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/IdenticalConditionalBranches: - Exclude: - - 'lib/solargraph/library.rb' - - 'lib/solargraph/type_checker.rb' - # This cop supports safe autocorrection (--autocorrect). Style/IfUnlessModifier: Enabled: false -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: InverseMethods, InverseBlocks. -Style/InverseMethods: - Exclude: - - 'lib/solargraph/source.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/MapIntoArray: - Exclude: - - 'lib/solargraph/diagnostics/update_errors.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/MapToHash: - Exclude: - - 'lib/solargraph/bench.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/MapToSet: - Exclude: - - 'lib/solargraph/library.rb' - - 'spec/source_map/clip_spec.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline @@ -440,17 +297,14 @@ Style/MultilineBlockChain: Exclude: - 'lib/solargraph/pin/search.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: literals, strict -Style/MutableConstant: - Enabled: false - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. # SupportedStyles: predicate, comparison Style/NumericPredicate: - Enabled: false + Exclude: + - 'spec/**/*' + - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' + - 'lib/solargraph/language_server/message/extended/document_gems.rb' Style/OpenStructUse: Exclude: @@ -461,30 +315,12 @@ Style/OpenStructUse: Style/OptionalBooleanParameter: Enabled: false -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: short, verbose -Style/PreferredHashMethods: - Exclude: - - 'lib/solargraph/language_server/message.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Methods. -Style/RedundantArgument: - Exclude: - - 'lib/solargraph/source_map/mapper.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/RedundantInterpolation: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/source_map/mapper.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # AllowedMethods: present?, blank?, presence, try, try! Style/SafeNavigation: - Enabled: false + Exclude: + - 'lib/solargraph/pin/base.rb' # Configuration parameters: Max. Style/SafeNavigationChainLength: @@ -493,12 +329,8 @@ Style/SafeNavigationChainLength: # This cop supports unsafe autocorrection (--autocorrect-all). Style/SlicingWithRange: - Enabled: false - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Mode. -Style/StringConcatenation: - Enabled: false + Exclude: + - 'lib/solargraph/convention/struct_definition/struct_definition_node.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. @@ -506,27 +338,13 @@ Style/StringConcatenation: Style/StringLiterals: Exclude: - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' + - 'spec/source/chain_spec.rb' # This cop supports safe autocorrection (--autocorrect). Style/SuperArguments: Exclude: - 'lib/solargraph/pin/callable.rb' - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/signature.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments. -# AllowedMethods: define_method -Style/SymbolProc: - Enabled: false - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/ZeroLengthPredicate: - 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: EnforcedStylePrototypeName. @@ -534,7 +352,6 @@ Style/ZeroLengthPredicate: YARD/MismatchName: Exclude: - 'lib/solargraph/pin/reference.rb' - - 'lib/solargraph/rbs_map/conversions.rb' YARD/TagTypeSyntax: Enabled: false diff --git a/Gemfile b/Gemfile index 6849d21b5..fafce06ec 100755 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source 'https://rubygems.org' gemspec name: 'solargraph' diff --git a/Rakefile b/Rakefile index 584322291..398957b1a 100755 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rake' require 'bundler/gem_tasks' require 'fileutils' diff --git a/bin/solargraph b/bin/solargraph index 8f47bbda6..cd5341820 100755 --- a/bin/solargraph +++ b/bin/solargraph @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true # turn off warning diagnostics from Ruby $VERBOSE = nil diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 87b96dba4..88e82a692 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -127,13 +127,6 @@ def catalog bench self end - # @todo need to model type def statement in chains as a symbol so - # that this overload of 'protected' will typecheck @sg-ignore - # @sg-ignore - protected def equality_fields - [self.class, @source_map_hash, conventions_environ, @doc_map, @unresolved_requires, @missing_docs, @loose_unions] - end - # @return [DocMap] def doc_map @doc_map ||= DocMap.new([], Workspace.new('.')) @@ -505,7 +498,7 @@ def get_complex_type_methods complex_type, context = '', internal = false result = Set.new complex_type.each do |type| if type.duck_type? - result.add Pin::DuckMethod.new(name: type.to_s[1..-1], source: :api_map) + result.add Pin::DuckMethod.new(name: type.to_s[1..], source: :api_map) result.merge get_methods('Object') else unless type.nil? || type.name == 'void' @@ -1006,5 +999,15 @@ def has_generics? namespace_pin def can_resolve_generics? namespace_pin, rooted_type has_generics?(namespace_pin) && !rooted_type.all_params.empty? end + + protected + + # @todo need to model type def statement in chains as a symbol so + # that this overload of 'protected' will typecheck @sg-ignore + # @sg-ignore + def equality_fields + [self.class, @source_map_hash, conventions_environ, @doc_map, @unresolved_requires, @missing_docs, + @loose_unions] + end end end diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 3d7405e0f..881d048a6 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -168,7 +168,7 @@ def map_overrides # YARD::Docstring#delete_tags: name expected String, # received String, Symbol - delete_tags is ok with a # _ToS, but we should fix anyway - new_pin.docstring.delete_tags tag if new_pin + new_pin&.docstring&.delete_tags tag end ovr.tags.each do |tag| pin.docstring.add_tag(tag) diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index f971d285d..a121a348b 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -14,7 +14,7 @@ module SourceToYard # @return [generic, nil] def code_object_at path, klass = YARD::CodeObjects::Base obj = code_object_map[path] - obj if obj&.is_a?(klass) + obj if obj.is_a?(klass) end # @return [Array] diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index cca195337..29b58bb1a 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -32,7 +32,7 @@ def update *pinsets # @todo Fix this map @fqns_pins_map = nil - return catalog(pinsets) if changed == 0 + return catalog(pinsets) if changed.zero? # @sg-ignore Need to add nil check here pinsets[changed..].each_with_index do |pins, idx| diff --git a/lib/solargraph/bench.rb b/lib/solargraph/bench.rb index ed72c5a82..de50e3df0 100644 --- a/lib/solargraph/bench.rb +++ b/lib/solargraph/bench.rb @@ -32,8 +32,7 @@ def initialize source_maps: [], workspace: Workspace.new, live_map: nil, externa # @return [Hash{String => SourceMap}] def source_map_hash # @todo Work around #to_h bug in current Ruby head (3.5) with #map#to_h - @source_map_hash ||= source_maps.map { |s| [s.filename, s] } - .to_h + @source_map_hash ||= source_maps.to_h { |s| [s.filename, s] } end # @return [Set] diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index b0f834318..c2dcf469b 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -29,10 +29,6 @@ def initialize types = [UniqueType::UNDEFINED] @items = items end - protected def equality_fields - [self.class, items] - end - # @param api_map [ApiMap] # @param gates [Array] # @@ -400,6 +396,10 @@ def intersect_with intersection_type, api_map protected + def equality_fields + [self.class, items] + end + # @return [ComplexType] def reduce_object new_items = items.flat_map do |ut| @@ -457,7 +457,7 @@ def parse *strings, partial: false elsif char == '<' point_stack += 1 elsif char == '>' - if subtype_string.end_with?('=') && curly_stack > 0 + if subtype_string.end_with?('=') && curly_stack.positive? subtype_string += char elsif base.end_with?('=') raise ComplexTypeError, 'Invalid hash thing' unless key_types.nil? @@ -473,7 +473,7 @@ def parse *strings, partial: false subtype_string.clear next else - raise ComplexTypeError, "Invalid close in type #{type_string}" if point_stack == 0 + raise ComplexTypeError, "Invalid close in type #{type_string}" if point_stack.zero? point_stack -= 1 subtype_string += char end @@ -483,23 +483,23 @@ def parse *strings, partial: false elsif char == '}' curly_stack -= 1 subtype_string += char - raise ComplexTypeError, "Invalid close in type #{type_string}" if curly_stack < 0 + raise ComplexTypeError, "Invalid close in type #{type_string}" if curly_stack.negative? next elsif char == '(' paren_stack += 1 elsif char == ')' paren_stack -= 1 subtype_string += char - raise ComplexTypeError, "Invalid close in type #{type_string}" if paren_stack < 0 + raise ComplexTypeError, "Invalid close in type #{type_string}" if paren_stack.negative? next - elsif char == ',' && point_stack == 0 && curly_stack == 0 && paren_stack == 0 + elsif char == ',' && point_stack.zero? && curly_stack.zero? && paren_stack.zero? # types.push ComplexType.new([UniqueType.new(base.strip, subtype_string.strip)]) types.push UniqueType.parse(base.strip, subtype_string.strip) base.clear subtype_string.clear next end - if point_stack == 0 && curly_stack == 0 && paren_stack == 0 + if point_stack.zero? && curly_stack.zero? && paren_stack.zero? base.concat char else subtype_string.concat char diff --git a/lib/solargraph/complex_type/type_methods.rb b/lib/solargraph/complex_type/type_methods.rb index afd73050e..5a1775c50 100644 --- a/lib/solargraph/complex_type/type_methods.rb +++ b/lib/solargraph/complex_type/type_methods.rb @@ -54,11 +54,11 @@ def duck_type? # @return [Boolean] def nil_type? - @nil_type ||= (name.casecmp('nil') == 0) + @nil_type ||= name.casecmp('nil').zero? end def tuple? - @tuple_type ||= (name == 'Tuple') || (name == 'Array' && subtypes.length >= 1 && fixed_parameters?) + @tuple ||= (name == 'Tuple') || (name == 'Array' && subtypes.length >= 1 && fixed_parameters?) end def void? diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index 201dec4f5..067b01082 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -11,10 +11,6 @@ class UniqueType attr_reader :all_params, :subtypes, :key_types - protected def equality_fields - [@name, @all_params, @subtypes, @key_types] - end - # Create a UniqueType with the specified name and an optional substring. # The substring is the parameter section of a parametrized type, e.g., # for the type `Array`, the name is `Array` and the substring is @@ -27,7 +23,7 @@ class UniqueType def self.parse name, substring = '', make_rooted: nil raise ComplexTypeError, "Illegal prefix: #{name}" if name.start_with?(':::') if name.start_with?('::') - name = name[2..-1] + name = name[2..] rooted = true elsif !can_root_name?(name) rooted = true @@ -46,7 +42,7 @@ def self.parse name, substring = '', make_rooted: nil # @sg-ignore Need to add nil check here parameters_type = PARAMETERS_TYPE_BY_STARTING_TAG.fetch(substring[0]) if parameters_type == :hash - unless !subs.is_a?(ComplexType) and subs.length == 2 and !subs[0].is_a?(UniqueType) and !subs[1].is_a?(UniqueType) + unless !subs.is_a?(ComplexType) && (subs.length == 2) && !subs[0].is_a?(UniqueType) && !subs[1].is_a?(UniqueType) raise ComplexTypeError, "Bad hash type: name=#{name}, substring=#{substring}" end @@ -455,7 +451,7 @@ def resolve_generics definitions, context_type idx = definitions.generics.index(generic_name) next t if idx.nil? if context_type.parameters_type == :hash - if idx == 0 + if idx.zero? next ComplexType.new(context_type.key_types) elsif idx == 1 next ComplexType.new(context_type.subtypes) @@ -463,7 +459,7 @@ def resolve_generics definitions, context_type next ComplexType::UNDEFINED end elsif context_type.all?(&:implicit_union?) - if idx == 0 && !context_type.all_params.empty? + if idx.zero? && !context_type.all_params.empty? ComplexType.new(context_type.all_params) else ComplexType::UNDEFINED @@ -636,6 +632,12 @@ def self.can_root_name? name }.freeze include Logging + + protected + + def equality_fields + [@name, @all_params, @subtypes, @key_types] + end end end end diff --git a/lib/solargraph/convention/data_definition/data_definition_node.rb b/lib/solargraph/convention/data_definition/data_definition_node.rb index d14817933..5c4b2276a 100644 --- a/lib/solargraph/convention/data_definition/data_definition_node.rb +++ b/lib/solargraph/convention/data_definition/data_definition_node.rb @@ -85,7 +85,7 @@ def data_node # @return [Array] def data_attribute_nodes # @sg-ignore Need to add nil check here - data_node.children[2..-1] + data_node.children[2..] end end end diff --git a/lib/solargraph/convention/gemfile.rb b/lib/solargraph/convention/gemfile.rb index 8484f6247..bcebe1de9 100644 --- a/lib/solargraph/convention/gemfile.rb +++ b/lib/solargraph/convention/gemfile.rb @@ -5,7 +5,7 @@ module Convention class Gemfile < Base def local source_map return EMPTY_ENVIRON unless File.basename(source_map.filename) == 'Gemfile' - @environ ||= Environ.new( + @local ||= Environ.new( requires: ['bundler'], domains: ['Bundler::Dsl'] ) diff --git a/lib/solargraph/convention/gemspec.rb b/lib/solargraph/convention/gemspec.rb index 66357ecbb..ce4ce78c7 100644 --- a/lib/solargraph/convention/gemspec.rb +++ b/lib/solargraph/convention/gemspec.rb @@ -5,7 +5,7 @@ module Convention class Gemspec < Base def local source_map return Convention::Base::EMPTY_ENVIRON unless File.basename(source_map.filename).end_with?('.gemspec') - @environ ||= Environ.new( + @local ||= Environ.new( requires: ['rubygems'], pins: [ Solargraph::Pin::Reference::Override.from_comment( diff --git a/lib/solargraph/convention/rakefile.rb b/lib/solargraph/convention/rakefile.rb index 17be306ae..c5286bd54 100644 --- a/lib/solargraph/convention/rakefile.rb +++ b/lib/solargraph/convention/rakefile.rb @@ -7,7 +7,7 @@ def local source_map basename = File.basename(source_map.filename) return EMPTY_ENVIRON unless basename.end_with?('.rake') || basename == 'Rakefile' - @environ ||= Environ.new( + @local ||= Environ.new( requires: ['rake'], domains: ['Rake::DSL'] ) diff --git a/lib/solargraph/converters/dd.rb b/lib/solargraph/converters/dd.rb index 98085da56..31c9e4b0f 100644 --- a/lib/solargraph/converters/dd.rb +++ b/lib/solargraph/converters/dd.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'nokogiri' module ReverseMarkdown diff --git a/lib/solargraph/converters/dl.rb b/lib/solargraph/converters/dl.rb index 92785622a..c4401ce87 100644 --- a/lib/solargraph/converters/dl.rb +++ b/lib/solargraph/converters/dl.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ReverseMarkdown module Converters class Dl < Base diff --git a/lib/solargraph/converters/dt.rb b/lib/solargraph/converters/dt.rb index edd420678..c7027e2ae 100644 --- a/lib/solargraph/converters/dt.rb +++ b/lib/solargraph/converters/dt.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ReverseMarkdown module Converters class Dt < Base diff --git a/lib/solargraph/converters/misc.rb b/lib/solargraph/converters/misc.rb index e45b5bc9e..febacb9ee 100644 --- a/lib/solargraph/converters/misc.rb +++ b/lib/solargraph/converters/misc.rb @@ -1 +1,3 @@ +# frozen_string_literal: true + ReverseMarkdown::Converters.register :tt, ReverseMarkdown::Converters::Code.new diff --git a/lib/solargraph/diagnostics/rubocop.rb b/lib/solargraph/diagnostics/rubocop.rb index 23dbbfe63..39b79d9b7 100644 --- a/lib/solargraph/diagnostics/rubocop.rb +++ b/lib/solargraph/diagnostics/rubocop.rb @@ -17,7 +17,7 @@ class Rubocop < Base 'warning' => Severities::WARNING, 'error' => Severities::ERROR, 'fatal' => Severities::ERROR - } + }.freeze # @param source [Solargraph::Source] # @param _api_map [Solargraph::ApiMap] diff --git a/lib/solargraph/diagnostics/rubocop_helpers.rb b/lib/solargraph/diagnostics/rubocop_helpers.rb index 52a205465..e97ca628e 100644 --- a/lib/solargraph/diagnostics/rubocop_helpers.rb +++ b/lib/solargraph/diagnostics/rubocop_helpers.rb @@ -52,7 +52,7 @@ def generate_options filename, code def fix_drive_letter path return path unless path.match(/^[a-z]:/) # @sg-ignore Need to add nil check here - path[0].upcase + path[1..-1] + path[0].upcase + path[1..] end # @todo This is a smelly way to redirect output, but the RuboCop specs do diff --git a/lib/solargraph/diagnostics/update_errors.rb b/lib/solargraph/diagnostics/update_errors.rb index 6da4c5835..c2ca02408 100644 --- a/lib/solargraph/diagnostics/update_errors.rb +++ b/lib/solargraph/diagnostics/update_errors.rb @@ -4,16 +4,12 @@ module Solargraph module Diagnostics class UpdateErrors < Base def diagnose source, api_map - result = [] - combine_ranges(source.code, source.error_ranges).each do |range| - result.push( - range: range.to_hash, + combine_ranges(source.code, source.error_ranges).map do |range| + { range: range.to_hash, severity: Diagnostics::Severities::ERROR, source: 'Solargraph', - message: 'Syntax error' - ) + message: 'Syntax error' } end - result end private diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index f3cdfa94a..2a55f55c0 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -136,9 +136,9 @@ def load_serialized_gem_pins out: @out with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v } # @sg-ignore Need better typing for Hash[] # @type [Array] - missing_paths = Hash[without_gemspecs].keys + missing_paths = without_gemspecs.to_h.keys # @type [Array] - gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies(out: out).to_a + gemspecs = with_gemspecs.to_h.values.flatten.compact + dependencies(out: out).to_a # if we are type checking a gem project, we should not include # pins from rbs or yard from that gem here - we use our own diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index 340746a8f..9be1e8eb7 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -14,7 +14,7 @@ class << self # @param pins [Array] # @return [Array] def self.combine_method_pins_by_path pins - method_pins, alias_pins = pins.partition { |pin| pin.class == Pin::Method } + method_pins, alias_pins = pins.partition { |pin| pin.instance_of?(Pin::Method) } by_path = method_pins.group_by(&:path) by_path.transform_values! do |pins| GemPins.combine_method_pins(*pins) @@ -92,7 +92,7 @@ class << self # @param choices [Array] # @return [ComplexType] def best_return_type *choices - choices.find { |pin| pin.defined? } || choices.first || ComplexType::UNDEFINED + choices.find(&:defined?) || choices.first || ComplexType::UNDEFINED end end end diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index e8b914767..d61283567 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -773,7 +773,7 @@ def check_diff uri, change source = sources.find(uri) 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 + return change if diffs.empty? || diffs.length > 1 || diffs.first.length > 1 # @type [Diff::LCS::Change] diff = diffs.first.first return change unless diff.adding? && ['.', ':', '(', ',', ' '].include?(diff.element) diff --git a/lib/solargraph/language_server/message.rb b/lib/solargraph/language_server/message.rb index ab74c2eb4..170bfdb4f 100644 --- a/lib/solargraph/language_server/message.rb +++ b/lib/solargraph/language_server/message.rb @@ -38,7 +38,7 @@ def register path, message_class # @param path [String] # @return [Class] def select path - if method_map.has_key?(path) + if method_map.key?(path) method_map[path] elsif path.start_with?('$/') MethodNotImplemented diff --git a/lib/solargraph/language_server/message/base.rb b/lib/solargraph/language_server/message/base.rb index cc72d99b5..6b61101c4 100644 --- a/lib/solargraph/language_server/message/base.rb +++ b/lib/solargraph/language_server/message/base.rb @@ -69,7 +69,7 @@ def send_response } response[:result] = result unless result.nil? response[:error] = error unless error.nil? - response[:result] = nil if result.nil? and error.nil? + response[:result] = nil if result.nil? && error.nil? json = response.to_json envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}" Solargraph.logger.debug envelope diff --git a/lib/solargraph/language_server/message/completion_item/resolve.rb b/lib/solargraph/language_server/message/completion_item/resolve.rb index 171ec6a6c..83cc5c1fe 100644 --- a/lib/solargraph/language_server/message/completion_item/resolve.rb +++ b/lib/solargraph/language_server/message/completion_item/resolve.rb @@ -46,7 +46,7 @@ def join_docs pins pins.each do |pin| this_link = host.options['enablePages'] ? pin.link_documentation : pin.text_documentation result.push this_link if this_link && this_link != last_link && this_link != 'undefined' - result.push pin.documentation unless result.last && result.last.end_with?(pin.documentation) + result.push pin.documentation unless result.last&.end_with?(pin.documentation) last_link = this_link end result.join("\n\n") diff --git a/lib/solargraph/language_server/message/text_document/definition.rb b/lib/solargraph/language_server/message/text_document/definition.rb index f88583f48..1bac9b36c 100644 --- a/lib/solargraph/language_server/message/text_document/definition.rb +++ b/lib/solargraph/language_server/message/text_document/definition.rb @@ -1,43 +1,49 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::TextDocument - class Definition < Base - def process - @line = params['position']['line'] - @column = params['position']['character'] - set_result(code_location || require_location || []) - end +module Solargraph + module LanguageServer + module Message + module TextDocument + class Definition < Base + def process + @line = params['position']['line'] + @column = params['position']['character'] + set_result(code_location || require_location || []) + end - private + private - # @return [Array, nil] - def code_location - suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column) - # @sg-ignore Need to add nil check here - return nil if suggestions.empty? - # @sg-ignore Need to add nil check here - suggestions.reject { |pin| pin.best_location.nil? || pin.best_location.filename.nil? }.map do |pin| - { - uri: file_to_uri(pin.best_location.filename), - range: pin.best_location.range.to_hash - } - end - end + # @return [Array, nil] + def code_location + suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column) + # @sg-ignore Need to add nil check here + return nil if suggestions.empty? + # @sg-ignore Need to add nil check here + suggestions.reject { |pin| pin.best_location.nil? || pin.best_location.filename.nil? }.map do |pin| + { + uri: file_to_uri(pin.best_location.filename), + range: pin.best_location.range.to_hash + } + end + end - # @return [Array, nil] - def require_location - # @todo Terrible hack - lib = host.library_for(params['textDocument']['uri']) - rloc = Solargraph::Location.new(uri_to_file(params['textDocument']['uri']), - Solargraph::Range.from_to(@line, @column, @line, @column)) - dloc = lib.locate_ref(rloc) - return nil if dloc.nil? - [ - { - uri: file_to_uri(dloc.filename), - range: dloc.range.to_hash - } - ] + # @return [Array, nil] + def require_location + # @todo Terrible hack + lib = host.library_for(params['textDocument']['uri']) + rloc = Solargraph::Location.new(uri_to_file(params['textDocument']['uri']), + Solargraph::Range.from_to(@line, @column, @line, @column)) + dloc = lib.locate_ref(rloc) + return nil if dloc.nil? + [ + { + uri: file_to_uri(dloc.filename), + range: dloc.range.to_hash + } + ] + end + end + end end end end diff --git a/lib/solargraph/language_server/message/text_document/document_highlight.rb b/lib/solargraph/language_server/message/text_document/document_highlight.rb index a0541762a..e1ecfcb29 100644 --- a/lib/solargraph/language_server/message/text_document/document_highlight.rb +++ b/lib/solargraph/language_server/message/text_document/document_highlight.rb @@ -1,17 +1,23 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::TextDocument - class DocumentHighlight < Base - def process - locs = host.references_from(params['textDocument']['uri'], params['position']['line'], - params['position']['character'], strip: true, only: true) - result = locs.map do |loc| - { - range: loc.range.to_hash, - kind: 1 - } +module Solargraph + module LanguageServer + module Message + module TextDocument + class DocumentHighlight < Base + def process + locs = host.references_from(params['textDocument']['uri'], params['position']['line'], + params['position']['character'], strip: true, only: true) + result = locs.map do |loc| + { + range: loc.range.to_hash, + kind: 1 + } + end + set_result result + end + end end - set_result result end end end diff --git a/lib/solargraph/language_server/message/text_document/document_symbol.rb b/lib/solargraph/language_server/message/text_document/document_symbol.rb index 3d3cccbde..04b706358 100644 --- a/lib/solargraph/language_server/message/text_document/document_symbol.rb +++ b/lib/solargraph/language_server/message/text_document/document_symbol.rb @@ -1,28 +1,36 @@ # frozen_string_literal: true -class Solargraph::LanguageServer::Message::TextDocument::DocumentSymbol < Solargraph::LanguageServer::Message::Base - include Solargraph::LanguageServer::UriHelpers +module Solargraph + module LanguageServer + module Message + module TextDocument + class DocumentSymbol < Solargraph::LanguageServer::Message::Base + include Solargraph::LanguageServer::UriHelpers - def process - pins = host.document_symbols params['textDocument']['uri'] - info = pins.map do |pin| - next nil unless pin.best_location&.filename + def process + pins = host.document_symbols params['textDocument']['uri'] + info = pins.map do |pin| + next nil unless pin.best_location&.filename - result = { - name: pin.name, - containerName: pin.namespace, - kind: pin.symbol_kind, - location: { - # @sg-ignore Need to add nil check here - uri: file_to_uri(pin.best_location.filename), - # @sg-ignore Need to add nil check here - range: pin.best_location.range.to_hash - }, - deprecated: pin.deprecated? - } - result - end.compact + result = { + name: pin.name, + containerName: pin.namespace, + kind: pin.symbol_kind, + location: { + # @sg-ignore Need to add nil check here + uri: file_to_uri(pin.best_location.filename), + # @sg-ignore Need to add nil check here + range: pin.best_location.range.to_hash + }, + deprecated: pin.deprecated? + } + result + end.compact - set_result info + set_result info + end + end + end + end end end diff --git a/lib/solargraph/language_server/message/text_document/hover.rb b/lib/solargraph/language_server/message/text_document/hover.rb index 379403258..6b60969e1 100644 --- a/lib/solargraph/language_server/message/text_document/hover.rb +++ b/lib/solargraph/language_server/message/text_document/hover.rb @@ -42,7 +42,7 @@ def process def contents_or_nil contents stripped = contents .map(&:strip) - .reject { |c| c.empty? } + .reject(&:empty?) return nil if stripped.empty? { contents: { diff --git a/lib/solargraph/language_server/message/text_document/prepare_rename.rb b/lib/solargraph/language_server/message/text_document/prepare_rename.rb index 770d126ad..2f742047a 100644 --- a/lib/solargraph/language_server/message/text_document/prepare_rename.rb +++ b/lib/solargraph/language_server/message/text_document/prepare_rename.rb @@ -1,12 +1,18 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::TextDocument - class PrepareRename < Base - def process - line = params['position']['line'] - col = params['position']['character'] - set_result host.sources.find(params['textDocument']['uri']).cursor_at(Solargraph::Position.new(line, - col)).range.to_hash +module Solargraph + module LanguageServer + module Message + module TextDocument + class PrepareRename < Base + def process + line = params['position']['line'] + col = params['position']['character'] + set_result host.sources.find(params['textDocument']['uri']).cursor_at(Solargraph::Position.new(line, + col)).range.to_hash + end + end + end end end end diff --git a/lib/solargraph/language_server/message/text_document/references.rb b/lib/solargraph/language_server/message/text_document/references.rb index 08ae35c9d..6a894ecd9 100644 --- a/lib/solargraph/language_server/message/text_document/references.rb +++ b/lib/solargraph/language_server/message/text_document/references.rb @@ -1,17 +1,23 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::TextDocument - class References < Base - def process - locs = host.references_from(params['textDocument']['uri'], params['position']['line'], - params['position']['character']) - result = locs.map do |loc| - { - uri: file_to_uri(loc.filename), - range: loc.range.to_hash - } +module Solargraph + module LanguageServer + module Message + module TextDocument + class References < Base + def process + locs = host.references_from(params['textDocument']['uri'], params['position']['line'], + params['position']['character']) + result = locs.map do |loc| + { + uri: file_to_uri(loc.filename), + range: loc.range.to_hash + } + end + set_result result + end + end end - set_result result end end end diff --git a/lib/solargraph/language_server/message/text_document/rename.rb b/lib/solargraph/language_server/message/text_document/rename.rb index 66bdce274..b0aff3c8a 100644 --- a/lib/solargraph/language_server/message/text_document/rename.rb +++ b/lib/solargraph/language_server/message/text_document/rename.rb @@ -1,20 +1,26 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::TextDocument - class Rename < Base - def process - locs = host.references_from(params['textDocument']['uri'], params['position']['line'], - params['position']['character'], strip: true) - changes = {} - locs.each do |loc| - uri = file_to_uri(loc.filename) - changes[uri] ||= [] - changes[uri].push({ - range: loc.range.to_hash, - newText: params['newName'] - }) +module Solargraph + module LanguageServer + module Message + module TextDocument + class Rename < Base + def process + locs = host.references_from(params['textDocument']['uri'], params['position']['line'], + params['position']['character'], strip: true) + changes = {} + locs.each do |loc| + uri = file_to_uri(loc.filename) + changes[uri] ||= [] + changes[uri].push({ + range: loc.range.to_hash, + newText: params['newName'] + }) + end + set_result changes: changes + end + end end - set_result changes: changes end end end diff --git a/lib/solargraph/language_server/message/text_document/signature_help.rb b/lib/solargraph/language_server/message/text_document/signature_help.rb index 5a460b205..675daaebe 100644 --- a/lib/solargraph/language_server/message/text_document/signature_help.rb +++ b/lib/solargraph/language_server/message/text_document/signature_help.rb @@ -10,7 +10,7 @@ def process col = params['position']['character'] suggestions = host.signatures_at(params['textDocument']['uri'], line, col) set_result({ - signatures: suggestions.flat_map { |pin| pin.signature_help } + signatures: suggestions.flat_map(&:signature_help) }) rescue FileNotFoundError => e Logging.logger.warn "[#{e.class}] #{e.message}" 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 3a2431659..1035869c9 100644 --- a/lib/solargraph/language_server/message/text_document/type_definition.rb +++ b/lib/solargraph/language_server/message/text_document/type_definition.rb @@ -1,26 +1,32 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::TextDocument - class TypeDefinition < Base - def process - @line = params['position']['line'] - @column = params['position']['character'] - set_result(code_location || []) - end +module Solargraph + module LanguageServer + module Message + module TextDocument + class TypeDefinition < Base + def process + @line = params['position']['line'] + @column = params['position']['character'] + set_result(code_location || []) + end - private + private - # @return [Array, nil] - def code_location - suggestions = host.type_definitions_at(params['textDocument']['uri'], @line, @column) - # @sg-ignore Need to add nil check here - return nil if suggestions.empty? - # @sg-ignore Need to add nil check here - suggestions.reject { |pin| pin.best_location.nil? || pin.best_location.filename.nil? }.map do |pin| - { - uri: file_to_uri(pin.best_location.filename), - range: pin.best_location.range.to_hash - } + # @return [Array, nil] + def code_location + suggestions = host.type_definitions_at(params['textDocument']['uri'], @line, @column) + # @sg-ignore Need to add nil check here + return nil if suggestions.empty? + # @sg-ignore Need to add nil check here + suggestions.reject { |pin| pin.best_location.nil? || pin.best_location.filename.nil? }.map do |pin| + { + uri: file_to_uri(pin.best_location.filename), + range: pin.best_location.range.to_hash + } + end + end + end end end end diff --git a/lib/solargraph/language_server/message/workspace/did_change_configuration.rb b/lib/solargraph/language_server/message/workspace/did_change_configuration.rb index 5a15ab0a5..2c9bd8d1c 100644 --- a/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +++ b/lib/solargraph/language_server/message/workspace/did_change_configuration.rb @@ -1,35 +1,41 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::Workspace - class DidChangeConfiguration < Solargraph::LanguageServer::Message::Base - def process - return unless params['settings'] - update = params['settings']['solargraph'] - host.configure update - register_from_options - end +module Solargraph + module LanguageServer + module Message + module Workspace + class DidChangeConfiguration < Solargraph::LanguageServer::Message::Base + def process + return unless params['settings'] + update = params['settings']['solargraph'] + host.configure update + register_from_options + end - private + private - # @return [void] - def register_from_options - Solargraph.logger.debug "Registering capabilities from options: #{host.options.inspect}" - # @type [Array] - y = [] - # @type [Array] - n = [] - (host.options['completion'] ? y : n).push('textDocument/completion') - (host.options['hover'] ? y : n).push('textDocument/hover', 'textDocument/signatureHelp') - (host.options['autoformat'] ? y : n).push('textDocument/onTypeFormatting') - (host.options['formatting'] ? y : n).push('textDocument/formatting') - (host.options['symbols'] ? y : n).push('textDocument/documentSymbol', 'workspace/symbol') - (host.options['definitions'] ? y : n).push('textDocument/definition') - (host.options['typeDefinitions'] ? y : n).push('textDocument/typeDefinition') - (host.options['references'] ? y : n).push('textDocument/references') - (host.options['folding'] ? y : n).push('textDocument/folding') - (host.options['highlights'] ? y : n).push('textDocument/documentHighlight') - host.register_capabilities y - host.unregister_capabilities n + # @return [void] + def register_from_options + Solargraph.logger.debug "Registering capabilities from options: #{host.options.inspect}" + # @type [Array] + y = [] + # @type [Array] + n = [] + (host.options['completion'] ? y : n).push('textDocument/completion') + (host.options['hover'] ? y : n).push('textDocument/hover', 'textDocument/signatureHelp') + (host.options['autoformat'] ? y : n).push('textDocument/onTypeFormatting') + (host.options['formatting'] ? y : n).push('textDocument/formatting') + (host.options['symbols'] ? y : n).push('textDocument/documentSymbol', 'workspace/symbol') + (host.options['definitions'] ? y : n).push('textDocument/definition') + (host.options['typeDefinitions'] ? y : n).push('textDocument/typeDefinition') + (host.options['references'] ? y : n).push('textDocument/references') + (host.options['folding'] ? y : n).push('textDocument/folding') + (host.options['highlights'] ? y : n).push('textDocument/documentHighlight') + host.register_capabilities y + host.unregister_capabilities n + end + end + end end end end diff --git a/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb b/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb index 133cf883d..d44c24097 100644 --- a/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +++ b/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb @@ -1,41 +1,48 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::Workspace - class DidChangeWatchedFiles < Solargraph::LanguageServer::Message::Base - CREATED = 1 - CHANGED = 2 - DELETED = 3 +module Solargraph + module LanguageServer + module Message + module Workspace + class DidChangeWatchedFiles < Solargraph::LanguageServer::Message::Base + CREATED = 1 + CHANGED = 2 + DELETED = 3 - include Solargraph::LanguageServer::UriHelpers + include Solargraph::LanguageServer::UriHelpers - def process - need_catalog = false - to_create = [] - to_delete = [] + def process + need_catalog = false + to_create = [] + to_delete = [] - # @param change [Hash] - params['changes'].each do |change| - if change['type'] == CREATED - to_create << change['uri'] - need_catalog = true - elsif change['type'] == CHANGED - next if host.open?(change['uri']) - to_create << change['uri'] - need_catalog = true - elsif change['type'] == DELETED - to_delete << change['uri'] - need_catalog = true - else - set_error Solargraph::LanguageServer::ErrorCodes::INVALID_PARAMS, - "Unknown change type ##{change['type']} for #{uri_to_file(change['uri'])}" - end - end + # @param change [Hash] + params['changes'].each do |change| + case change['type'] + when CREATED + to_create << change['uri'] + need_catalog = true + when CHANGED + next if host.open?(change['uri']) + to_create << change['uri'] + need_catalog = true + when DELETED + to_delete << change['uri'] + need_catalog = true + else + set_error Solargraph::LanguageServer::ErrorCodes::INVALID_PARAMS, + "Unknown change type ##{change['type']} for #{uri_to_file(change['uri'])}" + end + end - host.create(*to_create) - host.delete(*to_delete) + host.create(*to_create) + host.delete(*to_delete) - # Force host to catalog libraries after file changes (see castwide/solargraph#139) - host.catalog if need_catalog + # Force host to catalog libraries after file changes (see castwide/solargraph#139) + host.catalog if need_catalog + end + end + end end end end 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 8f93c02e7..7ba16b66c 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 @@ -1,25 +1,31 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::Workspace - class DidChangeWorkspaceFolders < Solargraph::LanguageServer::Message::Base - def process - add_folders - remove_folders - end +module Solargraph + module LanguageServer + module Message + module Workspace + class DidChangeWorkspaceFolders < Solargraph::LanguageServer::Message::Base + def process + add_folders + remove_folders + end - private + private - # @return [void] - def add_folders - return unless params['event'] && params['event']['added'] - host.prepare_folders params['event']['added'] - end + # @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| - host.remove_folders params['event']['removed'] + # @return [void] + def remove_folders + return unless params['event'] && params['event']['removed'] + params['event']['removed'].each do |_folder| + host.remove_folders params['event']['removed'] + end + end + end end end end diff --git a/lib/solargraph/language_server/message/workspace/workspace_symbol.rb b/lib/solargraph/language_server/message/workspace/workspace_symbol.rb index fe7e5efa5..8e8c884a7 100644 --- a/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +++ b/lib/solargraph/language_server/message/workspace/workspace_symbol.rb @@ -1,25 +1,33 @@ # frozen_string_literal: true -class Solargraph::LanguageServer::Message::Workspace::WorkspaceSymbol < Solargraph::LanguageServer::Message::Base - include Solargraph::LanguageServer::UriHelpers +module Solargraph + module LanguageServer + module Message + module Workspace + class WorkspaceSymbol < Solargraph::LanguageServer::Message::Base + include Solargraph::LanguageServer::UriHelpers - def process - pins = host.query_symbols(params['query']) - info = pins.map do |pin| - # @sg-ignore Need to add nil check here - uri = file_to_uri(pin.best_location.filename) - { - name: pin.path, - containerName: pin.namespace, - kind: pin.symbol_kind, - location: { - uri: uri, - # @sg-ignore Need to add nil check here - range: pin.best_location.range.to_hash - }, - deprecated: pin.deprecated? - } + def process + pins = host.query_symbols(params['query']) + info = pins.map do |pin| + # @sg-ignore Need to add nil check here + uri = file_to_uri(pin.best_location.filename) + { + name: pin.path, + containerName: pin.namespace, + kind: pin.symbol_kind, + location: { + uri: uri, + # @sg-ignore Need to add nil check here + range: pin.best_location.range.to_hash + }, + deprecated: pin.deprecated? + } + end + set_result info + end + end + end end - set_result info end end diff --git a/lib/solargraph/language_server/request.rb b/lib/solargraph/language_server/request.rb index 112ce64b2..e4ecc1e97 100644 --- a/lib/solargraph/language_server/request.rb +++ b/lib/solargraph/language_server/request.rb @@ -15,7 +15,7 @@ def initialize id, &block # @yieldreturn [generic] # @return [generic, nil] def process result - @block.call(result) unless @block.nil? + @block&.call(result) end # @return [void] diff --git a/lib/solargraph/language_server/transport/data_reader.rb b/lib/solargraph/language_server/transport/data_reader.rb index c24118f03..5145e41e6 100644 --- a/lib/solargraph/language_server/transport/data_reader.rb +++ b/lib/solargraph/language_server/transport/data_reader.rb @@ -57,7 +57,7 @@ def prepare_to_parse_message # @return [void] def parse_message_from_buffer msg = JSON.parse(@buffer) - @message_handler.call msg unless @message_handler.nil? + @message_handler&.call msg rescue JSON::ParserError => e Solargraph::Logging.logger.warn "Failed to parse request: #{e.message}" Solargraph::Logging.logger.debug "Buffer: #{@buffer}" diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 85d20a465..7e5b3623a 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -195,10 +195,10 @@ def definitions_at filename, line, column offset = Solargraph::Position.to_offset(source.code, Solargraph::Position.new(line, column)) # @sg-ignore Need to add nil check here # @type [MatchData, nil] - lft = source.code[0..offset - 1].match(/\[[a-z0-9_:<, ]*?([a-z0-9_:]*)\z/i) + lft = source.code[0..(offset - 1)].match(/\[[a-z0-9_:<, ]*?([a-z0-9_:]*)\z/i) # @sg-ignore Need to add nil check here # @type [MatchData, nil] - rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i) + rgt = source.code[offset..].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i) if lft && rgt # @sg-ignore Need to add nil check here tag = (lft[1] + rgt[1]).sub(/:+$/, '') @@ -291,7 +291,7 @@ def references_from filename, line, column, strip: false, only: false end end # HACK: for language clients that exclude special characters from the start of variable names - if strip && match = cursor.word.match(/^[^a-z0-9_]+/i) + if strip && (match = cursor.word.match(/^[^a-z0-9_]+/i)) found.map! do |loc| Solargraph::Location.new(loc.filename, # @sg-ignore flow sensitive typing needs to handle if foo = bar @@ -543,7 +543,7 @@ def source_map_external_require_hash # @return [void] def find_external_requires source_map # @type [Set] - new_set = source_map.requires.map(&:name).to_set + new_set = source_map.requires.to_set(&:name) # return if new_set == source_map_external_require_hash[source_map.filename] _filenames = nil filenames = -> { _filenames ||= workspace.filenames.to_set } @@ -679,13 +679,11 @@ def report_cache_progress gem_name, pending 0 else # @sg-ignore flow sensitive typing needs better handling of ||= on lvars - ((finished.to_f / @total.to_f) * 100).to_i + ((finished.to_f / @total) * 100).to_i end - message = "#{gem_name}#{" (+#{pending})" if pending > 0}" + message = "#{gem_name}#{" (+#{pending})" if pending.positive?}" # " - if @cache_progress - @cache_progress.report(message, pct) - else + unless @cache_progress @cache_progress = LanguageServer::Progress.new('Caching gem') # If we don't send both a begin and a report, the progress notification # might get stuck in the status bar forever @@ -694,8 +692,8 @@ def report_cache_progress gem_name, pending changed notify_observers @cache_progress # @sg-ignore flow sensitive typing should be able to handle redefinition - @cache_progress.report(message, pct) end + @cache_progress.report(message, pct) changed notify_observers @cache_progress end @@ -710,11 +708,11 @@ def end_cache_progress # @return [void] def sync_catalog - return if @sync_count == 0 + return if @sync_count.zero? mutex.synchronize do logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}" - source_map_hash.values.each { |map| find_external_requires(map) } + source_map_hash.each_value { |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_gemspecs.length} uncached gemspecs" diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index a7eaf9e6b..2317c3cb4 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -23,10 +23,6 @@ def initialize filename, range @range = range end - protected def equality_fields - [filename, range] - end - # @param other [self] def <=> other return nil unless other.is_a?(Location) @@ -79,5 +75,11 @@ def == other def inspect "#<#{self.class} #{filename}, #{range.inspect}>" end + + protected + + def equality_fields + [filename, range] + end end end diff --git a/lib/solargraph/logging.rb b/lib/solargraph/logging.rb index 60ca51f17..d472438e2 100644 --- a/lib/solargraph/logging.rb +++ b/lib/solargraph/logging.rb @@ -10,7 +10,7 @@ module Logging 'warn' => Logger::WARN, 'info' => Logger::INFO, 'debug' => Logger::DEBUG - } + }.freeze configured_level = ENV.fetch('SOLARGRAPH_LOG', nil) level = if LOG_LEVELS.keys.include?(configured_level) LOG_LEVELS.fetch(configured_level) @@ -21,7 +21,7 @@ module Logging end DEFAULT_LOG_LEVEL end - @@logger = Logger.new(STDERR, level: level) + @@logger = Logger.new($stderr, level: level) # @sg-ignore Fix cvar issue @@logger.formatter = proc do |severity, _datetime, _progname, msg| "[#{severity}] #{msg}\n" @@ -45,7 +45,7 @@ def logger @@logger else new_log_level = LOG_LEVELS[log_level.to_s] - logger = Logger.new(STDERR, level: new_log_level) + logger = Logger.new($stderr, level: new_log_level) # @sg-ignore Wrong argument type for Logger#formatter=: arg_0 # expected nil, received Logger::_Formatter, nil diff --git a/lib/solargraph/page.rb b/lib/solargraph/page.rb index 12782da90..207b84665 100644 --- a/lib/solargraph/page.rb +++ b/lib/solargraph/page.rb @@ -49,7 +49,7 @@ def ruby_to_html code # @param directory [String] def initialize directory = VIEWS_PATH - directory = VIEWS_PATH if directory.nil? or !File.directory?(directory) + directory = VIEWS_PATH if directory.nil? || !File.directory?(directory) directories = [directory] directories.push VIEWS_PATH if directory != VIEWS_PATH # @type [Proc] diff --git a/lib/solargraph/parser.rb b/lib/solargraph/parser.rb index 1f02befe7..7479f4fcc 100644 --- a/lib/solargraph/parser.rb +++ b/lib/solargraph/parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Parser autoload :CommentRipper, 'solargraph/parser/comment_ripper' diff --git a/lib/solargraph/parser/comment_ripper.rb b/lib/solargraph/parser/comment_ripper.rb index 41e68e744..89d4a2c91 100644 --- a/lib/solargraph/parser/comment_ripper.rb +++ b/lib/solargraph/parser/comment_ripper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'ripper' module Solargraph @@ -26,8 +28,8 @@ def on_comment *args # @sg-ignore Need to add nil check here if @buffer_lines[result[2][0]][0..result[2][1]].strip =~ /^#/ chomped = result[1].chomp - if result[2][0] == 0 && chomped.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, - replace: '').match(/^#\s*frozen_string_literal:/) + if result[2][0].zero? && chomped.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, + replace: '').match(/^#\s*frozen_string_literal:/) chomped = '#' end @comments[result[2][0]] = diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index 26fb646b4..32e4866d1 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Parser class FlowSensitiveTyping diff --git a/lib/solargraph/parser/parser_gem.rb b/lib/solargraph/parser/parser_gem.rb index 857e17768..3aca3804f 100644 --- a/lib/solargraph/parser/parser_gem.rb +++ b/lib/solargraph/parser/parser_gem.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Parser module ParserGem diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index 5803424d0..be6e287cf 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -63,10 +63,9 @@ def generate_links n result.concat generate_links(n.children[0]) result.push Chain::Call.new(n.children[1].to_s, Location.from_node(n), node_args(n), passed_block(n)) elsif n.children[0].nil? - args = [] # @sg-ignore Need to add nil check here - n.children[2..-1].each do |c| - args.push NodeChainer.chain(c, @filename, n) + n.children[2..].map do |c| + NodeChainer.chain(c, @filename, n) end result.push Chain::Call.new(n.children[1].to_s, Location.from_node(n), node_args(n), passed_block(n)) else @@ -142,7 +141,7 @@ def generate_links n # added in Ruby 3.1 - https://bugs.ruby-lang.org/issues/11256 result.push Chain::BlockVariable.new(nil) elsif block_variable_name_node.type == :sym - result.push Chain::BlockSymbol.new("#{block_variable_name_node.children[0]}") + result.push Chain::BlockSymbol.new(block_variable_name_node.children[0].to_s) else result.push Chain::BlockVariable.new("&#{block_variable_name_node.children[0]}") end @@ -182,7 +181,7 @@ def passed_block node # @return [Array] def node_args node # @sg-ignore Need to add nil check here - node.children[2..-1].map do |child| + node.children[2..].map do |child| NodeChainer.chain(child, @filename, node) end end diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index f026055db..3cbed9547 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -164,15 +164,15 @@ def call_nodes_from node result.push node if Parser.is_ast_node?(node.children[0]) && node.children[0].children.length > 2 # @sg-ignore Need to add nil check here - node.children[0].children[2..-1].each { |child| result.concat call_nodes_from(child) } + node.children[0].children[2..].each { |child| result.concat call_nodes_from(child) } end # @sg-ignore Need to add nil check here - node.children[1..-1].each { |child| result.concat call_nodes_from(child) } + node.children[1..].each { |child| result.concat call_nodes_from(child) } elsif node.type == :send result.push node result.concat call_nodes_from(node.children.first) # @sg-ignore Need to add nil check here - node.children[2..-1].each { |child| result.concat call_nodes_from(child) } + node.children[2..].each { |child| result.concat call_nodes_from(child) } elsif %i[super zsuper].include?(node.type) result.push node node.children.each { |child| result.concat call_nodes_from(child) } @@ -219,7 +219,7 @@ def find_recipient_node cursor offset = cursor.offset tree = if source.synchronized? # @sg-ignore Need to add nil check here - match = source.code[0..offset - 1].match(/,\s*\z/) + match = source.code[0..(offset - 1)].match(/,\s*\z/) if match # @sg-ignore Need to add nil check here source.tree_at(position.line, position.column - match[0].length) @@ -233,14 +233,14 @@ def find_recipient_node cursor prev = nil tree.each do |node| if node.type == :send - args = node.children[2..-1] + args = node.children[2..] # @sg-ignore Need to add nil check here if !args.empty? # @sg-ignore Need to add nil check here return node if prev && args.include?(prev) elsif source.synchronized? - return node if source.code[0..offset - 1] =~ /\(\s*\z/ && source.code[offset..-1] =~ /^\s*\)/ - elsif source.code[0..offset - 1] =~ /\([^(]*\z/ + return node if source.code[0..(offset - 1)] =~ /\(\s*\z/ && source.code[offset..] =~ /^\s*\)/ + elsif source.code[0..(offset - 1)] =~ /\([^(]*\z/ return node end end @@ -311,13 +311,13 @@ def repaired_find_recipient_node cursor # statements in value positions. module DeepInference class << self - CONDITIONAL_ALL_BUT_FIRST = %i[if unless] - ONLY_ONE_CHILD = [:return] - FIRST_TWO_CHILDREN = [:rescue] - COMPOUND_STATEMENTS = %i[begin kwbegin] - SKIPPABLE = %i[def defs class sclass module] - FUNCTION_VALUE = [:block] - CASE_STATEMENT = [:case] + CONDITIONAL_ALL_BUT_FIRST = %i[if unless].freeze + ONLY_ONE_CHILD = [:return].freeze + FIRST_TWO_CHILDREN = [:rescue].freeze + COMPOUND_STATEMENTS = %i[begin kwbegin].freeze + SKIPPABLE = %i[def defs class sclass module].freeze + FUNCTION_VALUE = [:block].freeze + CASE_STATEMENT = [:case].freeze # @param node [AST::Node] a method body compound statement # @return [Array] low-level value nodes from @@ -354,7 +354,7 @@ def from_value_position_statement node, include_explicit_returns: true result.concat from_value_position_compound_statement node elsif CONDITIONAL_ALL_BUT_FIRST.include?(node.type) # @sg-ignore Need to add nil check here - result.concat reduce_to_value_nodes(node.children[1..-1]) + result.concat reduce_to_value_nodes(node.children[1..]) # result.push NIL_NODE unless node.children[2] elsif ONLY_ONE_CHILD.include?(node.type) result.concat reduce_to_value_nodes([node.children[0]]) @@ -371,7 +371,7 @@ def from_value_position_statement node, include_explicit_returns: true end elsif CASE_STATEMENT.include?(node.type) # @sg-ignore Need to add nil check here - node.children[1..-1].each do |cc| + node.children[1..].each do |cc| if cc.nil? result.push NIL_NODE elsif cc.type == :when @@ -476,7 +476,7 @@ def reduce_to_value_nodes nodes # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check elsif CONDITIONAL_ALL_BUT_FIRST.include?(node.type) # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check - result.concat reduce_to_value_nodes(node.children[1..-1]) + result.concat reduce_to_value_nodes(node.children[1..]) # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check elsif node.type == :return # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check 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 2c8ef1c7b..2d3d967cc 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb @@ -24,7 +24,7 @@ def process if sclass.children[0].nil? && names.last != sclass.children[1].to_s names << sclass.children[1].to_s else - names.concat [NodeMethods.unpack_name(sclass.children[0]), sclass.children[1].to_s] + names.push NodeMethods.unpack_name(sclass.children[0]), sclass.children[1].to_s end name = names.reject(&:empty?).join('::') closure = Solargraph::Pin::Namespace.new(name: name, location: region.closure.location, source: :parser) 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 81404fcbf..a9e60cb65 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -59,7 +59,7 @@ def process def process_visibility if node.children.length > 2 # @sg-ignore Need to add nil check here - node.children[2..-1].each do |child| + node.children[2..].each do |child| # @sg-ignore Variable type could not be inferred for method_name # @type [Symbol] visibility = node.children[1] @@ -90,7 +90,7 @@ def process_visibility # @return [void] def process_attribute # @sg-ignore Need to add nil check here - node.children[2..-1].each do |a| + node.children[2..].each do |a| loc = get_node_location(node) clos = region.closure cmnt = comments_for(node) @@ -132,7 +132,7 @@ def process_include return unless node.children[2].is_a?(AST::Node) && node.children[2].type == :const cp = region.closure # @sg-ignore Need to add nil check here - node.children[2..-1].each do |i| + node.children[2..].each do |i| type = region.scope == :class ? Pin::Reference::Extend : Pin::Reference::Include pins.push type.new( location: get_node_location(i), @@ -148,7 +148,7 @@ def process_prepend return unless node.children[2].is_a?(AST::Node) && node.children[2].type == :const cp = region.closure # @sg-ignore Need to add nil check here - node.children[2..-1].each do |i| + node.children[2..].each do |i| pins.push Pin::Reference::Prepend.new( location: get_node_location(i), closure: cp, @@ -161,7 +161,7 @@ def process_prepend # @return [void] def process_extend # @sg-ignore Need to add nil check here - node.children[2..-1].each do |i| + node.children[2..].each do |i| loc = get_node_location(node) if i.type == :self pins.push Pin::Reference::Extend.new( @@ -202,7 +202,7 @@ def process_module_function region.instance_variable_set(:@visibility, :module_function) elsif %i[sym str].include?(node.children[2].type) # @sg-ignore Need to add nil check here - node.children[2..-1].each do |x| + node.children[2..].each do |x| cn = x.children[0].to_s # @type [Pin::Method, nil] ref = pins.find { |p| p.is_a?(Pin::Method) && p.namespace == region.closure.full_context.namespace && p.name == cn } @@ -265,7 +265,7 @@ def process_private_constant Solargraph::Pin::Constant].include?(p.class) && p.namespace == region.closure.full_context.namespace && p.name == cn end.first # HACK: Smelly instance variable access - ref.instance_variable_set(:@visibility, :private) unless ref.nil? + ref&.instance_variable_set(:@visibility, :private) end # @return [void] @@ -288,7 +288,7 @@ def process_private_class_method p.is_a?(Pin::Method) && p.namespace == region.closure.full_context.namespace && p.name == node.children[2].children[0].to_s end.first # HACK: Smelly instance variable access - ref.instance_variable_set(:@visibility, :private) unless ref.nil? + ref&.instance_variable_set(:@visibility, :private) false else process_children region.update(scope: :class, visibility: :private) diff --git a/lib/solargraph/parser/snippet.rb b/lib/solargraph/parser/snippet.rb index 1ea6bd6d9..7282a44d6 100644 --- a/lib/solargraph/parser/snippet.rb +++ b/lib/solargraph/parser/snippet.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Parser class Snippet diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index 49a34728a..240d46b44 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -195,11 +195,6 @@ def needs_consistent_name? true end - # @sg-ignore def should infer as symbol - "Not enough arguments to Module#protected" - protected def equality_fields - [name, location, type_location, closure, source] - end - # @param other [self] # @return [ComplexType] def combine_return_type other @@ -217,7 +212,7 @@ 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? do |item| + if all_items.any?(&:selfy?) && all_items.any? do |item| item.rooted_tag == context.reduce_class_type.rooted_tag end # assume this was a declaration that should have said 'self' @@ -442,7 +437,7 @@ def transform_types &transform # @param context_type [ComplexType] The receiver type # @return [self] def resolve_generics definitions, context_type - transform_types { |t| t.resolve_generics(definitions, context_type) if t } + transform_types { |t| t&.resolve_generics(definitions, context_type) } end def all_rooted? @@ -494,7 +489,7 @@ def best_location # @param other [Solargraph::Pin::Base, Object] # @return [Boolean] def nearly? other - self.class == other.class && + instance_of?(other.class) && # @sg-ignore Translate to something flow sensitive typing understands name == other.name && # @sg-ignore flow sensitive typing needs to handle attrs @@ -659,7 +654,7 @@ def type_desc rbs = return_type.rooted_tags if return_type.name == 'Class' if path if rbs - path + ' ' + rbs + "#{path} #{rbs}" else path end @@ -700,6 +695,11 @@ def all_location_text protected + # @sg-ignore def should infer as symbol - "Not enough arguments to Module#protected" + def equality_fields + [name, location, type_location, closure, source] + end + # @return [Boolean] attr_writer :probed @@ -757,7 +757,7 @@ def compare_directives dir1, dir2 # @param tag2 [YARD::Tags::Tag] # @return [Boolean] def compare_tags tag1, tag2 - tag1.class == tag2.class && + tag1.instance_of?(tag2.class) && tag1.tag_name == tag2.tag_name && tag1.text == tag2.text && tag1.name == tag2.name && diff --git a/lib/solargraph/pin/breakable.rb b/lib/solargraph/pin/breakable.rb index 7cf6df9ab..f5e4bfd4a 100644 --- a/lib/solargraph/pin/breakable.rb +++ b/lib/solargraph/pin/breakable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Pin # Mix-in for pins which enclose code which the 'break' statement diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 4b3b238dd..2ad4bbf62 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -256,14 +256,11 @@ def mandatory_positional_param_count # @return [String] def parameters_to_rbs - rbs_generics + '(' + parameters.map { |param| - param.to_rbs - # @sg-ignore Need to add nil check here - }.join(', ') + ') ' + (block.nil? ? '' : '{ ' + block.to_rbs + ' } ') + "#{rbs_generics}(#{parameters.map(&:to_rbs).join(', ')}) #{"{ #{block.to_rbs} } " unless block.nil?}" end def to_rbs - parameters_to_rbs + '-> ' + (return_type&.to_rbs || 'untyped') + "#{parameters_to_rbs}-> #{return_type&.to_rbs || 'untyped'}" end def block? diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index b01d760df..4b5738cfc 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -62,7 +62,7 @@ def to_rbs def rbs_generics return '' if generics.empty? - '[' + generics.map { |gen| gen.to_s }.join(', ') + '] ' + "[#{generics.map(&:to_s).join(', ')}] " end end end diff --git a/lib/solargraph/pin/compound_statement.rb b/lib/solargraph/pin/compound_statement.rb index 415996642..39d9cf2d5 100644 --- a/lib/solargraph/pin/compound_statement.rb +++ b/lib/solargraph/pin/compound_statement.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Pin # A series of statements where if a given statement executes, /all diff --git a/lib/solargraph/pin/constant.rb b/lib/solargraph/pin/constant.rb index 8da79f682..a7696fd1f 100644 --- a/lib/solargraph/pin/constant.rb +++ b/lib/solargraph/pin/constant.rb @@ -33,8 +33,8 @@ def path # @return [ComplexType] def generate_complex_type - tags = docstring.tags(:return).map(&:types).flatten.reject(&:nil?) - tags = docstring.tags(:type).map(&:types).flatten.reject(&:nil?) if tags.empty? + tags = docstring.tags(:return).map(&:types).flatten.compact + tags = docstring.tags(:type).map(&:types).flatten.compact if tags.empty? return ComplexType::UNDEFINED if tags.empty? ComplexType.try_parse(*tags) end diff --git a/lib/solargraph/pin/conversions.rb b/lib/solargraph/pin/conversions.rb index 25d585ea0..0d45741d4 100644 --- a/lib/solargraph/pin/conversions.rb +++ b/lib/solargraph/pin/conversions.rb @@ -44,7 +44,7 @@ def completion_item path: path, return_type: return_type.tag, # @sg-ignore flow sensitive typing needs to handle attrs - location: (location ? location.to_hash : nil), + location: location&.to_hash, deprecated: deprecated? } } diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index 83273705f..7e17f5042 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -115,7 +115,7 @@ def resolve_method api_map def print_chain chain out = +'' chain.links.each_with_index do |link, index| - if index > 0 + if index.positive? if Source::Chain::Constant out << '::' unless link.word.start_with?('::') else diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 580775bd7..077da21be 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -6,7 +6,7 @@ class LocalVariable < BaseVariable # @param api_map [ApiMap] # @return [ComplexType, ComplexType::UniqueType] def probe api_map - if presence_certain? && return_type && return_type&.defined? + if presence_certain? && return_type&.defined? # flow sensitive typing has already figured out this type # has been downcast - use the type it figured out # @sg-ignore flow sensitive typing should support ivars @@ -24,7 +24,7 @@ def combine_with other, attrs = {} end def to_rbs - (name || '(anon)') + ' ' + (return_type&.to_rbs || 'untyped') + "#{name || '(anon)'} #{return_type&.to_rbs || 'untyped'}" end end end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index aa363b844..10f91e07d 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -54,7 +54,7 @@ def combine_with other, attrs = {} return priority_choice unless priority_choice.nil? sigs = combine_signatures(other) - parameters = if sigs.length > 0 + parameters = if sigs.length.positive? [].freeze else choose(other, :parameters).clone.freeze @@ -172,7 +172,7 @@ def generate_signature parameters, return_type name: name, decl: decl, # @sg-ignore flow sensitive typing needs to handle attrs - presence: location ? location.range : nil, + presence: location&.range, return_type: ComplexType.try_parse(*p.types), source: source ) @@ -238,7 +238,7 @@ def detail def signature_help @signature_help ||= signatures.map do |sig| { - label: name + '(' + sig.parameters.map(&:full).join(', ') + ')', + label: "#{name}(#{sig.parameters.map(&:full).join(', ')})", documentation: documentation } end @@ -302,26 +302,26 @@ def documentation if @documentation.nil? method_docs ||= super || '' param_tags = docstring.tags(:param) - unless param_tags.nil? or param_tags.empty? + unless param_tags.nil? || param_tags.empty? method_docs += "\n\n" unless method_docs.empty? method_docs += "Params:\n" lines = [] param_tags.each do |p| l = "* #{p.name}" - l += " [#{escape_brackets(p.types.join(', '))}]" unless p.types.nil? or p.types.empty? + l += " [#{escape_brackets(p.types.join(', '))}]" unless p.types.nil? || p.types.empty? l += " #{p.text}" lines.push l end method_docs += lines.join("\n") end yieldparam_tags = docstring.tags(:yieldparam) - unless yieldparam_tags.nil? or yieldparam_tags.empty? + unless yieldparam_tags.nil? || yieldparam_tags.empty? method_docs += "\n\n" unless method_docs.empty? method_docs += "Block Params:\n" lines = [] yieldparam_tags.each do |p| l = "* #{p.name}" - l += " [#{escape_brackets(p.types.join(', '))}]" unless p.types.nil? or p.types.empty? + l += " [#{escape_brackets(p.types.join(', '))}]" unless p.types.nil? || p.types.empty? l += " #{p.text}" lines.push l end @@ -334,7 +334,7 @@ def documentation lines = [] yieldreturn_tags.each do |r| l = '*' - l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? or r.types.empty? + l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? || r.types.empty? l += " #{r.text}" lines.push l end @@ -347,7 +347,7 @@ def documentation lines = [] return_tags.each do |r| l = '*' - l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? or r.types.empty? + l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? || r.types.empty? l += " #{r.text}" lines.push l end @@ -403,7 +403,7 @@ def overloads name: name, decl: decl, # @sg-ignore flow sensitive typing needs to handle attrs - presence: location ? location.range : nil, + presence: location&.range, return_type: param_type_from_name(tag, src.first), source: :overloads ) @@ -698,9 +698,9 @@ def infer_from_iv api_map def parse_overload_param name # @todo this needs to handle mandatory vs not args, kwargs, blocks, etc if name.start_with?('**') - [name[2..-1], :kwrestarg] + [name[2..], :kwrestarg] elsif name.start_with?('*') - [name[1..-1], :restarg] + [name[1..], :restarg] else [name, :arg] end diff --git a/lib/solargraph/pin/namespace.rb b/lib/solargraph/pin/namespace.rb index f090716c8..55bb52b0e 100644 --- a/lib/solargraph/pin/namespace.rb +++ b/lib/solargraph/pin/namespace.rb @@ -27,7 +27,7 @@ def initialize type: :class, visibility: :public, gates: [''], name: '', **splat @type = type @visibility = visibility if name.start_with?('::') - name = name[2..-1] || '' + name = name[2..] || '' @closure = Solargraph::Pin::ROOT_PIN end @open_gates = gates @@ -40,7 +40,7 @@ def initialize type: :class, visibility: :public, gates: [''], name: '', **splat '' else # @sg-ignore Need to add nil check here - closure.full_context.namespace + '::' + "#{closure.full_context.namespace}::" end closure_name += parts.join('::') @closure = Pin::Namespace.new(name: closure_name, gates: [parts.join('::')], source: :namespace) diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index c8c47ca2c..972753f7d 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -178,14 +178,15 @@ def return_type if @return_type.nil? @return_type = ComplexType::UNDEFINED found = param_tag - @return_type = ComplexType.try_parse(*found.types) unless found.nil? or found.types.nil? + @return_type = ComplexType.try_parse(*found.types) unless found.nil? || found.types.nil? # @sg-ignore flow sensitive typing should be able to handle redefinition if @return_type.undefined? - if decl == :restarg + case decl + when :restarg @return_type = ComplexType.try_parse('::Array') - elsif decl == :kwrestarg + when :kwrestarg @return_type = ComplexType.try_parse('::Hash') - elsif decl == :blockarg + when :blockarg @return_type = ComplexType.try_parse('::Proc') end end @@ -278,7 +279,7 @@ def typify_method_param api_map found = p break end - if found.nil? and !index.nil? && params[index] && (params[index].name.nil? || params[index].name.empty?) + if found.nil? && !index.nil? && params[index] && (params[index].name.nil? || params[index].name.empty?) found = params[index] end unless found.nil? || found.types.nil? @@ -328,8 +329,6 @@ def resolve_reference ref, api_map, skip pins.each do |pin| params = pin.docstring.tags(:param) return params unless params.empty? - end - pins.each do |pin| params = see_reference(pin.docstring, api_map, skip) return params unless params.empty? end diff --git a/lib/solargraph/pin/search.rb b/lib/solargraph/pin/search.rb index 532b9774e..54e4d5bc5 100644 --- a/lib/solargraph/pin/search.rb +++ b/lib/solargraph/pin/search.rb @@ -54,7 +54,7 @@ def do_query # # @return [Float] def fuzzy_string_match str1, str2 - return 1.0 + (str2.length.to_f / str1.length.to_f) if str1.downcase.include?(str2.downcase) + return 1.0 + (str2.length.to_f / str1.length) if str1.downcase.include?(str2.downcase) JaroWinkler.similarity(str1, str2, ignore_case: true) end end diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 437744c37..221682ccd 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Pin class Signature < Callable @@ -5,10 +7,6 @@ class Signature < Callable # to the method pin attr_writer :closure - def initialize **splat - super(**splat) - end - def generics # @type [Array<::String, nil>] @generics ||= [].freeze diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index e3311e6ea..886d55838 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'fileutils' require 'rbs' require 'rubygems' @@ -448,7 +450,7 @@ 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? + out&.puts "Clearing pin cache in #{path}" else out&.puts "Pin cache file #{path} does not exist" end @@ -460,11 +462,11 @@ def uncache *path_segments, out: nil 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? + 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}" unless out.nil? + out&.puts "Clearing pin cache in #{file}" end end diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 779ce03ce..cc20f9607 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -21,10 +21,6 @@ def initialize line, character @character = character end - protected def equality_fields - [line, character] - end - # @param other [Position] def <=> other return nil unless other.is_a?(Position) @@ -70,7 +66,7 @@ def self.to_offset text, position last_line_index = newline_index end - last_line_index += 1 if position.line > 0 + last_line_index += 1 if position.line.positive? # @sg-ignore flow sensitive typing should be able to handle redefinition last_line_index + position.character end @@ -107,7 +103,7 @@ def self.from_offset text, offset # @sg-ignore flow sensitive typing should be able to handle redefinition character = offset - newline_index - 1 end - character = 0 if character.nil? and (cursor - offset).between?(0, 1) + character = 0 if character.nil? && (cursor - offset).between?(0, 1) raise InvalidOffsetError if character.nil? # @sg-ignore flow sensitive typing needs to handle 'raise if' Position.new(line, character) @@ -130,5 +126,11 @@ def == other return false unless other.is_a?(Position) line == other.line and character == other.character end + + protected + + def equality_fields + [line, character] + end end end diff --git a/lib/solargraph/range.rb b/lib/solargraph/range.rb index 337e1db0d..e1ed89592 100644 --- a/lib/solargraph/range.rb +++ b/lib/solargraph/range.rb @@ -19,10 +19,6 @@ def initialize start, ending @ending = ending end - protected def equality_fields - [start, ending] - end - # @param other [BasicObject] def <=> other return nil unless other.is_a?(Range) @@ -86,7 +82,7 @@ def self.from_to l1, c1, l2, c2 # @param node [::Parser::AST::Node] # @return [Range, nil] def self.from_node node - return unless node&.loc && node.loc.expression + return unless node&.loc&.expression from_expr(node.loc.expression) end @@ -107,5 +103,11 @@ def == other def inspect "#<#{self.class} #{start.inspect} to #{ending.inspect}>" end + + protected + + def equality_fields + [start, ending] + end end end diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index fd366f8d5..66aff1288 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -68,7 +68,7 @@ def loader def cache_key return CACHE_KEY_UNRESOLVED unless resolved? - @hextdigest ||= begin + @cache_key ||= begin # @type [String, nil] data = nil # @type gem_config [nil, Hash{String => Hash{String => String}}] @@ -141,7 +141,7 @@ def pins out: $stderr # @return [generic, nil] def path_pin path, klass = Pin::Base pin = pins.find { |p| p.path == path } - pin if pin&.is_a?(klass) + pin if pin.is_a?(klass) end # @param path [String] diff --git a/lib/solargraph/rbs_map/core_fills.rb b/lib/solargraph/rbs_map/core_fills.rb index 809e802a1..0116b15eb 100644 --- a/lib/solargraph/rbs_map/core_fills.rb +++ b/lib/solargraph/rbs_map/core_fills.rb @@ -19,7 +19,7 @@ module CoreFills Solargraph::Pin::Method.new(name: 'class', scope: :instance, closure: Solargraph::Pin::Namespace.new(name: 'Object', source: :core_fill), comments: '@return [::Class]', source: :core_fill) - ] + ].freeze OVERRIDES = [ Override.from_comment('BasicObject#instance_eval', '@yieldreceiver [self]', @@ -39,7 +39,7 @@ module CoreFills # RBS does not define Class with a generic, so all calls to # generic() return an 'untyped'. We can do better: Override.method_return('Class#allocate', 'self', source: :core_fill) - ] + ].freeze # @todo I don't see any direct link in RBS to build this from - # presumably RBS is using duck typing to match interfaces @@ -73,7 +73,7 @@ module CoreFills closure: Solargraph::Pin::Namespace.new(name: 'String', source: :core_fill), source: :core_fill) - ] + ].freeze # HACK: Add Errno exception classes errno = Solargraph::Pin::Namespace.new(name: 'Errno', source: :core_fill) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 2859ae325..8ee13eacf 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -84,7 +84,7 @@ def config directory = '.' File.open(File.join(directory, '.solargraph.yml'), 'w') do |file| file.puts conf.to_yaml end - STDOUT.puts 'Configuration file initialized.' + $stdout.puts 'Configuration file initialized.' end desc 'clear', 'Delete all cached documentation' @@ -246,9 +246,13 @@ def typecheck *files end end puts "Typecheck finished in #{time.real} seconds." - puts "#{probcount} problem#{'s' if probcount != 1} found#{" in #{filecount} of #{files.length} files" if files.length != 1}." + puts "#{probcount} problem#{if probcount != 1 + 's' + end} found#{if files.length != 1 + " in #{filecount} of #{files.length} files" + end}." # " - exit 1 if probcount > 0 + exit 1 if probcount.positive? end desc 'scan', 'Test the workspace for problems' diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 5e88fb78a..94147989e 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -66,7 +66,7 @@ def at range def from_to l1, c1, l2, c2 b = Solargraph::Position.line_char_to_offset(code, l1, c1) e = Solargraph::Position.line_char_to_offset(code, l2, c2) - code[b..e - 1] + code[b..(e - 1)] end # Get the nearest node that contains the specified index. @@ -196,7 +196,7 @@ def code_for node b = Position.line_char_to_offset(code, rng.start.line, rng.start.column) # @sg-ignore Need to add nil check here e = Position.line_char_to_offset(code, rng.ending.line, rng.ending.column) - frag = code[b..e - 1].to_s + frag = code[b..(e - 1)].to_s frag.strip.gsub(/,$/, '') end @@ -293,7 +293,7 @@ def inner_folding_ranges top, result = [], parent = nil # @sg-ignore Translate to something flow sensitive typing understands range = Range.from_node(top) # @sg-ignore Need to add nil check here - if (result.empty? || range.start.line > result.last.start.line) && !(range.ending.line - range.start.line < 2) + if (result.empty? || range.start.line > result.last.start.line) && range.ending.line - range.start.line >= 2 result.push range end end @@ -321,7 +321,7 @@ def stringify_comment_array comments here = p.index(/[^ \t]/) # @sg-ignore flow sensitive typing should be able to handle redefinition skip = here if skip.nil? || here < skip - ctxt.concat p[skip..-1] + ctxt.concat p[skip..] end started = true end @@ -353,7 +353,7 @@ def foldable_comment_block_ranges return [] unless synchronized? result = [] grouped = [] - comments.keys.each do |l| + comments.each_key do |l| if grouped.empty? || l == grouped.last + 1 grouped.push l else diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index b4cda9456..4bd1b67b6 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -48,11 +48,6 @@ class Chain attr_reader :node - # @sg-ignore Fix "Not enough arguments to Module#protected" - protected def equality_fields - [links, node] - end - # @param node [Parser::AST::Node, nil] # @param links [::Array] # @param splat [Boolean] @@ -295,6 +290,13 @@ def maybe_nil type return type unless nullable? ComplexType.new(type.items + [ComplexType::NIL]) end + + protected + + # @sg-ignore Fix "Not enough arguments to Module#protected" + def equality_fields + [links, node] + end end end end diff --git a/lib/solargraph/source/chain/array.rb b/lib/solargraph/source/chain/array.rb index 544934d8a..352ccf8f0 100644 --- a/lib/solargraph/source/chain/array.rb +++ b/lib/solargraph/source/chain/array.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph class Source class Chain @@ -20,11 +22,11 @@ def resolve api_map, name_pin, locals child_types = @children.map do |child| child.infer(api_map, name_pin, locals).simplify_literals end - type = if child_types.length == 0 || child_types.any?(&:undefined?) + type = if child_types.empty? || child_types.any?(&:undefined?) ComplexType::UniqueType.new('Array', rooted: true) elsif child_types.uniq.length == 1 && child_types.first.defined? ComplexType::UniqueType.new('Array', [], child_types.uniq, rooted: true, parameters_type: :list) - elsif child_types.length == 0 + elsif child_types.empty? ComplexType::UniqueType.new('Array', rooted: true, parameters_type: :list) else ComplexType::UniqueType.new('Array', [], child_types, rooted: true, parameters_type: :fixed) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index 5f4f57e13..e9e7bbfae 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -36,12 +36,6 @@ def initialize word, location = nil, arguments = [], block = nil fix_block_pass end - # @sg-ignore Fix "Not enough arguments to Module#protected" - protected def equality_fields - # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array - super + [arguments, block] - end - def with_block? !!@block end @@ -70,7 +64,7 @@ def resolve api_map, name_pin, locals [stack.first].compact end # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array - pin_groups = [] if !api_map.loose_unions && pin_groups.any? { |pins| pins.empty? } + pin_groups = [] if !api_map.loose_unions && pin_groups.any?(&:empty?) pins = pin_groups.flatten.uniq(&:path) return [] if pins.empty? inferred_pins(pins, api_map, name_pin, locals) @@ -370,6 +364,14 @@ def block_call_type api_map, name_pin, locals # @sg-ignore Need to add nil check here block.infer(api_map, block_pin, locals) end + + protected + + # @sg-ignore Fix "Not enough arguments to Module#protected" + def equality_fields + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array + super + [arguments, block] + end end end end diff --git a/lib/solargraph/source/chain/constant.rb b/lib/solargraph/source/chain/constant.rb index 688705150..e558a2e1f 100644 --- a/lib/solargraph/source/chain/constant.rb +++ b/lib/solargraph/source/chain/constant.rb @@ -13,7 +13,7 @@ def initialize word def resolve api_map, name_pin, locals return [Pin::ROOT_PIN] if word.empty? if word.start_with?('::') - base = word[2..-1] + base = word[2..] gates = [''] else base = word diff --git a/lib/solargraph/source/chain/hash.rb b/lib/solargraph/source/chain/hash.rb index bf2aa484c..a75963478 100644 --- a/lib/solargraph/source/chain/hash.rb +++ b/lib/solargraph/source/chain/hash.rb @@ -12,12 +12,6 @@ def initialize type, node, splatted = false @splatted = splatted end - # @sg-ignore Fix "Not enough arguments to Module#protected" - protected def equality_fields - # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array - super + [@splatted] - end - def word @word ||= "<#{@type}>" end @@ -29,6 +23,14 @@ def resolve api_map, name_pin, locals def splatted? @splatted end + + protected + + # @sg-ignore Fix "Not enough arguments to Module#protected" + def equality_fields + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array + super + [@splatted] + end end end end diff --git a/lib/solargraph/source/chain/if.rb b/lib/solargraph/source/chain/if.rb index 454a85647..186f6e6f0 100644 --- a/lib/solargraph/source/chain/if.rb +++ b/lib/solargraph/source/chain/if.rb @@ -11,17 +11,19 @@ def initialize links @links = links end - # @sg-ignore Fix "Not enough arguments to Module#protected" - protected def equality_fields - # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array - super + [@links] - end - def resolve api_map, name_pin, locals types = @links.map { |link| link.infer(api_map, name_pin, locals) } [Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.try_parse(types.map(&:tag).uniq.join(', ')), source: :chain)] end + + protected + + # @sg-ignore Fix "Not enough arguments to Module#protected" + def equality_fields + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array + super + [@links] + end end end end diff --git a/lib/solargraph/source/chain/link.rb b/lib/solargraph/source/chain/link.rb index dcc5789e1..4eb6c7d5c 100644 --- a/lib/solargraph/source/chain/link.rb +++ b/lib/solargraph/source/chain/link.rb @@ -17,17 +17,6 @@ def initialize word = '' @word = word end - # @sg-ignore two problems - Declared return type - # ::Solargraph::Source::Chain::Array does not match inferred - # type ::Array(::Class<::Solargraph::Source::Chain::Link>, - # ::String) for - # Solargraph::Source::Chain::Link#equality_fields - # and - # Not enough arguments to Module#protected - protected def equality_fields - [self.class, word] - end - def undefined? word == '' end @@ -85,6 +74,17 @@ def desc protected + # @sg-ignore two problems - Declared return type + # ::Solargraph::Source::Chain::Array does not match inferred + # type ::Array(::Class<::Solargraph::Source::Chain::Link>, + # ::String) for + # Solargraph::Source::Chain::Link#equality_fields + # and + # Not enough arguments to Module#protected + def equality_fields + [self.class, word] + end + # Mark whether this link is the head of a chain # # @param bool [Boolean] diff --git a/lib/solargraph/source/chain/q_call.rb b/lib/solargraph/source/chain/q_call.rb index 811594f7d..7247cf4cc 100644 --- a/lib/solargraph/source/chain/q_call.rb +++ b/lib/solargraph/source/chain/q_call.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph class Source class Chain diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index 97df1c140..acea51b67 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 && !range.nil? && new_text.match(/[.\[{(@$:]$/) [':', '@'].each do |dupable| next unless new_text == dupable # @sg-ignore flow sensitive typing needs to handle attrs @@ -66,7 +66,7 @@ def repair text match = result[0, off].match(/[.:]+\z/) if match # @sg-ignore flow sensitive typing should be able to handle redefinition - result = result[0, off].sub(/#{match[0]}\z/, ' ' * match[0].length) + result[off..-1] + result = result[0, off].sub(/#{match[0]}\z/, ' ' * match[0].length) + result[off..] end result end @@ -82,7 +82,7 @@ def commit text, insert start_offset = Position.to_offset(text, range.start) # @sg-ignore Need to add nil check here end_offset = Position.to_offset(text, range.ending) - (start_offset == 0 ? '' : text[0..start_offset - 1].to_s) + normalize(insert) + text[end_offset..-1].to_s + (start_offset.zero? ? '' : text[0..(start_offset - 1)].to_s) + normalize(insert) + text[end_offset..].to_s end end end diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 0b3b40606..1afa1c76c 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -39,11 +39,11 @@ def word # @return [String] def start_of_word @start_of_word ||= begin - match = source.code[0..offset - 1].to_s.match(start_word_pattern) + match = source.code[0..(offset - 1)].to_s.match(start_word_pattern) result = (match ? match[0] : '') # Including the preceding colon if the word appears to be a symbol # @sg-ignore Need to add nil check here - if source.code[0..offset - result.length - 1].end_with?(':') and !source.code[0..offset - result.length - 1].end_with?('::') + if source.code[0..(offset - result.length - 1)].end_with?(':') && !source.code[0..(offset - result.length - 1)].end_with?('::') result = ":#{result}" end result @@ -57,7 +57,7 @@ def start_of_word # @sg-ignore Need to add nil check here def end_of_word @end_of_word ||= begin - match = source.code[offset..-1].to_s.match(end_word_pattern) + match = source.code[offset..].to_s.match(end_word_pattern) match ? match[0] : '' end end diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index b5738341d..b410e0214 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -103,7 +103,7 @@ def chain # @sg-ignore Need to add nil check here # @return [String] def phrase - @phrase ||= source.code[signature_data..offset - 1] + @phrase ||= source.code[signature_data..(offset - 1)] end # @sg-ignore Need to add nil check here @@ -165,36 +165,37 @@ def get_signature_data_at index in_whitespace = false while index >= 0 pos = Position.from_offset(@source.code, index) - break if index > 0 and @source.comment_at?(pos) - break if brackets > 0 or parens > 0 or squares > 0 + break if index.positive? && @source.comment_at?(pos) + break if brackets.positive? || parens.positive? || squares.positive? char = @source.code[index, 1] break if char.nil? # @todo Is this the right way to handle this? - if brackets.zero? and parens.zero? and squares.zero? and [' ', "\r", "\n", "\t"].include?(char) + if brackets.zero? && parens.zero? && squares.zero? && [' ', "\r", "\n", "\t"].include?(char) in_whitespace = true else # @sg-ignore Need to add nil check here - if brackets.zero? and parens.zero? and squares.zero? and in_whitespace && !(char == '.' or @source.code[index + 1..-1].strip.start_with?('.')) - @source.code[index + 1..-1] + if brackets.zero? && parens.zero? && squares.zero? && in_whitespace && !((char == '.') || @source.code[(index + 1)..].strip.start_with?('.')) + @source.code[(index + 1)..] # @sg-ignore Need to add nil check here - @source.code[index + 1..-1].lstrip + @source.code[(index + 1)..].lstrip # @sg-ignore Need to add nil check here - index += (@source.code[index + 1..-1].length - @source.code[index + 1..-1].lstrip.length) + index += (@source.code[(index + 1)..].length - @source.code[(index + 1)..].lstrip.length) break end - if char == ')' + case char + when ')' parens -= 1 - elsif char == ']' + when ']' squares -= 1 - elsif char == '}' + when '}' brackets -= 1 - elsif char == '(' + when '(' parens += 1 - elsif char == '{' + when '{' brackets += 1 - elsif char == '[' + when '[' squares += 1 end - if brackets.zero? and parens.zero? and squares.zero? + if brackets.zero? && parens.zero? && squares.zero? break if ['"', "'", ',', ';', '%'].include?(char) break if ['!', '?'].include?(char) && index < offset - 1 break if char == '$' diff --git a/lib/solargraph/source/updater.rb b/lib/solargraph/source/updater.rb index 4fdb78330..9debc0283 100644 --- a/lib/solargraph/source/updater.rb +++ b/lib/solargraph/source/updater.rb @@ -33,7 +33,7 @@ def initialize filename, version, changes # @return [String] def write text, nullable = false can_nullify = (nullable and changes.length == 1) - return @output if @input == text and can_nullify == @did_nullify + return @output if (@input == text) && (can_nullify == @did_nullify) @input = text @output = text @did_nullify = can_nullify diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index d38dfdc36..c1e7c24af 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -161,7 +161,7 @@ def package_completions result def tag_complete result = [] # @sg-ignore Need to add nil check here - match = source_map.code[0..cursor.offset - 1].match(/[\[<, ]([a-z0-9_:]*)\z/i) + match = source_map.code[0..(cursor.offset - 1)].match(/[\[<, ]([a-z0-9_:]*)\z/i) if match # @sg-ignore Need to add nil check here full = match[1] diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index e1d005415..8c306473d 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -94,7 +94,7 @@ def find_directive_line_number comment, tag, start # Avoid overruning the index return start unless start < comment.lines.length # @sg-ignore Need to add nil check here - num = comment.lines[start..-1].find_index do |line| + num = comment.lines[start..].find_index do |line| # Legacy method directives might be `@method` instead of `@!method` # @todo Legacy syntax should probably emit a warning line.include?("@!#{tag}") || (tag == 'method' && line.include?("@#{tag}")) @@ -138,7 +138,7 @@ def process_directive source_position, comment_position, directive when 'attribute' return if directive.tag.name.nil? namespace = closure_at(source_position) - t = directive.tag.types.nil? || directive.tag.types.empty? ? nil : directive.tag.types.flatten.join('') + t = directive.tag.types.nil? || directive.tag.types.empty? ? nil : directive.tag.types.join if t.nil? || t.include?('r') pins.push Solargraph::Pin::Method.new( location: location, @@ -212,7 +212,7 @@ def process_directive source_position, comment_position, directive Parser.process_node(src.node, region, @pins, locals, ivars) @pins.concat ivars # @sg-ignore Need to add nil check here - @pins[index..-1].each do |p| + @pins[index..].each do |p| # @todo Smelly instance variable access p.location.range.start.instance_variable_set(:@line, p.location.range.start.line + loff) p.location.range.ending.instance_variable_set(:@line, p.location.range.ending.line + loff) @@ -258,7 +258,7 @@ def remove_inline_comment_hashes comment # @sg-ignore Need to add nil check here num = cur if cur < num end - ctxt += "#{p[num..-1]}" if started + ctxt += p[num..].to_s if started end ctxt end diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 6a3e37e0b..708f5b9a6 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -444,17 +444,16 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum return errors if par.decl == :restarg # bail out and assume the rest is valid pending better arg processing argchain = arguments[idx] if argchain.nil? + final_arg = arguments.last if par.decl == :arg - final_arg = arguments.last if final_arg && final_arg.node.type == :splat argchain = final_arg return errors else errors.push Problem.new(location, "Not enough arguments to #{pin.path}") end - else - final_arg = arguments.last - argchain = final_arg if final_arg && %i[kwsplat hash].include?(final_arg.node.type) + elsif final_arg && %i[kwsplat hash].include?(final_arg.node.type) + argchain = final_arg end end if argchain @@ -812,7 +811,7 @@ def optional_param_count parameters def abstract? pin pin.docstring.has_tag?('abstract') || # @sg-ignore of low sensitive typing needs to handle ivars - (pin.closure && pin.closure.docstring.has_tag?('abstract')) + pin.closure&.docstring&.has_tag?('abstract') end # @param pin [Pin::Method] diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index 838bd2b22..351ee28a5 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -215,7 +215,7 @@ def gemspec_files # @return [String, nil] def rbs_collection_path - @gem_rbs_collection ||= read_rbs_collection_path + @rbs_collection_path ||= read_rbs_collection_path end # @return [String, nil] @@ -307,7 +307,7 @@ def load_sources source_hash.clear return if directory.empty? || directory == '*' size = config.calculated.length - if config.max_files > 0 and size > config.max_files + if config.max_files.positive? && (size > config.max_files) raise WorkspaceTooLargeError, "The workspace is too large to index (#{size} files, #{config.max_files} max)" end diff --git a/lib/solargraph/yard_map/helpers.rb b/lib/solargraph/yard_map/helpers.rb index 7969ffce5..8c1747d9a 100644 --- a/lib/solargraph/yard_map/helpers.rb +++ b/lib/solargraph/yard_map/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph class YardMap module Helpers diff --git a/lib/solargraph/yard_map/mapper.rb b/lib/solargraph/yard_map/mapper.rb index 67a9ff860..c08021364 100644 --- a/lib/solargraph/yard_map/mapper.rb +++ b/lib/solargraph/yard_map/mapper.rb @@ -35,12 +35,13 @@ def map # @return [Array] def generate_pins code_object result = [] - if code_object.is_a?(YARD::CodeObjects::NamespaceObject) + case code_object + when YARD::CodeObjects::NamespaceObject nspin = ToNamespace.make(code_object, @spec, @namespace_pins[code_object.namespace.to_s]) @namespace_pins[code_object.path] = nspin result.push nspin # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check - if code_object.is_a?(YARD::CodeObjects::ClassObject) and !code_object.superclass.nil? + if code_object.is_a?(YARD::CodeObjects::ClassObject) && !code_object.superclass.nil? # This method of superclass detection is a bit of a hack. If # the superclass is a Proxy, it is assumed to be undefined in its # yardoc and converted to a fully qualified namespace. @@ -64,7 +65,7 @@ def generate_pins code_object source: :yard_map ) end - elsif code_object.is_a?(YARD::CodeObjects::MethodObject) + when YARD::CodeObjects::MethodObject closure = @namespace_pins[code_object.namespace.to_s] if code_object.name == :initialize && code_object.scope == :instance # @todo Check the visibility of .new @@ -73,7 +74,7 @@ def generate_pins code_object else result.push ToMethod.make(code_object, nil, nil, nil, closure, @spec) end - elsif code_object.is_a?(YARD::CodeObjects::ConstantObject) + when YARD::CodeObjects::ConstantObject closure = @namespace_pins[code_object.namespace] result.push ToConstant.make(code_object, closure, @spec) end diff --git a/lib/solargraph/yard_map/mapper/to_method.rb b/lib/solargraph/yard_map/mapper/to_method.rb index f4d5b0dad..047f53957 100644 --- a/lib/solargraph/yard_map/mapper/to_method.rb +++ b/lib/solargraph/yard_map/mapper/to_method.rb @@ -10,7 +10,7 @@ module ToMethod VISIBILITY_OVERRIDE = { # YARD pays attention to 'private' statements prior to class methods but shouldn't ['Rails::Engine', :class, 'find_root_with_flag'] => :public - } + }.freeze # @param code_object [YARD::CodeObjects::MethodObject] # @param name [String, nil] diff --git a/solargraph.gemspec b/solargraph.gemspec index e8435db6f..06edbf19f 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -1,5 +1,7 @@ +# frozen_string_literal: true + # @sg-ignore Should better support meaning of '&' in RBS -$LOAD_PATH.unshift File.dirname(__FILE__) + '/lib' +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/lib" require 'solargraph/version' require 'date' diff --git a/spec/api_map/cache_spec.rb b/spec/api_map/cache_spec.rb index 0a8d002de..392f04fe4 100644 --- a/spec/api_map/cache_spec.rb +++ b/spec/api_map/cache_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::ApiMap::Cache do it 'recognizes empty caches' do - cache = Solargraph::ApiMap::Cache.new + cache = described_class.new expect(cache).to be_empty cache.set_methods('', :class, [:public], true, []) expect(cache).not_to be_empty diff --git a/spec/api_map/config_spec.rb b/spec/api_map/config_spec.rb index 936d05e27..993cb9a97 100644 --- a/spec/api_map/config_spec.rb +++ b/spec/api_map/config_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'tmpdir' describe Solargraph::Workspace::Config do diff --git a/spec/api_map/source_to_yard_spec.rb b/spec/api_map/source_to_yard_spec.rb index e7dcf3d0d..c232ca41d 100644 --- a/spec/api_map/source_to_yard_spec.rb +++ b/spec/api_map/source_to_yard_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::ApiMap::SourceToYard do it 'rakes sources' do source = Solargraph::SourceMap.load_string(%( @@ -9,7 +11,7 @@ def baz end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) expect(object.code_object_paths.length).to eq(3) expect(object.code_object_paths).to include('Foo') @@ -30,7 +32,7 @@ def self.baz end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) class_object = object.code_object_at('Foo') expect(class_object.docstring).to eq('My foo class 描述') @@ -51,7 +53,7 @@ class Baz end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) module_object = object.code_object_at('Foo') class_object = object.code_object_at('Baz') @@ -68,7 +70,7 @@ class Baz end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) module_object = object.code_object_at('Foo') class_object = object.code_object_at('Baz') @@ -84,7 +86,7 @@ class Foo end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) expect(object.code_object_at('Foo#bar')).not_to be_nil expect(object.code_object_at('Foo#bar=')).to be_nil @@ -102,7 +104,7 @@ def bar baz, boo = 'boo' end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) method_object = object.code_object_at('Foo#bar') expect(method_object.parameters.length).to eq(2) @@ -118,7 +120,7 @@ def bar baz, boo: 'boo' end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) method_object = object.code_object_at('Foo#bar') expect(method_object.parameters.length).to eq(2) diff --git a/spec/api_map/store_spec.rb b/spec/api_map/store_spec.rb index dc8413de5..059c3eb1b 100644 --- a/spec/api_map/store_spec.rb +++ b/spec/api_map/store_spec.rb @@ -4,7 +4,7 @@ it 'indexes multiple pinsets' do foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo') bar_pin = Solargraph::Pin::Namespace.new(name: 'Bar') - store = Solargraph::ApiMap::Store.new([foo_pin], [bar_pin]) + store = described_class.new([foo_pin], [bar_pin]) expect(store.get_path_pins('Foo')).to eq([foo_pin]) expect(store.get_path_pins('Bar')).to eq([bar_pin]) @@ -13,7 +13,7 @@ it 'indexes empty pinsets' do foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo') - store = Solargraph::ApiMap::Store.new([], [foo_pin]) + store = described_class.new([], [foo_pin]) expect(store.get_path_pins('Foo')).to eq([foo_pin]) end @@ -21,7 +21,7 @@ foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo') bar_pin = Solargraph::Pin::Namespace.new(name: 'Bar') baz_pin = Solargraph::Pin::Namespace.new(name: 'Baz') - store = Solargraph::ApiMap::Store.new([foo_pin], [bar_pin]) + store = described_class.new([foo_pin], [bar_pin]) store.update([foo_pin], [baz_pin]) expect(store.get_path_pins('Foo')).to eq([foo_pin]) @@ -32,7 +32,7 @@ it 'updates new pinsets' do foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo') bar_pin = Solargraph::Pin::Namespace.new(name: 'Bar') - store = Solargraph::ApiMap::Store.new([foo_pin]) + store = described_class.new([foo_pin]) store.update([foo_pin], [bar_pin]) expect(store.get_path_pins('Foo')).to eq([foo_pin]) @@ -42,7 +42,7 @@ it 'updates empty stores' do foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo') bar_pin = Solargraph::Pin::Namespace.new(name: 'Bar') - store = Solargraph::ApiMap::Store.new + store = described_class.new store.update([foo_pin, bar_pin]) expect(store.get_path_pins('Foo')).to eq([foo_pin]) @@ -56,20 +56,20 @@ class Foo; end class Bar < Foo; end ), 'test.rb') - store = Solargraph::ApiMap::Store.new(map.pins) + store = described_class.new(map.pins) ref = store.get_superclass('Bar') expect(ref.name).to eq('Foo') end it 'returns Boolean superclass' do - store = Solargraph::ApiMap::Store.new + store = described_class.new ref = store.get_superclass('TrueClass') expect(ref.name).to eq('Boolean') end it 'maps core Errno classes' do map = Solargraph::RbsMap::CoreMap.new - store = Solargraph::ApiMap::Store.new(map.pins) + store = described_class.new(map.pins) Errno.constants.each do |const| pin = store.get_path_pins("Errno::#{const}").first expect(pin).to be_a(Solargraph::Pin::Namespace) diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index e488c2ada..87469562b 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe Solargraph::ApiMap do - let(:api_map) { Solargraph::ApiMap.new } + let(:api_map) { described_class.new } let(:bench) do Solargraph::Bench.new(external_requires: external_requires, workspace: Solargraph::Workspace.new('.')) end @@ -37,7 +37,7 @@ module Bar Bar::Baz ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) + api_map = described_class.new.map(source) clip = api_map.clip_at('test.rb', [11, 8]) expect(clip.infer.to_s).to eq('Symbol') @@ -60,7 +60,7 @@ module Bar a ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) + api_map = described_class.new.map(source) clip = api_map.clip_at('test.rb', [13, 8]) expect(clip.infer.to_s).to eq('Symbol') @@ -85,7 +85,7 @@ module Bar a ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) + api_map = described_class.new.map(source) clip = api_map.clip_at('test.rb', [15, 8]) expect(clip.infer.to_s).to eq('Symbol') @@ -110,7 +110,7 @@ class B a ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) + api_map = described_class.new.map(source) clip = api_map.clip_at('test.rb', [15, 8]) expect(clip.infer.to_s).to eq('Symbol') @@ -119,7 +119,7 @@ class B describe '#get_method_stack' do let(:out) { StringIO.new } - let(:api_map) { Solargraph::ApiMap.load_with_cache(Dir.pwd, out) } + let(:api_map) { described_class.load_with_cache(Dir.pwd, out) } context 'with stdlib that has vital dependencies' do let(:external_requires) { ['yaml'] } @@ -142,7 +142,7 @@ class B describe '#cache_all_for_doc_map!' do it 'can cache gems without a bench' do - api_map = Solargraph::ApiMap.new + api_map = described_class.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!(out: $stderr) @@ -152,14 +152,14 @@ class B describe '#workspace' do it 'can get a default workspace without a bench' do - api_map = Solargraph::ApiMap.new + api_map = described_class.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 + api_map = described_class.new expect(api_map.uncached_gemspecs).not_to be_nil end end @@ -178,7 +178,7 @@ class Includer end ), 'test.rb') - api_map = Solargraph::ApiMap.new + api_map = described_class.new api_map.map source pins = api_map.get_methods('Foo::Includer') expect(pins.map(&:path)).to include('Foo::Bar#baz') diff --git a/spec/api_map_spec.rb b/spec/api_map_spec.rb index 8c396e5ee..facf9489c 100755 --- a/spec/api_map_spec.rb +++ b/spec/api_map_spec.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + require 'tmpdir' describe Solargraph::ApiMap do before :all do - @api_map = Solargraph::ApiMap.new + @api_map = described_class.new end it 'returns core methods' do @@ -190,7 +192,7 @@ def prot end it 'adds Object instance methods to duck types' do - api_map = Solargraph::ApiMap.new + api_map = described_class.new type = Solargraph::ComplexType.parse('#foo') pins = api_map.get_complex_type_methods(type) expect(pins.any? { |p| p.namespace == 'BasicObject' }).to be(true) @@ -439,7 +441,7 @@ class Sup end it 'loads workspaces from directories' do - api_map = Solargraph::ApiMap.load('spec/fixtures/workspace') + api_map = described_class.load('spec/fixtures/workspace') expect(api_map.source_map(File.absolute_path('spec/fixtures/workspace/app.rb'))).to be_a(Solargraph::SourceMap) end @@ -752,17 +754,17 @@ def bar; end end it 'can qualify "Boolean"' do - api_map = Solargraph::ApiMap.new + api_map = described_class.new expect(api_map.qualify('Boolean')).to eq('Boolean') end it 'knows that true is a "subtype" of Boolean' do - api_map = Solargraph::ApiMap.new + api_map = described_class.new expect(api_map.super_and_sub?('Boolean', 'true')).to be(true) end it 'knows that false is a "subtype" of Boolean' do - api_map = Solargraph::ApiMap.new + api_map = described_class.new expect(api_map.super_and_sub?('Boolean', 'false')).to be(true) end @@ -790,7 +792,7 @@ class Foo mixin = Solargraph::Pin::Reference::Include.new( name: 'defined?(DidYouMean::SpellChecker) && defined?(DidYouMean::Correctable)', closure: closure ) - api_map = Solargraph::ApiMap.new(pins: [closure, mixin]) + api_map = described_class.new(pins: [closure, mixin]) expect(api_map.get_method_stack('Foo', 'foo')).to be_empty end @@ -811,7 +813,7 @@ def baz end ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) + api_map = described_class.new.map(source) clip = api_map.clip_at('test.rb', [11, 10]) expect(clip.infer.to_s).to eq('Symbol') diff --git a/spec/complex_type_spec.rb b/spec/complex_type_spec.rb index cb056cee7..3075aaaaf 100644 --- a/spec/complex_type_spec.rb +++ b/spec/complex_type_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe 'YARD type specifier list parsing' do context 'with https://www.rubydoc.info/gems/yard/file/docs/Tags.md#type-list-conventions compliance' do # Types Specifier List @@ -215,7 +217,7 @@ types = Solargraph::ComplexType.parse('Hash{String, Symbol => Integer, BigDecimal}') expect(types.length).to eq(1) type = types.first - expect(type.hash_parameters?).to eq(true) + expect(type.hash_parameters?).to be(true) expect(type.key_types.map(&:name)).to eq(%w[String Symbol]) expect(type.value_types.map(&:name)).to eq(%w[Integer BigDecimal]) expect(type.to_rbs).to eq('Hash[(String | Symbol), (Integer | BigDecimal)]') @@ -535,7 +537,7 @@ ['generic', 'Array>', { 'B' => 'Integer' }, 'Array', { 'B' => 'Integer', 'A' => 'Array' }], ['Array>', 'Array', {}, 'Array', { 'A' => 'String' }] - ] + ].freeze UNIQUE_METHOD_GENERIC_TESTS.each do |tag, context_type_tag, unfrozen_input_map, expected_tag, expected_output_map| context "when resolveing #{tag} with context #{context_type_tag} and existing resolved generics #{unfrozen_input_map}" do @@ -545,7 +547,7 @@ let(:context_type) { Solargraph::ComplexType.parse(context_type_tag) } let(:generic_value) { unfrozen_input_map.transform_values! { |tag| Solargraph::ComplexType.parse(tag) } } - it '#{tag} is a unique type' do + it "#{tag} is a unique type" do expect(complex_type.length).to eq(1) end diff --git a/spec/convention/struct_definition_spec.rb b/spec/convention/struct_definition_spec.rb index 51a71f673..02786cfe6 100644 --- a/spec/convention/struct_definition_spec.rb +++ b/spec/convention/struct_definition_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Convention::StructDefinition do describe 'parsing docs' do it 'supports keyword args' do diff --git a/spec/convention_spec.rb b/spec/convention_spec.rb index b6f4fc52e..7785aef68 100644 --- a/spec/convention_spec.rb +++ b/spec/convention_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Convention do it 'newly defined pins are resolved by ApiMap after file changes' do filename = 'test.rb' diff --git a/spec/diagnostics/base_spec.rb b/spec/diagnostics/base_spec.rb index 42fbb097e..417797172 100644 --- a/spec/diagnostics/base_spec.rb +++ b/spec/diagnostics/base_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::Diagnostics::Base do it 'returns empty diagnostics' do - reporter = Solargraph::Diagnostics::Base.new + reporter = described_class.new expect(reporter.diagnose(nil, nil)).to be_empty end end diff --git a/spec/diagnostics/require_not_found_spec.rb b/spec/diagnostics/require_not_found_spec.rb index 53b375d7e..1588a033b 100644 --- a/spec/diagnostics/require_not_found_spec.rb +++ b/spec/diagnostics/require_not_found_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Diagnostics::RequireNotFound do before do @source = Solargraph::Source.new(%( @@ -12,7 +14,7 @@ end it 'reports unresolved requires' do - reporter = Solargraph::Diagnostics::RequireNotFound.new + reporter = described_class.new result = reporter.diagnose(@source, @api_map) expect(result.length).to eq(1) end diff --git a/spec/diagnostics/rubocop_helpers_spec.rb b/spec/diagnostics/rubocop_helpers_spec.rb index 111fef850..7bf374d67 100644 --- a/spec/diagnostics/rubocop_helpers_spec.rb +++ b/spec/diagnostics/rubocop_helpers_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Diagnostics::RubocopHelpers do context 'with custom version' do around do |example| @@ -20,7 +22,7 @@ it 'requires the specified version of rubocop' do input = custom_version - Solargraph::Diagnostics::RubocopHelpers.require_rubocop(input) + described_class.require_rubocop(input) output = RuboCop::Version::STRING expect(output).to eq(custom_version) end @@ -31,7 +33,7 @@ it 'requires the default version of rubocop' do input = nil - Solargraph::Diagnostics::RubocopHelpers.require_rubocop(input) + described_class.require_rubocop(input) output = RuboCop::Version::STRING expect(output).to eq(default_version) end @@ -39,13 +41,13 @@ it 'converts lower-case drive letters to upper-case' do input = 'c:/one/two' - output = Solargraph::Diagnostics::RubocopHelpers.fix_drive_letter(input) + output = described_class.fix_drive_letter(input) expect(output).to eq('C:/one/two') end it 'ignores paths without drive letters' do input = 'one/two' - output = Solargraph::Diagnostics::RubocopHelpers.fix_drive_letter(input) + output = described_class.fix_drive_letter(input) expect(output).to eq('one/two') end end diff --git a/spec/diagnostics/rubocop_spec.rb b/spec/diagnostics/rubocop_spec.rb index 4926a458f..7f6a9221d 100644 --- a/spec/diagnostics/rubocop_spec.rb +++ b/spec/diagnostics/rubocop_spec.rb @@ -10,7 +10,7 @@ def bar foo = Foo.new ), 'file.rb') - rubocop = Solargraph::Diagnostics::Rubocop.new + rubocop = described_class.new result = rubocop.diagnose(source, nil) expect(result).to be_a(Array) end @@ -28,13 +28,13 @@ def bar YAML example.run ensure - File.delete(config_file) if File.exist?(config_file) + FileUtils.rm_f(config_file) end it 'handles validation errors' do file = File.realpath(File.join(fixture_path, 'app.rb')) source = Solargraph::Source.load(file) - rubocop = Solargraph::Diagnostics::Rubocop.new + rubocop = described_class.new expect do rubocop.diagnose(source, nil) end.to raise_error(Solargraph::DiagnosticsError) @@ -44,7 +44,7 @@ def bar it 'calculates ranges' do file = File.realpath(File.join('spec', 'fixtures', 'rubocop-unused-variable-error', 'app.rb')) source = Solargraph::Source.load(file) - rubocop = Solargraph::Diagnostics::Rubocop.new + rubocop = described_class.new results = rubocop.diagnose(source, nil) expect(results).to be_one diff --git a/spec/diagnostics/type_check_spec.rb b/spec/diagnostics/type_check_spec.rb index c140f9593..f28cc7b73 100644 --- a/spec/diagnostics/type_check_spec.rb +++ b/spec/diagnostics/type_check_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Diagnostics::TypeCheck do let(:api_map) { Solargraph::ApiMap.new } @@ -8,7 +10,7 @@ def foo end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always').diagnose(source, api_map) + result = described_class.new('always').diagnose(source, api_map) expect(result).to be_empty end @@ -18,7 +20,7 @@ def foo end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always', 'strong').diagnose(source, api_map) + result = described_class.new('always', 'strong').diagnose(source, api_map) expect(result.length).to eq(1) expect(result[0][:message]).to include('foo') end @@ -31,7 +33,7 @@ def foo(bar) end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always').diagnose(source, api_map) + result = described_class.new('always').diagnose(source, api_map) expect(result).to be_empty end @@ -43,7 +45,7 @@ def foo(bar) end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always', 'strong').diagnose(source, api_map) + result = described_class.new('always', 'strong').diagnose(source, api_map) expect(result.length).to eq(1) expect(result[0][:message]).to include('bar') end @@ -61,7 +63,7 @@ def foo end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always').diagnose(source, api_map) + result = described_class.new('always').diagnose(source, api_map) expect(result).to be_empty end @@ -79,7 +81,7 @@ def foo bar end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always').diagnose(source, api_map) + result = described_class.new('always').diagnose(source, api_map) expect(result).to be_empty end @@ -92,7 +94,7 @@ def foo(bar = 'bar', baz: 'baz') end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always').diagnose(source, api_map) + result = described_class.new('always').diagnose(source, api_map) expect(result).to be_empty end end diff --git a/spec/diagnostics/update_errors_spec.rb b/spec/diagnostics/update_errors_spec.rb index 1a05f93d5..0bb013231 100644 --- a/spec/diagnostics/update_errors_spec.rb +++ b/spec/diagnostics/update_errors_spec.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + describe Solargraph::Diagnostics::UpdateErrors do it 'detects repaired lines' do api_map = Solargraph::ApiMap.new orig = Solargraph::Source.load_string('foo', 'test.rb') - diagnoser = Solargraph::Diagnostics::UpdateErrors.new + diagnoser = described_class.new result = diagnoser.diagnose(orig, api_map) expect(result.length).to eq(0) updater = Solargraph::Source::Updater.new('test.rb', 2, [ @@ -12,7 +14,7 @@ ) ]) source = orig.synchronize(updater) - diagnoser = Solargraph::Diagnostics::UpdateErrors.new + diagnoser = described_class.new result = diagnoser.diagnose(source, api_map) expect(result.length).to eq(1) end diff --git a/spec/diagnostics_spec.rb b/spec/diagnostics_spec.rb index 628341f2a..83e222e12 100644 --- a/spec/diagnostics_spec.rb +++ b/spec/diagnostics_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + describe Solargraph::Diagnostics do it 'registers reporters' do - Solargraph::Diagnostics.register 'base', Solargraph::Diagnostics::Base - expect(Solargraph::Diagnostics.reporters).to include('base') - expect(Solargraph::Diagnostics.reporter('base')).to be(Solargraph::Diagnostics::Base) + described_class.register 'base', Solargraph::Diagnostics::Base + expect(described_class.reporters).to include('base') + expect(described_class.reporter('base')).to be(Solargraph::Diagnostics::Base) end end diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index c323095ec..8ff1e70b1 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -71,7 +71,7 @@ # 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'], workspace) + described_class.new(['set'], workspace) expect(Solargraph.logger).not_to have_received(:warn).with(/path set/) end @@ -173,7 +173,7 @@ def global doc_map Solargraph::Convention.register dummy_convention - doc_map = Solargraph::DocMap.new(['original_gem'], workspace) + doc_map = described_class.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 diff --git a/spec/language_server/host/diagnoser_spec.rb b/spec/language_server/host/diagnoser_spec.rb index c290bf03b..9ce9df627 100644 --- a/spec/language_server/host/diagnoser_spec.rb +++ b/spec/language_server/host/diagnoser_spec.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Host::Diagnoser do it 'diagnoses on ticks' do host = instance_double(Solargraph::LanguageServer::Host, options: { 'diagnostics' => true }, synchronizing?: false) allow(host).to receive(:diagnose) - diagnoser = Solargraph::LanguageServer::Host::Diagnoser.new(host) + diagnoser = described_class.new(host) diagnoser.schedule 'file.rb' diagnoser.tick expect(host).to have_received(:diagnose).with('file.rb') diff --git a/spec/language_server/host/dispatch_spec.rb b/spec/language_server/host/dispatch_spec.rb index 1ecd49c9b..8db9c1246 100644 --- a/spec/language_server/host/dispatch_spec.rb +++ b/spec/language_server/host/dispatch_spec.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Host::Dispatch do before :all do # @dispatch = Solargraph::LanguageServer::Host::Dispatch @dispatch = Object.new - @dispatch.extend Solargraph::LanguageServer::Host::Dispatch + @dispatch.extend described_class end after do diff --git a/spec/language_server/host/message_worker_spec.rb b/spec/language_server/host/message_worker_spec.rb index e2f83d952..4b10f0265 100644 --- a/spec/language_server/host/message_worker_spec.rb +++ b/spec/language_server/host/message_worker_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Host::MessageWorker do it 'handle requests on queue' do host = instance_double(Solargraph::LanguageServer::Host) message = { 'method' => '$/example' } allow(host).to receive(:receive).with(message).and_return(nil) - worker = Solargraph::LanguageServer::Host::MessageWorker.new(host) + worker = described_class.new(host) worker.queue(message) expect(worker.messages).to eq [message] worker.tick diff --git a/spec/language_server/host_spec.rb b/spec/language_server/host_spec.rb index 95d5ee3bb..f0497b8f3 100644 --- a/spec/language_server/host_spec.rb +++ b/spec/language_server/host_spec.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + require 'tmpdir' describe Solargraph::LanguageServer::Host do it 'prepares a workspace' do - host = Solargraph::LanguageServer::Host.new + host = described_class.new Dir.mktmpdir do |dir| host.prepare(dir) expect(host.libraries.first).not_to be_nil @@ -10,7 +12,7 @@ end it 'processes responses to message requests' do - host = Solargraph::LanguageServer::Host.new + host = described_class.new done_somethings = 0 host.send_request 'window/showMessageRequest', { 'message' => 'Message', @@ -28,7 +30,7 @@ it 'creates files from disk' do Dir.mktmpdir do |dir| - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.prepare dir file = File.join(dir, 'test.rb') File.write(file, "foo = 'foo'") @@ -41,7 +43,7 @@ it 'deletes files' do Dir.mktmpdir do |dir| expect do - host = Solargraph::LanguageServer::Host.new + host = described_class.new file = File.join(dir, 'test.rb') File.write(file, "foo = 'foo'") host.prepare dir @@ -52,14 +54,14 @@ end it 'cancels requests' do - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.cancel 1 expect(host.cancel?(1)).to be(true) end it 'runs diagnostics on opened files' do Dir.mktmpdir do |dir| - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.configure({ 'diagnostics' => true }) file = File.join(dir, 'test.rb') File.write(file, "foo = 'foo'") @@ -82,12 +84,10 @@ end it 'handles DiagnosticsErrors' do - host = Solargraph::LanguageServer::Host.new + host = described_class.new library = instance_double(Solargraph::Library) allow(library).to receive(:diagnose).and_raise(Solargraph::DiagnosticsError) - allow(library).to receive(:contain?).and_return(true) - allow(library).to receive(:synchronized?).and_return(true) - allow(library).to receive(:mapped?).and_return(true) + allow(library).to receive_messages(contain?: true, synchronized?: true, mapped?: true) allow(library).to receive(:attach) allow(library).to receive(:merge) allow(library).to receive(:catalog) @@ -102,7 +102,7 @@ end it 'opens multiple folders' do - host = Solargraph::LanguageServer::Host.new + host = described_class.new app1_folder = File.absolute_path('spec/fixtures/workspace_folders/folder1').gsub('\\', '/') app2_folder = File.absolute_path('spec/fixtures/workspace_folders/folder2').gsub('\\', '/') host.prepare(app1_folder) @@ -120,7 +120,7 @@ end it 'stops' do - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.stop expect(host.stopped?).to be(true) end @@ -129,7 +129,7 @@ dir = File.absolute_path('spec/fixtures/workspace') file = File.join(dir, 'lib', 'thing.rb') file_uri = Solargraph::LanguageServer::UriHelpers.uri_to_file(file) - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.prepare(dir) host.open(file_uri, File.read(file), 1) host.remove(dir) @@ -139,7 +139,7 @@ end it 'responds with empty diagnostics for unopened files' do - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.diagnose 'file:///file.rb' response = host.flush json = JSON.parse(response.lines.last) @@ -148,7 +148,7 @@ end it 'rescues runtime errors from messages' do - host = Solargraph::LanguageServer::Host.new + host = described_class.new message_class = Class.new(Solargraph::LanguageServer::Message::Base) do def process raise 'Always raise an error from this message' @@ -165,7 +165,7 @@ def process end it 'ignores invalid messages' do - host = Solargraph::LanguageServer::Host.new + host = described_class.new expect do host.receive({ 'bad' => 'message' }) end.not_to raise_error @@ -174,7 +174,7 @@ def process it 'repairs simple breaking changes without incremental sync' do file = '/test.rb' uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.prepare '' host.open uri, 'Foo::Bar', 1 sleep 0.1 until host.libraries.all?(&:mapped?) @@ -206,7 +206,7 @@ def initialize(foo); end file = '/test.rb' uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.prepare '' host.open uri, code, 1 sleep 0.1 until host.libraries.all?(&:mapped?) @@ -234,26 +234,26 @@ def initialize(foo); end describe '#references_from' do it 'rescues FileNotFound errors' do - host = Solargraph::LanguageServer::Host.new + host = described_class.new expect { host.references_from('file:///not_a_file.rb', 1, 1) }.not_to raise_error end it 'logs FileNotFound errors' do allow(Solargraph.logger).to receive(:warn) - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.references_from('file:///not_a_file.rb', 1, 1) expect(Solargraph.logger).to have_received(:warn).with(/FileNotFoundError/) end it 'rescues InvalidOffset errors' do - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.open('file:///file.rb', 'class Foo; end', 1) expect { host.references_from('file:///file.rb', 0, 100) }.not_to raise_error end it 'logs InvalidOffset errors' do allow(Solargraph.logger).to receive(:warn) - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.open('file:///file.rb', 'class Foo; end', 1) host.references_from('file:///file.rb', 0, 100) expect(Solargraph.logger).to have_received(:warn).with(/InvalidOffsetError/) @@ -262,7 +262,7 @@ def initialize(foo); end describe 'Workspace variations' do before do - @host = Solargraph::LanguageServer::Host.new + @host = described_class.new end after do diff --git a/spec/language_server/message/completion_item/resolve_spec.rb b/spec/language_server/message/completion_item/resolve_spec.rb index f7f347f16..9e270b4e9 100644 --- a/spec/language_server/message/completion_item/resolve_spec.rb +++ b/spec/language_server/message/completion_item/resolve_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::CompletionItem::Resolve do it 'returns MarkupContent for documentation' do pin = Solargraph::Pin::Method.new( @@ -10,9 +12,9 @@ parameters: [] ) host = instance_double(Solargraph::LanguageServer::Host, locate_pins: [pin], options: { 'enablePages' => true }) - resolve = Solargraph::LanguageServer::Message::CompletionItem::Resolve.new(host, { - 'params' => pin.completion_item - }) + resolve = described_class.new(host, { + 'params' => pin.completion_item + }) resolve.process expect(resolve.result[:documentation][:kind]).to eq('markdown') expect(resolve.result[:documentation][:value]).to include('A method') @@ -26,9 +28,9 @@ comments: '' ) host = instance_double(Solargraph::LanguageServer::Host, locate_pins: [pin]) - resolve = Solargraph::LanguageServer::Message::CompletionItem::Resolve.new(host, { - 'params' => pin.completion_item - }) + resolve = described_class.new(host, { + 'params' => pin.completion_item + }) resolve.process expect(resolve.result[:documentation]).to be_nil end diff --git a/spec/language_server/message/extended/check_gem_version_spec.rb b/spec/language_server/message/extended/check_gem_version_spec.rb index 3f5dd6850..26023f505 100644 --- a/spec/language_server/message/extended/check_gem_version_spec.rb +++ b/spec/language_server/message/extended/check_gem_version_spec.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::Extended::CheckGemVersion do before do version = instance_double(Gem::Version, version: Gem::Version.new('1.0.0')) - Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = + described_class.fetcher = instance_double(Gem::SpecFetcher, search_for_dependency: [version]) end after do - Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = nil + described_class.fetcher = nil end it 'checks the gem source' do @@ -35,7 +37,7 @@ it 'responds to update actions' do host = Solargraph::LanguageServer::Host.new - message = Solargraph::LanguageServer::Message::Extended::CheckGemVersion.new(host, {}, current: Gem::Version.new('0.0.1')) + message = described_class.new(host, {}, current: Gem::Version.new('0.0.1')) message.process response = nil reader = Solargraph::LanguageServer::Transport::DataReader.new @@ -55,7 +57,7 @@ it 'uses bundler' do host = Solargraph::LanguageServer::Host.new host.configure({ 'useBundler' => true }) - message = Solargraph::LanguageServer::Message::Extended::CheckGemVersion.new(host, {}, current: Gem::Version.new('0.0.1')) + message = described_class.new(host, {}, current: Gem::Version.new('0.0.1')) message.process response = nil reader = Solargraph::LanguageServer::Transport::DataReader.new diff --git a/spec/language_server/message/initialize_spec.rb b/spec/language_server/message/initialize_spec.rb index 877add68f..aae61cff7 100644 --- a/spec/language_server/message/initialize_spec.rb +++ b/spec/language_server/message/initialize_spec.rb @@ -1,22 +1,24 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::Initialize do it 'prepares workspace folders' do host = Solargraph::LanguageServer::Host.new dir = File.realpath(File.join('spec', 'fixtures', 'workspace')) - init = Solargraph::LanguageServer::Message::Initialize.new(host, { - 'params' => { - 'capabilities' => { - 'workspace' => { - 'workspaceFolders' => true - } - }, - 'workspaceFolders' => [ - { - 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(dir), - 'name' => 'workspace' - } - ] - } - }) + init = described_class.new(host, { + 'params' => { + 'capabilities' => { + 'workspace' => { + 'workspaceFolders' => true + } + }, + 'workspaceFolders' => [ + { + 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(dir), + 'name' => 'workspace' + } + ] + } + }) init.process expect(host.folders.length).to eq(1) end @@ -24,16 +26,16 @@ it 'prepares rootUri as a workspace' do host = Solargraph::LanguageServer::Host.new dir = File.realpath(File.join('spec', 'fixtures', 'workspace')) - init = Solargraph::LanguageServer::Message::Initialize.new(host, { - 'params' => { - 'capabilities' => { - 'workspace' => { - 'workspaceFolders' => true - } - }, - 'rootUri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(dir) - } - }) + init = described_class.new(host, { + 'params' => { + 'capabilities' => { + 'workspace' => { + 'workspaceFolders' => true + } + }, + 'rootUri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(dir) + } + }) init.process expect(host.folders.length).to eq(1) end @@ -41,23 +43,23 @@ it 'prepares rootPath as a workspace' do host = Solargraph::LanguageServer::Host.new dir = File.realpath(File.join('spec', 'fixtures', 'workspace')) - init = Solargraph::LanguageServer::Message::Initialize.new(host, { - 'params' => { - 'capabilities' => { - 'workspace' => { - 'workspaceFolders' => true - } - }, - 'rootPath' => dir - } - }) + init = described_class.new(host, { + 'params' => { + 'capabilities' => { + 'workspace' => { + 'workspaceFolders' => true + } + }, + 'rootPath' => dir + } + }) init.process expect(host.folders.length).to eq(1) end it 'returns the default capabilities' do host = Solargraph::LanguageServer::Host.new - init = Solargraph::LanguageServer::Message::Initialize.new(host, {}) + init = described_class.new(host, {}) init.process result = init.result expect(result).to include(:capabilities) @@ -82,15 +84,15 @@ it 'returns all capabilities when all options are enabled' do host = Solargraph::LanguageServer::Host.new - init = Solargraph::LanguageServer::Message::Initialize.new(host, { - 'params' => { - 'initializationOptions' => { - 'completion' => true, - 'autoformat' => true, - 'formatting' => true - } - } - }) + init = described_class.new(host, { + 'params' => { + 'initializationOptions' => { + 'completion' => true, + 'autoformat' => true, + 'formatting' => true + } + } + }) init.process result = init.result diff --git a/spec/language_server/message/text_document/definition_spec.rb b/spec/language_server/message/text_document/definition_spec.rb index 1266e4aa0..d84d23cbe 100644 --- a/spec/language_server/message/text_document/definition_spec.rb +++ b/spec/language_server/message/text_document/definition_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::TextDocument::Definition do it 'prepares empty directory' do Dir.mktmpdir do |dir| @@ -11,7 +13,7 @@ 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 + message = described_class .new(host, { 'params' => { 'textDocument' => { @@ -35,17 +37,17 @@ host.catalog file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/other.rb')) other_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/thing.rb')) - message = Solargraph::LanguageServer::Message::TextDocument::Definition.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => file_uri - }, - 'position' => { - 'line' => 4, - 'character' => 10 - } - } - }) + message = described_class.new(host, { + 'params' => { + 'textDocument' => { + 'uri' => file_uri + }, + 'position' => { + 'line' => 4, + 'character' => 10 + } + } + }) message.process expect(message.result.first[:uri]).to eq(other_uri) end @@ -56,19 +58,19 @@ host.prepare(path) sleep 0.1 until host.libraries.all?(&:mapped?) host.catalog - message = Solargraph::LanguageServer::Message::TextDocument::Definition.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(File.join( - path, 'lib', 'other.rb' - )) - }, - 'position' => { - 'line' => 0, - 'character' => 10 - } - } - }) + message = described_class.new(host, { + 'params' => { + 'textDocument' => { + 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(File.join( + path, 'lib', 'other.rb' + )) + }, + 'position' => { + 'line' => 0, + 'character' => 10 + } + } + }) message.process expect(message.result.first[:uri]).to eq(Solargraph::LanguageServer::UriHelpers.file_to_uri(File.join(path, 'lib', 'thing.rb'))) diff --git a/spec/language_server/message/text_document/formatting_spec.rb b/spec/language_server/message/text_document/formatting_spec.rb index cf68f3cf3..65c0841d7 100644 --- a/spec/language_server/message/text_document/formatting_spec.rb +++ b/spec/language_server/message/text_document/formatting_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::TextDocument::Formatting do it 'gracefully handles empty files' do host = instance_double(Solargraph::LanguageServer::Host, read_text: '', formatter_config: {}) @@ -8,7 +10,7 @@ } } } - message = Solargraph::LanguageServer::Message::TextDocument::Formatting.new(host, request) + message = described_class.new(host, request) message.process expect(message.process.first[:newText]).to be_empty end diff --git a/spec/language_server/message/text_document/hover_spec.rb b/spec/language_server/message/text_document/hover_spec.rb index 93f05c127..76b3c9082 100644 --- a/spec/language_server/message/text_document/hover_spec.rb +++ b/spec/language_server/message/text_document/hover_spec.rb @@ -1,20 +1,22 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::TextDocument::Hover do it 'returns nil for empty documentation' do host = Solargraph::LanguageServer::Host.new host.prepare('spec/fixtures/workspace') sleep 0.1 until host.libraries.all?(&:mapped?) host.catalog - message = Solargraph::LanguageServer::Message::TextDocument::Hover.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => 'file://spec/fixtures/workspace/lib/other.rb' - }, - 'position' => { - 'line' => 5, - 'character' => 0 - } - } - }) + message = described_class.new(host, { + 'params' => { + 'textDocument' => { + 'uri' => 'file://spec/fixtures/workspace/lib/other.rb' + }, + 'position' => { + 'line' => 5, + 'character' => 0 + } + } + }) message.process expect(message.result).to be_nil end @@ -29,17 +31,17 @@ def foo host = Solargraph::LanguageServer::Host.new host.open('file:///test.rb', code, 1) host.catalog - message = Solargraph::LanguageServer::Message::TextDocument::Hover.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => 'file:///test.rb' - }, - 'position' => { - 'line' => 4, - 'character' => 6 - } - } - }) + message = described_class.new(host, { + 'params' => { + 'textDocument' => { + 'uri' => 'file:///test.rb' + }, + 'position' => { + 'line' => 4, + 'character' => 6 + } + } + }) message.process expect(message.result[:contents][:value]).to eq("x\n\n`=~ String`") end diff --git a/spec/language_server/message/text_document/rename_spec.rb b/spec/language_server/message/text_document/rename_spec.rb index 900752e63..19903eaf9 100644 --- a/spec/language_server/message/text_document/rename_spec.rb +++ b/spec/language_server/message/text_document/rename_spec.rb @@ -10,20 +10,20 @@ class Foo foo = Foo.new ), 1) sleep 0.01 until host.libraries.all?(&:mapped?) - rename = Solargraph::LanguageServer::Message::TextDocument::Rename.new(host, { - 'id' => 1, - 'method' => 'textDocument/rename', - 'params' => { - 'textDocument' => { - 'uri' => 'file:///file.rb' - }, - 'position' => { - 'line' => 1, - 'character' => 12 - }, - 'newName' => 'Bar' - } - }) + rename = described_class.new(host, { + 'id' => 1, + 'method' => 'textDocument/rename', + 'params' => { + 'textDocument' => { + 'uri' => 'file:///file.rb' + }, + 'position' => { + 'line' => 1, + 'character' => 12 + }, + 'newName' => 'Bar' + } + }) rename.process expect(rename.result[:changes]['file:///file.rb'].length).to eq(2) end @@ -40,20 +40,20 @@ def foo(bar) end ), 1) - rename = Solargraph::LanguageServer::Message::TextDocument::Rename.new(host, { - 'id' => 1, - 'method' => 'textDocument/rename', - 'params' => { - 'textDocument' => { - 'uri' => 'file:///file.rb' - }, - 'position' => { - 'line' => 2, - 'character' => 14 - }, - 'newName' => 'baz' - } - }) + rename = described_class.new(host, { + 'id' => 1, + 'method' => 'textDocument/rename', + 'params' => { + 'textDocument' => { + 'uri' => 'file:///file.rb' + }, + 'position' => { + 'line' => 2, + 'character' => 14 + }, + 'newName' => 'baz' + } + }) rename.process expect(rename.result[:changes]['file:///file.rb'].length).to eq(3) end @@ -69,20 +69,20 @@ def foo(bar) end end ), 1) - rename = Solargraph::LanguageServer::Message::TextDocument::Rename.new(host, { - 'id' => 1, - 'method' => 'textDocument/rename', - 'params' => { - 'textDocument' => { - 'uri' => 'file:///file.rb' - }, - 'position' => { - 'line' => 3, - 'character' => 6 - }, - 'newName' => 'baz' - } - }) + rename = described_class.new(host, { + 'id' => 1, + 'method' => 'textDocument/rename', + 'params' => { + 'textDocument' => { + 'uri' => 'file:///file.rb' + }, + 'position' => { + 'line' => 3, + 'character' => 6 + }, + 'newName' => 'baz' + } + }) rename.process expect(rename.result[:changes]['file:///file.rb'].length).to eq(3) end @@ -96,20 +96,20 @@ class Namespace::ExampleClass end obj = Namespace::ExampleClass.new ), 1) - rename = Solargraph::LanguageServer::Message::TextDocument::Rename.new(host, { - 'id' => 1, - 'method' => 'textDocument/rename', - 'params' => { - 'textDocument' => { - 'uri' => 'file:///file.rb' - }, - 'position' => { - 'line' => 2, - 'character' => 12 - }, - 'newName' => 'Nameplace' - } - }) + rename = described_class.new(host, { + 'id' => 1, + 'method' => 'textDocument/rename', + 'params' => { + 'textDocument' => { + 'uri' => 'file:///file.rb' + }, + 'position' => { + 'line' => 2, + 'character' => 12 + }, + 'newName' => 'Nameplace' + } + }) rename.process changes = rename.result[:changes]['file:///file.rb'] expect(changes.length).to eq(3) diff --git a/spec/language_server/message/text_document/type_definition_spec.rb b/spec/language_server/message/text_document/type_definition_spec.rb index 7fdd9ce6f..16f7f3006 100644 --- a/spec/language_server/message/text_document/type_definition_spec.rb +++ b/spec/language_server/message/text_document/type_definition_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::TextDocument::TypeDefinition do it 'finds definitions of methods' do host = Solargraph::LanguageServer::Host.new @@ -6,17 +8,17 @@ host.catalog file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/other.rb')) something_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/something.rb')) - message = Solargraph::LanguageServer::Message::TextDocument::TypeDefinition.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => file_uri - }, - 'position' => { - 'line' => 4, - 'character' => 10 - } - } - }) + message = described_class.new(host, { + 'params' => { + 'textDocument' => { + 'uri' => file_uri + }, + 'position' => { + 'line' => 4, + 'character' => 10 + } + } + }) message.process expect(message.result.first[:uri]).to eq(something_uri) end diff --git a/spec/language_server/message/workspace/did_change_configuration_spec.rb b/spec/language_server/message/workspace/did_change_configuration_spec.rb index 0a082875d..a352a5c85 100644 --- a/spec/language_server/message/workspace/did_change_configuration_spec.rb +++ b/spec/language_server/message/workspace/did_change_configuration_spec.rb @@ -71,8 +71,8 @@ described_class.new(host, message).process expect( - host.registered?('textDocument/completion') - ).to be_truthy, 'Expected textDocument/completion to be registered' + host + ).to be_registered('textDocument/completion'), 'Expected textDocument/completion to be registered' end end @@ -83,8 +83,8 @@ described_class.new(host, message).process expect( - host.registered?('textDocument/completion') - ).to be_falsy, 'Expected textDocument/completion to not be registered' + host + ).not_to be_registered('textDocument/completion'), 'Expected textDocument/completion to not be registered' end end end diff --git a/spec/language_server/message/workspace/did_change_watched_files_spec.rb b/spec/language_server/message/workspace/did_change_watched_files_spec.rb index 52e224533..ebe76fc50 100644 --- a/spec/language_server/message/workspace/did_change_watched_files_spec.rb +++ b/spec/language_server/message/workspace/did_change_watched_files_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'tmpdir' describe Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles do @@ -8,17 +10,17 @@ file = File.join(dir, 'foo.rb') File.write file, 'class Foo; end' uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) - changed = Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles.new(host, { - 'method' => 'workspace/didChangeWatchedFiles', - 'params' => { - 'changes' => [ - { - 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::CREATED, - 'uri' => uri - } - ] - } - }) + changed = described_class.new(host, { + 'method' => 'workspace/didChangeWatchedFiles', + 'params' => { + 'changes' => [ + { + 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::CREATED, + 'uri' => uri + } + ] + } + }) changed.process expect(host.synchronizing?).to be(false) expect(host.library_for(uri)).to be_a(Solargraph::Library) @@ -33,17 +35,17 @@ uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) host = Solargraph::LanguageServer::Host.new host.prepare dir - changed = Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles.new(host, { - 'method' => 'workspace/didChangeWatchedFiles', - 'params' => { - 'changes' => [ - { - 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::DELETED, - 'uri' => uri - } - ] - } - }) + changed = described_class.new(host, { + 'method' => 'workspace/didChangeWatchedFiles', + 'params' => { + 'changes' => [ + { + 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::DELETED, + 'uri' => uri + } + ] + } + }) changed.process expect(host.synchronizing?).to be(false) expect do @@ -60,17 +62,17 @@ host = Solargraph::LanguageServer::Host.new host.prepare dir File.write file, 'class FooBar; end' - changed = Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles.new(host, { - 'method' => 'workspace/didChangeWatchedFiles', - 'params' => { - 'changes' => [ - { - 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::CHANGED, - 'uri' => uri - } - ] - } - }) + changed = described_class.new(host, { + 'method' => 'workspace/didChangeWatchedFiles', + 'params' => { + 'changes' => [ + { + 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::CHANGED, + 'uri' => uri + } + ] + } + }) changed.process expect(host.synchronizing?).to be(false) library = host.library_for(uri) @@ -84,17 +86,17 @@ host = instance_double(Solargraph::LanguageServer::Host, catalog: nil) allow(host).to receive(:create) allow(host).to receive(:delete) - changed = Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles.new(host, { - 'method' => 'workspace/didChangeWatchedFiles', - 'params' => { - 'changes' => [ - { - 'type' => -1, - 'uri' => 'file:///foo.rb' - } - ] - } - }) + changed = described_class.new(host, { + 'method' => 'workspace/didChangeWatchedFiles', + 'params' => { + 'changes' => [ + { + 'type' => -1, + 'uri' => 'file:///foo.rb' + } + ] + } + }) changed.process expect(changed.error).not_to be_nil end diff --git a/spec/language_server/message_spec.rb b/spec/language_server/message_spec.rb index 4dca484d5..504e66d07 100644 --- a/spec/language_server/message_spec.rb +++ b/spec/language_server/message_spec.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message do it 'returns MethodNotFound for unregistered methods' do - msg = Solargraph::LanguageServer::Message.select 'notARealMethod' + msg = described_class.select 'notARealMethod' expect(msg).to be(Solargraph::LanguageServer::Message::MethodNotFound) end it 'returns MethodNotImplemented for unregistered $ methods' do - msg = Solargraph::LanguageServer::Message.select '$/notARealMethod' + msg = described_class.select '$/notARealMethod' expect(msg).to be(Solargraph::LanguageServer::Message::MethodNotImplemented) end end diff --git a/spec/language_server/protocol_spec.rb b/spec/language_server/protocol_spec.rb index 49f9bdea8..25764e6eb 100644 --- a/spec/language_server/protocol_spec.rb +++ b/spec/language_server/protocol_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Protocol attr_reader :response @@ -34,7 +36,7 @@ def stop describe Protocol do before :all do - @protocol = Protocol.new(Solargraph::LanguageServer::Host.new) + @protocol = described_class.new(Solargraph::LanguageServer::Host.new) end after :all do @@ -163,7 +165,7 @@ def bar baz } response = @protocol.response expect(response['error']).to be_nil - expect(response['result']['items'].length > 0).to be(true) + expect(!response['result']['items'].empty?).to be(true) end it 'handles completionItem/resolve' do diff --git a/spec/language_server/transport/adapter_spec.rb b/spec/language_server/transport/adapter_spec.rb index 6ec360395..23d6ac123 100644 --- a/spec/language_server/transport/adapter_spec.rb +++ b/spec/language_server/transport/adapter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AdapterTester include Solargraph::LanguageServer::Transport::Adapter diff --git a/spec/language_server/transport/data_reader_spec.rb b/spec/language_server/transport/data_reader_spec.rb index bf9bbcc15..179f14a24 100644 --- a/spec/language_server/transport/data_reader_spec.rb +++ b/spec/language_server/transport/data_reader_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Transport::DataReader do it 'rescues exceptions for invalid JSON' do - reader = Solargraph::LanguageServer::Transport::DataReader.new + reader = described_class.new handled = 0 reader.set_message_handler do |_msg| handled += 1 diff --git a/spec/language_server/uri_helpers_spec.rb b/spec/language_server/uri_helpers_spec.rb index 002a10d1c..038dca0bc 100644 --- a/spec/language_server/uri_helpers_spec.rb +++ b/spec/language_server/uri_helpers_spec.rb @@ -1,37 +1,39 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::UriHelpers do it "doesn't escapes colons in file paths" do file = 'c:/one/two' - uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) + uri = described_class.file_to_uri(file) expect(uri).to start_with('file:///c:') end it 'uses %20 for spaces' do file = '/path/to/a file' - uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) + uri = described_class.file_to_uri(file) expect(uri).to end_with('a%20file') end it 'removes file:// prefix' do uri = 'file:///dev_tools/' - file = Solargraph::LanguageServer::UriHelpers.uri_to_file(uri) + file = described_class.uri_to_file(uri) expect(file).to eq('/dev_tools/') end it 'removes file: prefix' do uri = 'file:/dev_tools/' - file = Solargraph::LanguageServer::UriHelpers.uri_to_file(uri) + file = described_class.uri_to_file(uri) expect(file).to eq('/dev_tools/') end it 'removes file:/// prefix when a drive is specified' do uri = 'file:///Z:/dev_tools/' - file = Solargraph::LanguageServer::UriHelpers.uri_to_file(uri) + file = described_class.uri_to_file(uri) expect(file).to eq('Z:/dev_tools/') end it 'removes file:/ prefix when a drive is specified' do uri = 'file:/Z:/dev_tools/' - file = Solargraph::LanguageServer::UriHelpers.uri_to_file(uri) + file = described_class.uri_to_file(uri) expect(file).to eq('Z:/dev_tools/') end end diff --git a/spec/library_spec.rb b/spec/library_spec.rb index 60e1c9316..9f9ab87dc 100644 --- a/spec/library_spec.rb +++ b/spec/library_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'tmpdir' require 'yard' @@ -8,7 +10,7 @@ workspace_path = File.realpath(temp_dir_path) file_path = File.join(workspace_path, 'file.rb') File.write(file_path, 'a = b') - library = Solargraph::Library.load(workspace_path) + library = described_class.load(workspace_path) result = library.create(file_path, File.read(file_path)) expect(result).to be(true) expect(library.open?(file_path)).to be(false) @@ -16,7 +18,7 @@ end it 'returns a Completion' do - library = Solargraph::Library.new + library = described_class.new library.attach Solargraph::Source.load_string(%( x = 1 x @@ -32,8 +34,8 @@ end it 'returns a Completion', time_limit_seconds: 50 do - library = Solargraph::Library.new(Solargraph::Workspace.new(Dir.pwd, - Solargraph::Workspace::Config.new)) + library = described_class.new(Solargraph::Workspace.new(Dir.pwd, + Solargraph::Workspace::Config.new)) library.attach Solargraph::Source.load_string(%( require 'backport' @@ -57,8 +59,8 @@ def foo(adapter) end it 'returns a Completion' do - library = Solargraph::Library.new(Solargraph::Workspace.new(Dir.pwd, - Solargraph::Workspace::Config.new)) + library = described_class.new(Solargraph::Workspace.new(Dir.pwd, + Solargraph::Workspace::Config.new)) library.attach Solargraph::Source.load_string(%( require 'backport' @@ -74,7 +76,7 @@ def foo(adapter) end it 'gets definitions from a file' do - library = Solargraph::Library.new + library = described_class.new src = Solargraph::Source.load_string %( class Foo def bar @@ -87,7 +89,7 @@ def bar end it 'gets type definitions from a file' do - library = Solargraph::Library.new + library = described_class.new src = Solargraph::Source.load_string %( class Bar; end class Foo @@ -103,7 +105,7 @@ def self.bar end it 'signifies method arguments' do - library = Solargraph::Library.new + library = described_class.new src = Solargraph::Source.load_string %( class Foo def bar baz, key: '' @@ -118,7 +120,7 @@ def bar baz, key: '' end it 'ignores invalid filenames in create_from_disk' do - library = Solargraph::Library.new + library = described_class.new filename = 'not_a_real_file.rb' expect(library.create_from_disk(filename)).to be(false) expect(library.contain?(filename)).to be(false) @@ -128,7 +130,7 @@ def bar baz, key: '' Dir.mktmpdir do |temp_dir_path| # Ensure we resolve any symlinks to their real path workspace_path = File.realpath(temp_dir_path) - library = Solargraph::Library.load(workspace_path) + library = described_class.load(workspace_path) file_path = File.join(workspace_path, 'created.rb') File.write(file_path, "puts 'hello'") expect(library.create_from_disk(file_path)).to be(true) @@ -138,7 +140,7 @@ def bar baz, key: '' it 'ignores non-mergeable files in create_from_disk' do Dir.mktmpdir do |dir| - library = Solargraph::Library.load(dir) + library = described_class.load(dir) filename = File.join(dir, 'created.txt') File.write(filename, "puts 'hello'") expect(library.create_from_disk(filename)).to be(false) @@ -147,7 +149,7 @@ def bar baz, key: '' end it 'diagnoses files' do - library = Solargraph::Library.new + library = described_class.new src = Solargraph::Source.load_string(%( puts 'hello' ), 'file.rb', 0) @@ -162,7 +164,7 @@ def bar baz, key: '' 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 + library = described_class.new workspace src = Solargraph::Source.load_string(%( puts 'hello' ), 'file.rb', 0) @@ -172,7 +174,7 @@ def bar baz, key: '' end it 'documents symbols' do - library = Solargraph::Library.new + library = described_class.new src = Solargraph::Source.load_string(%( class Foo def bar @@ -189,7 +191,7 @@ def bar 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) + library = described_class.new(workspace) src1 = Solargraph::Source.load_string(%( Foo.new ), 'file1.rb', 0) @@ -206,7 +208,7 @@ def bar it 'collects references to a new method to a constant from assignment' do workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) + library = described_class.new(workspace) src1 = Solargraph::Source.load_string(%( Foo.new ), 'file1.rb', 0) @@ -225,7 +227,7 @@ class Foo it 'collects references to an instance method symbol' do workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) + library = described_class.new(workspace) src1 = Solargraph::Source.load_string(%( class Foo def bar @@ -252,7 +254,7 @@ def bar; end it 'collects references to a class method symbol' do workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) + library = described_class.new(workspace) src1 = Solargraph::Source.load_string(%( class Foo def self.bar @@ -287,7 +289,7 @@ def bar; end it 'collects stripped references to constant symbols' do workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) + library = described_class.new(workspace) src1 = Solargraph::Source.load_string(%( class Foo def bar @@ -310,13 +312,13 @@ class Other 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') + expect(code[o1..(o2 - 1)]).to eq('Foo') end end it 'rejects new references from different classes' do workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) + library = described_class.new(workspace) source = Solargraph::Source.load_string(%( class Foo def bar @@ -334,20 +336,20 @@ def bar end it 'searches the core for queries' do - library = Solargraph::Library.new + library = described_class.new result = library.search('String') expect(result).not_to be_empty end it 'returns YARD documentation from the core' do - library = Solargraph::Library.new + library = described_class.new _, 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 + library = described_class.new src = Solargraph::Source.load_string(%( class Foo # My bar method @@ -361,7 +363,7 @@ def bar; end end it 'synchronizes sources from updaters' do - library = Solargraph::Library.new + library = described_class.new src = Solargraph::Source.load_string(%( class Foo end @@ -382,7 +384,7 @@ def bar; end end it 'finds unique references' do - library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + library = described_class.new(Solargraph::Workspace.new('*')) src1 = Solargraph::Source.load_string(%( class Foo end @@ -398,7 +400,7 @@ class Foo end it 'includes method parameters in references' do - library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + library = described_class.new(Solargraph::Workspace.new('*')) source = Solargraph::Source.load_string(%( class Foo def bar(baz) @@ -414,7 +416,7 @@ def bar(baz) end it "lies about names when client can't handle the truth" do - library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + library = described_class.new(Solargraph::Workspace.new('*')) source = Solargraph::Source.load_string(%( class Foo def 🤦🏻foo♀️; 123; end @@ -426,7 +428,7 @@ def 🤦🏻foo♀️; 123; end end it 'tells the truth about names when client can handle the truth' do - library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + library = described_class.new(Solargraph::Workspace.new('*')) source = Solargraph::Source.load_string(%( class Foo def 🤦🏻foo♀️; 123; end @@ -438,7 +440,7 @@ def 🤦🏻foo♀️; 123; end end it 'includes block parameters in references' do - library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + library = described_class.new(Solargraph::Workspace.new('*')) source = Solargraph::Source.load_string(%( 100.times do |foo| puts foo @@ -453,7 +455,7 @@ def 🤦🏻foo♀️; 123; end end it 'defines YARD tags' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class TaggedExample end @@ -475,7 +477,7 @@ def foo; end end it 'defines YARD tags with nested namespaces' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class Tagged class Example; end @@ -495,7 +497,7 @@ def foo; end end it 'defines generic YARD tags' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class TaggedExample; end class CallerExample @@ -509,7 +511,7 @@ def foo; end end it 'defines multiple YARD tags' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class TaggedExample; end class CallerExample @@ -523,7 +525,7 @@ def foo; end end it 'skips comment text outside of tags' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( # String def foo; end @@ -534,7 +536,7 @@ def foo; end end it 'marks aliases as methods or attributes in completion items' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class Example attr_reader :foo @@ -557,7 +559,7 @@ def baz end it 'marks aliases as methods or attributes in definitions' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class Example attr_reader :foo @@ -576,7 +578,7 @@ def bar; end end it 'detaches current source with nil' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class Example attr_reader :foo @@ -594,7 +596,7 @@ def bar; end describe '#locate_ref' do it 'returns nil without a matching reference location' do workspace = File.absolute_path(File.join('spec', 'fixtures', 'workspace')) - library = Solargraph::Library.load(workspace) + library = described_class.load(workspace) library.map! location = Solargraph::Location.new(File.join(workspace, 'app.rb'), Solargraph::Range.from_to(0, 8, 0, 8)) found = library.locate_ref(location) @@ -605,7 +607,7 @@ def bar; end describe '#delete' do it 'removes files from Library#source_map_hash' do workspace = File.absolute_path(File.join('spec', 'fixtures', 'workspace')) - library = Solargraph::Library.load(workspace) + library = described_class.load(workspace) library.map! library.catalog other_file = File.absolute_path(File.join('spec', 'fixtures', 'workspace', 'lib', 'other.rb')) @@ -623,7 +625,7 @@ def bar; end end context 'when unsynchronized' do - let(:library) { Solargraph::Library.load File.absolute_path(File.join('spec', 'fixtures', 'workspace')) } + let(:library) { described_class.load File.absolute_path(File.join('spec', 'fixtures', 'workspace')) } let(:good_file) { File.join(library.workspace.directory, 'lib', 'thing.rb') } let(:bad_file) { File.join(library.workspace.directory, 'lib', 'not_a_thing.rb') } diff --git a/spec/logging_spec.rb b/spec/logging_spec.rb index 0c200594b..7dcd52000 100644 --- a/spec/logging_spec.rb +++ b/spec/logging_spec.rb @@ -1,15 +1,17 @@ +# frozen_string_literal: true + require 'tempfile' describe Solargraph::Logging do it 'logs messages with levels' do file = Tempfile.new('log') - Solargraph::Logging.logger.reopen file - Solargraph::Logging.logger.warn 'Test' + described_class.logger.reopen file + described_class.logger.warn 'Test' file.rewind msg = file.read file.close file.unlink - Solargraph::Logging.logger.reopen File::NULL + described_class.logger.reopen File::NULL expect(msg).to include('WARN') end end diff --git a/spec/parser/node_chainer_spec.rb b/spec/parser/node_chainer_spec.rb index 1c1213564..8f24eb335 100644 --- a/spec/parser/node_chainer_spec.rb +++ b/spec/parser/node_chainer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe 'NodeChainer' do def chain_string str Solargraph::Parser.chain_string(str, 'file.rb', 0) diff --git a/spec/parser/node_methods_spec.rb b/spec/parser/node_methods_spec.rb index 63dd2bfa7..1ead0a6b6 100644 --- a/spec/parser/node_methods_spec.rb +++ b/spec/parser/node_methods_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # These tests are deliberately generic because they apply to both the Legacy # and Rubyvm node methods. describe Solargraph::Parser::NodeMethods do @@ -7,66 +9,66 @@ def parse source it 'unpacks constant nodes into strings' do ast = parse('Foo::Bar') - expect(Solargraph::Parser::NodeMethods.unpack_name(ast)).to eq 'Foo::Bar' + expect(described_class.unpack_name(ast)).to eq 'Foo::Bar' end it 'infers literal strings' do ast = parse("x = 'string'") - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::String' + expect(described_class.infer_literal_node_type(ast.children[1])).to eq '::String' end it 'infers literal hashes' do ast = parse('x = {}') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Hash' + expect(described_class.infer_literal_node_type(ast.children[1])).to eq '::Hash' end it 'infers literal arrays' do ast = parse('x = []') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Array' + expect(described_class.infer_literal_node_type(ast.children[1])).to eq '::Array' end it 'infers literal integers' do ast = parse('x = 100') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Integer' + expect(described_class.infer_literal_node_type(ast.children[1])).to eq '::Integer' end it 'infers literal floats' do ast = parse('x = 10.1') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Float' + expect(described_class.infer_literal_node_type(ast.children[1])).to eq '::Float' end it 'infers literal symbols' do ast = parse(':symbol') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' + expect(described_class.infer_literal_node_type(ast)).to eq '::Symbol' end it 'infers double quoted symbols' do ast = parse(':"symbol"') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' + expect(described_class.infer_literal_node_type(ast)).to eq '::Symbol' end it 'infers interpolated double quoted symbols' do - ast = parse(':"#{Object}"') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' + ast = parse(%(:"#{Object}")) + expect(described_class.infer_literal_node_type(ast)).to eq '::Symbol' end it 'infers single quoted symbols' do ast = parse(":'symbol'") - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' + expect(described_class.infer_literal_node_type(ast)).to eq '::Symbol' end it 'infers literal booleans' do true_ast = parse('true') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(true_ast)).to eq '::Boolean' + expect(described_class.infer_literal_node_type(true_ast)).to eq '::Boolean' false_ast = parse('false') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(false_ast)).to eq '::Boolean' + expect(described_class.infer_literal_node_type(false_ast)).to eq '::Boolean' end it 'handles empty return nodes with implicit nil values' do node = parse(%( return if true )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) # @todo Should there be two returns, the second being nil? expect(rets.map(&:to_s)).to eq(['(nil)', '(nil)']) # The expectation is changing from previous versions. If conditions @@ -78,7 +80,7 @@ def parse source node = parse(%( return bla if true )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) # Two returns, the second being implicit nil expect(rets.length).to eq(2) end @@ -90,7 +92,7 @@ def parse source true end )) - returns = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + returns = described_class.returns_from_method_body(node) # Include an implicit `nil` for missing else expect(returns.map(&:to_s)).to eq(['(true)', '(nil)']) end @@ -114,7 +116,7 @@ def parse source end end )) - returns = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + returns = described_class.returns_from_method_body(node) expect(returns.length).to eq(6) expect(returns.map(&:to_s)).to eq(['(true)', '(int 73)', '(false)', '(nil)', '(false)', '(true)']) end @@ -128,7 +130,7 @@ def parse source false end )) - returns = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + returns = described_class.returns_from_method_body(node) expect(returns.length).to eq(2) end @@ -140,7 +142,7 @@ def parse source return if true end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(2) end @@ -150,23 +152,23 @@ def parse source return x if foo y )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(2) end it "handles nested 'and' nodes" do node = parse('return 1 && "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(1) expect(rets[0].type.to_s.downcase).to eq('and') end it "handles nested 'or' nodes" do node = parse('return 1 || "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(2) - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(rets[0])).to eq('::Integer') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(rets[1])).to eq('::String') + expect(described_class.infer_literal_node_type(rets[0])).to eq('::Integer') + expect(described_class.infer_literal_node_type(rets[1])).to eq('::String') end it 'finds return nodes in blocks' do @@ -175,7 +177,7 @@ def parse source return item if foo end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq(%i[block lvar]) end @@ -186,7 +188,7 @@ def parse source '123' end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:str]) end @@ -199,7 +201,7 @@ def parse source end nil )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq(%i[lvar nil]) end @@ -207,7 +209,7 @@ def parse source node = parse(%( return bla if true )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq(%i[send nil]) end @@ -216,7 +218,7 @@ def parse source x = 1 return x )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:lvar]) end @@ -226,7 +228,7 @@ def parse source return x y )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(1) end @@ -237,7 +239,7 @@ def parse source raise "Error" y )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(0) end @@ -247,7 +249,7 @@ def parse source raise "Error" if foo y )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(1) end @@ -257,7 +259,7 @@ def parse source return "Error" if foo y )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(2) end @@ -268,31 +270,31 @@ def parse source y end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(1) end it "handles top 'and' nodes" do node = parse('1 && "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:and]) end it "handles top 'or' nodes" do node = parse('1 || "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:or]) end it "handles nested 'and' nodes from return" do node = parse('return 1 && "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:and]) end it "handles nested 'or' nodes from return" do node = parse('return 1 || "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq(%i[int str]) end @@ -304,7 +306,7 @@ def parse source "" end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq(%i[str str]) end @@ -315,7 +317,7 @@ def parse source "" end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq(%i[str nil]) end @@ -328,20 +330,20 @@ def parse source super end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq(%i[send zsuper]) end describe 'convert_hash' do it 'converts literal hash arguments' do node = parse('{foo: :bar}') - hash = Solargraph::Parser::NodeMethods.convert_hash(node) + hash = described_class.convert_hash(node) expect(hash.keys).to eq([:foo]) end it 'ignores call arguments' do node = parse('some_call') - hash = Solargraph::Parser::NodeMethods.convert_hash(node) + hash = described_class.convert_hash(node) expect(hash).to eq({}) end end @@ -355,7 +357,7 @@ def super_with_block end end )) - calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + calls = described_class.call_nodes_from(source.node) expect(calls).to be_one end @@ -363,7 +365,7 @@ def super_with_block source = Solargraph::Source.load_string(%( Foo.new.bar('string') )) - calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + calls = described_class.call_nodes_from(source.node) expect(calls.length).to eq(2) end @@ -371,7 +373,7 @@ def super_with_block source = Solargraph::Source.load_string(%( [ Foo.new.bar('string') ] )) - calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + calls = described_class.call_nodes_from(source.node) expect(calls.length).to eq(2) end @@ -379,7 +381,7 @@ def super_with_block source = Solargraph::Source.load_string(%( [ Foo.new.bar('string') ].compact )) - calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + calls = described_class.call_nodes_from(source.node) expect(calls.length).to eq(3) end @@ -395,7 +397,7 @@ def something end end )) - calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + calls = described_class.call_nodes_from(source.node) expect(calls.length).to eq(2) end end diff --git a/spec/parser/node_processor_spec.rb b/spec/parser/node_processor_spec.rb index 6785b9a09..da7031779 100644 --- a/spec/parser/node_processor_spec.rb +++ b/spec/parser/node_processor_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Parser::NodeProcessor do def parse source Solargraph::Parser.parse(source, 'file.rb', 0) @@ -10,7 +12,7 @@ class Foo end )) expect do - Solargraph::Parser::NodeProcessor.process(node) + described_class.process(node) end.not_to raise_error end @@ -18,7 +20,7 @@ class Foo node = parse(%( def foo(bar = nil, baz = nil); end )) - pins, = Solargraph::Parser::NodeProcessor.process(node) + pins, = described_class.process(node) # Method pin is first pin after default namespace pin = pins[1] expect(pin.parameters.map(&:name)).to eq(%w[bar baz]) @@ -30,7 +32,7 @@ def foo(bar = nil, baz = nil); end detail += "foo" detail.strip! )) - _, vars = Solargraph::Parser::NodeProcessor.process(node) + _, vars = described_class.process(node) # ensure we parsed the += correctly and won't report an unexpected # nil assignment @@ -55,17 +57,17 @@ def process end end - Solargraph::Parser::NodeProcessor.register(:def, dummy_processor1) - Solargraph::Parser::NodeProcessor.register(:def, dummy_processor2) + described_class.register(:def, dummy_processor1) + described_class.register(:def, dummy_processor2) node = parse(%( def some_method; end )) - pins, = Solargraph::Parser::NodeProcessor.process(node) + pins, = described_class.process(node) # empty namespace pin is root namespace expect(pins.map(&:name)).to contain_exactly('', 'foo', 'bar', 'some_method') # Clean up the registered processors - Solargraph::Parser::NodeProcessor.deregister(:def, dummy_processor1) - Solargraph::Parser::NodeProcessor.deregister(:def, dummy_processor2) + described_class.deregister(:def, dummy_processor1) + described_class.deregister(:def, dummy_processor2) end end diff --git a/spec/parser_spec.rb b/spec/parser_spec.rb index 1757bb90d..62fe7d955 100644 --- a/spec/parser_spec.rb +++ b/spec/parser_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Parser do def parse source Solargraph::Parser.parse(source, 'file.rb', 0) @@ -5,7 +7,7 @@ def parse source it 'parses nodes' do node = parse('class Foo; end') - expect(Solargraph::Parser.is_ast_node?(node)).to be(true) + expect(described_class.is_ast_node?(node)).to be(true) end it 'raises repairable SyntaxError for unknown encoding errors' do diff --git a/spec/pin/base_spec.rb b/spec/pin/base_spec.rb index 97de966df..4d4315040 100644 --- a/spec/pin/base_spec.rb +++ b/spec/pin/base_spec.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Base do let(:zero_location) { Solargraph::Location.new('test.rb', Solargraph::Range.from_to(0, 0, 0, 0)) } let(:one_location) { Solargraph::Location.new('test.rb', Solargraph::Range.from_to(0, 0, 1, 0)) } it 'does not combine pins with directive changes' do - pin1 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: 'A Foo class', - source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) - pin2 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@!macro my_macro', - source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) + pin1 = described_class.new(location: zero_location, name: 'Foo', comments: 'A Foo class', + source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) + pin2 = described_class.new(location: zero_location, name: 'Foo', comments: '@!macro my_macro', + source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) expect(pin1.nearly?(pin2)).to be(false) # enable asserts with_env_var('SOLARGRAPH_ASSERTS', 'on') do @@ -15,10 +17,10 @@ end it 'does not combine pins with different directives' do - pin1 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@!macro my_macro', - source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) - pin2 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@!macro other', - source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) + pin1 = described_class.new(location: zero_location, name: 'Foo', comments: '@!macro my_macro', + source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) + pin2 = described_class.new(location: zero_location, name: 'Foo', comments: '@!macro other', + source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) expect(pin1.nearly?(pin2)).to be(false) with_env_var('SOLARGRAPH_ASSERTS', 'on') do expect { pin1.combine_with(pin2) }.to raise_error(RuntimeError, /Inconsistent :macros values/) @@ -26,26 +28,26 @@ end it 'sees tag differences as not near or equal' do - pin1 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@return [Foo]') - pin2 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@return [Bar]') + pin1 = described_class.new(location: zero_location, name: 'Foo', comments: '@return [Foo]') + pin2 = described_class.new(location: zero_location, name: 'Foo', comments: '@return [Bar]') expect(pin1.nearly?(pin2)).to be(false) expect(pin1 == pin2).to be(false) end it 'sees comment differences as nearly but not equal' do - pin1 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: 'A Foo class') - pin2 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: 'A different Foo') + pin1 = described_class.new(location: zero_location, name: 'Foo', comments: 'A Foo class') + pin2 = described_class.new(location: zero_location, name: 'Foo', comments: 'A different Foo') expect(pin1.nearly?(pin2)).to be(true) expect(pin1 == pin2).to be(false) end it 'recognizes deprecated tags' do - pin = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@deprecated Use Bar instead.') + pin = described_class.new(location: zero_location, name: 'Foo', comments: '@deprecated Use Bar instead.') expect(pin).to be_deprecated end it 'does not link documentation for undefined return types' do - pin = Solargraph::Pin::Base.new(name: 'Foo', comments: '@return [undefined]') + pin = described_class.new(name: 'Foo', comments: '@return [undefined]') expect(pin.link_documentation).to eq('Foo') end diff --git a/spec/pin/base_variable_spec.rb b/spec/pin/base_variable_spec.rb index f5d60c03a..9ca63f3dd 100644 --- a/spec/pin/base_variable_spec.rb +++ b/spec/pin/base_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Pin::BaseVariable do it 'checks assignments for equality' do smap = Solargraph::SourceMap.load_string('foo = "foo"') diff --git a/spec/pin/block_spec.rb b/spec/pin/block_spec.rb index d6fc051e9..7d7d1b028 100644 --- a/spec/pin/block_spec.rb +++ b/spec/pin/block_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Block do let(:foo) { instance_double(Solargraph::Pin::Parameter, name: 'foo') } let(:bar) { instance_double(Solargraph::Pin::Parameter, name: 'bar') } let(:block) { instance_double(Solargraph::Pin::Parameter, name: 'block') } it 'strips prefixes from parameter names' do - pin = Solargraph::Pin::Block.new(args: [foo, bar, block]) + pin = described_class.new(args: [foo, bar, block]) expect(pin.parameter_names).to eq(%w[foo bar block]) end end diff --git a/spec/pin/class_variable_spec.rb b/spec/pin/class_variable_spec.rb index c327f01a1..686b13177 100644 --- a/spec/pin/class_variable_spec.rb +++ b/spec/pin/class_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # describe Solargraph::Pin::ClassVariable do # it "always has class scope" do # source = Solargraph::Source.load_string(%( diff --git a/spec/pin/constant_spec.rb b/spec/pin/constant_spec.rb index 124c36174..116c72b2a 100644 --- a/spec/pin/constant_spec.rb +++ b/spec/pin/constant_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Constant do it 'resolves constant paths' do source = Solargraph::Source.new(%( diff --git a/spec/pin/delegated_method_spec.rb b/spec/pin/delegated_method_spec.rb index a20fc26d4..7063d42c0 100644 --- a/spec/pin/delegated_method_spec.rb +++ b/spec/pin/delegated_method_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'pry' describe Solargraph::Pin::DelegatedMethod do it 'can be constructed from a Method pin' do method_pin = Solargraph::Pin::Method.new(comments: '@return [Hash{String => String}]') - delegation_pin = Solargraph::Pin::DelegatedMethod.new(method: method_pin, scope: :instance) + delegation_pin = described_class.new(method: method_pin, scope: :instance) expect(delegation_pin.return_type.to_s).to eq('Hash{String => String}') end @@ -26,7 +28,7 @@ def collaborator; end class2 = api_map.get_path_pins('Class2').first chain = Solargraph::Source::Chain.new([Solargraph::Source::Chain::Call.new('collaborator', nil)]) - pin = Solargraph::Pin::DelegatedMethod.new( + pin = described_class.new( closure: class2, scope: :instance, name: 'name', diff --git a/spec/pin/documenting_spec.rb b/spec/pin/documenting_spec.rb index 42da36dd1..cfecb0468 100644 --- a/spec/pin/documenting_spec.rb +++ b/spec/pin/documenting_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Documenting do let(:object) do Class.new do diff --git a/spec/pin/instance_variable_spec.rb b/spec/pin/instance_variable_spec.rb index c9fd036d7..e1b08e028 100644 --- a/spec/pin/instance_variable_spec.rb +++ b/spec/pin/instance_variable_spec.rb @@ -1,14 +1,16 @@ +# frozen_string_literal: true + describe Solargraph::Pin::InstanceVariable do it 'is a kind of variable' do source = Solargraph::Source.load_string("@foo = 'foo'", 'file.rb') map = Solargraph::SourceMap.map(source) - pin = map.pins.select { |p| p.is_a?(Solargraph::Pin::InstanceVariable) }.first + pin = map.pins.select { |p| p.is_a?(described_class) }.first expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::VARIABLE) expect(pin.symbol_kind).to eq(Solargraph::LanguageServer::SymbolKinds::VARIABLE) end it 'does not link documentation for undefined return types' do - pin = Solargraph::Pin::InstanceVariable.new(name: '@bar') + pin = described_class.new(name: '@bar') expect(pin.link_documentation).to eq('@bar') end end diff --git a/spec/pin/keyword_spec.rb b/spec/pin/keyword_spec.rb index 99e51f287..4ffb9d34c 100644 --- a/spec/pin/keyword_spec.rb +++ b/spec/pin/keyword_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Keyword do it 'is a kind of keyword' do - pin = Solargraph::Pin::Keyword.new('foo') + pin = described_class.new('foo') expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end end diff --git a/spec/pin/local_variable_spec.rb b/spec/pin/local_variable_spec.rb index 28481bf4d..d9e8590f9 100644 --- a/spec/pin/local_variable_spec.rb +++ b/spec/pin/local_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Pin::LocalVariable do it 'merges presence changes so that [not currently used]' do pending 'but not sure why' diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index 1469d1c3d..c109746af 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Method do it 'tracks code parameters' do source = Solargraph::Source.new(%( @@ -33,7 +35,7 @@ def foo bar:, baz: MyClass.new )) map = Solargraph::SourceMap.map(source) pin = map.pins.select { |pin| pin.path == '#foo' }.first - expect(pin.class).to eq(Solargraph::Pin::Method) + expect(pin.class).to eq(described_class) method_pin = pin expect(method_pin.signatures.length).to eq(1) method_signature = method_pin.signatures.first @@ -53,7 +55,7 @@ def foo bar:, baz: MyClass.new @param two [Second] description2 COMMENTS # pin = source.pins.select{|pin| pin.path == 'Foo#bar'}.first - pin = Solargraph::Pin::Method.new(comments: comments) + pin = described_class.new(comments: comments) expect(pin.documentation).to include('one') expect(pin.documentation).to include('[First]') expect(pin.documentation).to include('description1') @@ -77,9 +79,9 @@ def bazzle; end COMMENTS map = Solargraph::SourceMap.map(source) bazzle = map.pins.select { |pin| pin.path == 'Bar::Baz#bazzle' }.first - expect(bazzle.return_type.rooted?).to eq(false) + expect(bazzle.return_type.rooted?).to be(false) bing = map.pins.select { |pin| pin.path == 'Bar::Baz#bing' }.first - expect(bing.return_type.rooted?).to eq(true) + expect(bing.return_type.rooted?).to be(true) end it 'includes yieldparam tags in documentation' do @@ -87,7 +89,7 @@ def bazzle; end @yieldparam one [First] description1 @yieldparam two [Second] description2 COMMENTS - pin = Solargraph::Pin::Method.new(comments: comments) + pin = described_class.new(comments: comments) expect(pin.documentation).to include('one') expect(pin.documentation).to include('[First]') expect(pin.documentation).to include('description1') @@ -101,37 +103,37 @@ def bazzle; end @yieldreturn [YRet] yretdescription @return [String] COMMENTS - pin = Solargraph::Pin::Method.new(comments: comments) + pin = described_class.new(comments: comments) expect(pin.documentation).to include('YRet') expect(pin.documentation).to include('yretdescription') end it 'detects return types from tags' do - pin = Solargraph::Pin::Method.new(comments: '@return [Hash]') + pin = described_class.new(comments: '@return [Hash]') expect(pin.return_type.tag).to eq('Hash') end it 'ignores malformed return tags' do - pin = Solargraph::Pin::Method.new(name: 'bar', comments: '@return [Array) @@ -419,7 +421,7 @@ def bar?; end end it 'supports multiple return tags' do - pin = Solargraph::Pin::Method.new( + pin = described_class.new( name: 'foo', comments: %( @return [String] @@ -430,7 +432,7 @@ def bar?; end end it 'includes @return text in documentation' do - pin = Solargraph::Pin::Method.new( + pin = described_class.new( name: 'foo', comments: %( @return [String] the foo text string @@ -440,7 +442,7 @@ def bar?; end end it 'includes @example text in documentation' do - pin = Solargraph::Pin::Method.new( + pin = described_class.new( name: 'foo', comments: %( @example @@ -452,7 +454,7 @@ def bar?; end end it 'includes @example names' do - pin = Solargraph::Pin::Method.new( + pin = described_class.new( name: 'foo', comments: %( @example Call foo @@ -514,32 +516,32 @@ class Foo end )) map = Solargraph::SourceMap.map(source) - pin = map.pins.select { |p| p.is_a?(Solargraph::Pin::Method) }.first + pin = map.pins.select { |p| p.is_a?(described_class) }.first expect(pin).to be_attribute expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::PROPERTY) expect(pin.symbol_kind).to eq(Solargraph::LanguageServer::SymbolKinds::PROPERTY) end it 'uses return type tags' do - pin = Solargraph::Pin::Method.new(name: 'bar', comments: '@return [File]', attribute: true) + pin = described_class.new(name: 'bar', comments: '@return [File]', attribute: true) expect(pin.return_type.tag).to eq('File') end it 'detects undefined types' do - pin = Solargraph::Pin::Method.new(name: 'bar', attribute: true) + pin = described_class.new(name: 'bar', attribute: true) expect(pin.return_type).to be_undefined end it 'generates paths' do npin = Solargraph::Pin::Namespace.new(name: 'Foo', type: :class) - ipin = Solargraph::Pin::Method.new(closure: npin, name: 'bar', attribute: true, scope: :instance) + ipin = described_class.new(closure: npin, name: 'bar', attribute: true, scope: :instance) expect(ipin.path).to eq('Foo#bar') - cpin = Solargraph::Pin::Method.new(closure: npin, name: 'bar', attribute: true, scope: :class) + cpin = described_class.new(closure: npin, name: 'bar', attribute: true, scope: :class) expect(cpin.path).to eq('Foo.bar') end it 'handles invalid return type tags' do - pin = Solargraph::Pin::Method.new(name: 'bar', comments: '@return [Array<]', attribute: true) + pin = described_class.new(name: 'bar', comments: '@return [Array<]', attribute: true) expect(pin.return_type).to be_undefined end @@ -626,7 +628,7 @@ def bar end it 'ignores malformed overload tags' do - pin = Solargraph::Pin::Method.new(name: 'example', comments: "@overload\n @param") + pin = described_class.new(name: 'example', comments: "@overload\n @param") expect(pin.overloads).to be_empty end end diff --git a/spec/pin/namespace_spec.rb b/spec/pin/namespace_spec.rb index 0421def05..cafadcc58 100644 --- a/spec/pin/namespace_spec.rb +++ b/spec/pin/namespace_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Namespace do it 'handles long namespaces' do - pin = Solargraph::Pin::Namespace.new(closure: Solargraph::Pin::Namespace.new(name: 'Foo'), name: 'Bar') + pin = described_class.new(closure: described_class.new(name: 'Foo'), name: 'Bar') expect(pin.path).to eq('Foo::Bar') end @@ -9,26 +11,26 @@ class Foo end )) - pin = Solargraph::Pin::Namespace.new(name: 'Foo') + pin = described_class.new(name: 'Foo') expect(pin.context.scope).to eq(:class) end it 'is a kind of namespace/class/module' do - pin1 = Solargraph::Pin::Namespace.new(name: 'Foo') + pin1 = described_class.new(name: 'Foo') expect(pin1.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::CLASS) - pin2 = Solargraph::Pin::Namespace.new(name: 'Foo', type: :module) + pin2 = described_class.new(name: 'Foo', type: :module) expect(pin2.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::MODULE) end it 'handles nested namespaces inside closures' do - pin = Solargraph::Pin::Namespace.new(closure: Solargraph::Pin::Namespace.new(name: 'Foo'), name: 'Bar::Baz') + pin = described_class.new(closure: described_class.new(name: 'Foo'), name: 'Bar::Baz') expect(pin.namespace).to eq('Foo::Bar') expect(pin.name).to eq('Baz') expect(pin.path).to eq('Foo::Bar::Baz') end it 'uses @param tags as generic type parameters' do - pin = Solargraph::Pin::Namespace.new(name: 'Foo', comments: '@generic GenericType') + pin = described_class.new(name: 'Foo', comments: '@generic GenericType') expect(pin.generics).to eq(['GenericType']) expect(pin.to_rbs).to eq('class ::Foo[GenericType]') end diff --git a/spec/pin/parameter_spec.rb b/spec/pin/parameter_spec.rb index 8605bd441..971acc10e 100644 --- a/spec/pin/parameter_spec.rb +++ b/spec/pin/parameter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Parameter do it 'detects block parameter return types from @yieldparam tags' do api_map = Solargraph::ApiMap.new @@ -195,8 +197,8 @@ def baz; end it 'uses longer comment while combining compatible parameters' do loc = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(0, 0, 0, 0)) block = Solargraph::Pin::Block.new(location: loc, name: 'Foo') - pin1 = Solargraph::Pin::Parameter.new(closure: block, name: 'bar') - pin2 = Solargraph::Pin::Parameter.new(closure: block, name: 'bar', comments: 'a comment') + pin1 = described_class.new(closure: block, name: 'bar') + pin2 = described_class.new(closure: block, name: 'bar', comments: 'a comment') expect(pin1.combine_with(pin2).comments).to eq('a comment') end @@ -207,7 +209,7 @@ def baz; end ), 'test.rb') api_map = Solargraph::ApiMap.new api_map.map source - pin = api_map.source_map('test.rb').locals.select { |p| p.is_a?(Solargraph::Pin::Parameter) }.first + pin = api_map.source_map('test.rb').locals.select { |p| p.is_a?(described_class) }.first # expect(pin.infer(api_map)).to be_undefined expect(pin.typify(api_map)).to be_undefined expect(pin.probe(api_map)).to be_undefined diff --git a/spec/pin/search_spec.rb b/spec/pin/search_spec.rb index 28c302839..f505f45e9 100644 --- a/spec/pin/search_spec.rb +++ b/spec/pin/search_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Search do it 'returns ordered matches on paths' do example_class = Solargraph::Pin::Namespace.new(name: 'Example') @@ -6,7 +8,7 @@ Solargraph::Pin::Method.new(name: 'foobar', closure: example_class), Solargraph::Pin::Method.new(name: 'foo_bar', closure: example_class) ] - search = Solargraph::Pin::Search.new(pins, 'example') + search = described_class.new(pins, 'example') expect(search.results).to eq(pins) end @@ -17,7 +19,7 @@ Solargraph::Pin::Method.new(name: 'foobar', closure: example_class), Solargraph::Pin::Method.new(name: 'foo_bar', closure: example_class) ] - search = Solargraph::Pin::Search.new(pins, 'foobar') + search = described_class.new(pins, 'foobar') expect(search.results.map(&:path)).to eq(['Example.foobar', 'Example.foo_bar']) end @@ -28,7 +30,7 @@ Solargraph::Pin::Method.new(name: 'foobar', closure: example_class), Solargraph::Pin::Method.new(name: 'bazquz', closure: example_class) ] - search = Solargraph::Pin::Search.new(pins, 'foobar') + search = described_class.new(pins, 'foobar') expect(search.results.map(&:path)).to eq(['Example.foobar']) end end diff --git a/spec/pin/symbol_spec.rb b/spec/pin/symbol_spec.rb index ce841a399..585c8b5b1 100644 --- a/spec/pin/symbol_spec.rb +++ b/spec/pin/symbol_spec.rb @@ -1,53 +1,55 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Symbol do context 'when an unquoted literal' do it 'is a kind of keyword to the LSP' do - pin = Solargraph::Pin::Symbol.new(nil, ':symbol') + pin = described_class.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') + pin = described_class.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') + pin = described_class.new(nil, ':symbol') expect(pin.return_type.tag).to eq('Symbol') end end context 'when a double quoted literal' do it 'is a kind of keyword' do - pin = Solargraph::Pin::Symbol.new(nil, ':"symbol"') + pin = described_class.new(nil, ':"symbol"') expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end it 'has a Symbol return type' do - pin = Solargraph::Pin::Symbol.new(nil, ':"symbol"') + pin = described_class.new(nil, ':"symbol"') expect(pin.return_type.tag).to eq('Symbol') end end context 'when a double quoted interpolated literal' do it 'is a kind of keyword' do - pin = Solargraph::Pin::Symbol.new(nil, ':"symbol #{variable}"') + pin = described_class.new(nil, ':"symbol #{variable}"') expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end it 'has a Symbol return type' do - pin = Solargraph::Pin::Symbol.new(nil, ':"symbol #{variable}"') + pin = described_class.new(nil, ':"symbol #{variable}"') expect(pin.return_type.tag).to eq('Symbol') end end context 'when a single quoted literal' do it 'is a kind of keyword' do - pin = Solargraph::Pin::Symbol.new(nil, ":'symbol'") + pin = described_class.new(nil, ":'symbol'") expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end it 'has a Symbol return type' do - pin = Solargraph::Pin::Symbol.new(nil, ":'symbol'") + pin = described_class.new(nil, ":'symbol'") expect(pin.return_type.tag).to eq('Symbol') end end diff --git a/spec/position_spec.rb b/spec/position_spec.rb index 5612e8a5e..d61b05ce5 100644 --- a/spec/position_spec.rb +++ b/spec/position_spec.rb @@ -1,49 +1,51 @@ +# frozen_string_literal: true + describe Solargraph::Position do it 'normalizes arrays into positions' do - pos = Solargraph::Position.normalize([0, 1]) - expect(pos).to be_a(Solargraph::Position) + pos = described_class.normalize([0, 1]) + expect(pos).to be_a(described_class) expect(pos.line).to eq(0) expect(pos.column).to eq(1) end it 'returns original positions when normalizing' do - orig = Solargraph::Position.new(0, 1) - norm = Solargraph::Position.normalize(orig) + orig = described_class.new(0, 1) + norm = described_class.normalize(orig) expect(orig).to be(norm) end it 'finds offset from position' do text = "\n class Foo\n def bar baz, boo = 'boo'\n end\n end\n " - expect(Solargraph::Position.to_offset(text, Solargraph::Position.new(0, 0))).to eq(0) - expect(Solargraph::Position.to_offset(text, Solargraph::Position.new(0, 4))).to eq(4) - expect(Solargraph::Position.to_offset(text, Solargraph::Position.new(2, 12))).to eq(29) - expect(Solargraph::Position.to_offset(text, Solargraph::Position.new(2, 27))).to eq(44) - expect(Solargraph::Position.to_offset(text, Solargraph::Position.new(3, 8))).to eq(58) + expect(described_class.to_offset(text, described_class.new(0, 0))).to eq(0) + expect(described_class.to_offset(text, described_class.new(0, 4))).to eq(4) + expect(described_class.to_offset(text, described_class.new(2, 12))).to eq(29) + expect(described_class.to_offset(text, described_class.new(2, 27))).to eq(44) + expect(described_class.to_offset(text, described_class.new(3, 8))).to eq(58) end it 'constructs position from offset' do text = "\n class Foo\n def bar baz, boo = 'boo'\n end\n end\n " - expect(Solargraph::Position.from_offset(text, 0)).to eq(Solargraph::Position.new(0, 0)) - expect(Solargraph::Position.from_offset(text, 4)).to eq(Solargraph::Position.new(1, 3)) - expect(Solargraph::Position.from_offset(text, 29)).to eq(Solargraph::Position.new(2, 12)) - expect(Solargraph::Position.from_offset(text, 44)).to eq(Solargraph::Position.new(2, 27)) + expect(described_class.from_offset(text, 0)).to eq(described_class.new(0, 0)) + expect(described_class.from_offset(text, 4)).to eq(described_class.new(1, 3)) + expect(described_class.from_offset(text, 29)).to eq(described_class.new(2, 12)) + expect(described_class.from_offset(text, 44)).to eq(described_class.new(2, 27)) end it 'raises an error for objects that cannot be normalized' do expect do - Solargraph::Position.normalize('0, 1') + described_class.normalize('0, 1') end.to raise_error(ArgumentError) end it 'avoids fencepost errors' do text = " class Foo\n def bar baz, boo = 'boo'\n end\n end\n " - offset = Solargraph::Position.to_offset(text, Solargraph::Position.new(3, 6)) + offset = described_class.to_offset(text, described_class.new(3, 6)) expect(offset).to eq(67) end it 'avoids fencepost errors with multiple blank lines' do text = " class Foo\n def bar baz, boo = 'boo'\n\n end\n end\n " - offset = Solargraph::Position.to_offset(text, Solargraph::Position.new(4, 6)) + offset = described_class.to_offset(text, described_class.new(4, 6)) expect(offset).to eq(68) end end diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index ca01d1ae1..e9dc1ccf8 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::RbsMap::Conversions do context 'with RBS to digest' do # create a temporary directory with the scope of the spec @@ -12,7 +14,7 @@ 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) + described_class.new(loader: loader) end let(:api_map) { Solargraph::ApiMap.new } diff --git a/spec/rbs_map/core_map_spec.rb b/spec/rbs_map/core_map_spec.rb index 56e13a8d3..79878c572 100644 --- a/spec/rbs_map/core_map_spec.rb +++ b/spec/rbs_map/core_map_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::RbsMap::CoreMap do it 'maps core Errno classes' do - map = Solargraph::RbsMap::CoreMap.new + map = described_class.new store = Solargraph::ApiMap::Store.new(map.pins) Errno.constants.each do |const| pin = store.get_path_pins("Errno::#{const}").first @@ -12,7 +14,7 @@ end it 'understands RBS class aliases' do - map = Solargraph::RbsMap::CoreMap.new + map = described_class.new store = Solargraph::ApiMap::Store.new(map.pins) # The core RBS contains: # class Mutex = Thread::Mutex @@ -25,7 +27,7 @@ end it 'understands RBS global variables' do - map = Solargraph::RbsMap::CoreMap.new + map = described_class.new store = Solargraph::ApiMap::Store.new(map.pins) global_variable_pins = store.pins_by_class(Solargraph::Pin::GlobalVariable) stderr_pins = global_variable_pins.select do |pin| @@ -79,7 +81,7 @@ # @todo This is a simple smoke test to ensure that mixins are applied # correctly. It would be better to test RbsMap or RbsMap::Conversions # with an RBS fixture. - core_map = Solargraph::RbsMap::CoreMap.new + core_map = described_class.new pins = core_map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.name == '::Enumerable' } expect(pins.map(&:closure).map(&:namespace)).to include('Enumerator') end diff --git a/spec/rbs_map/stdlib_map_spec.rb b/spec/rbs_map/stdlib_map_spec.rb index bb1e33177..00afc6542 100644 --- a/spec/rbs_map/stdlib_map_spec.rb +++ b/spec/rbs_map/stdlib_map_spec.rb @@ -1,18 +1,20 @@ +# frozen_string_literal: true + describe Solargraph::RbsMap::StdlibMap do it 'finds stdlib require paths' do - rbs_map = Solargraph::RbsMap::StdlibMap.load('fileutils') + rbs_map = described_class.load('fileutils') pin = rbs_map.path_pin('FileUtils#chdir') expect(pin).not_to be_nil end it 'maps YAML' do - rbs_map = Solargraph::RbsMap::StdlibMap.load('yaml') + rbs_map = described_class.load('yaml') pin = rbs_map.path_pin('YAML') expect(pin).to be_a(Solargraph::Pin::Base) end it 'processes RBS module aliases' do - map = Solargraph::RbsMap::StdlibMap.load('yaml') + map = described_class.load('yaml') store = Solargraph::ApiMap::Store.new(map.pins) constant_pins = store.get_constants('') yaml_pins = constant_pins.select do |pin| @@ -25,7 +27,7 @@ end it 'pins are marked as coming from RBS parsing' do - map = Solargraph::RbsMap::StdlibMap.load('yaml') + map = described_class.load('yaml') store = Solargraph::ApiMap::Store.new(map.pins) constant_pins = store.get_constants('') pin = constant_pins.first diff --git a/spec/rbs_map_spec.rb b/spec/rbs_map_spec.rb index 4631c9ca5..09e7a1a80 100644 --- a/spec/rbs_map_spec.rb +++ b/spec/rbs_map_spec.rb @@ -1,25 +1,27 @@ +# frozen_string_literal: true + describe Solargraph::RbsMap do it 'loads from a gemspec' do spec = Gem::Specification.find_by_name('rbs') - rbs_map = Solargraph::RbsMap.from_gemspec(spec, nil, nil) + rbs_map = described_class.from_gemspec(spec, nil, nil) pin = rbs_map.path_pin('RBS::EnvironmentLoader#add_collection') 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) + rbs_map = described_class.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') + rbs_map = described_class.new('lskdflaksdfjl') expect(rbs_map.pins).to be_empty end it 'converts constants and aliases to correct types' do spec = Gem::Specification.find_by_name('rbs') - rbs_map = Solargraph::RbsMap.from_gemspec(spec, nil, nil) + rbs_map = described_class.from_gemspec(spec, nil, nil) pin = rbs_map.path_pin('RBS::EnvironmentLoader::DEFAULT_CORE_ROOT') expect(pin.return_type.tag).to eq('Pathname') pin = rbs_map.path_pin('RBS::EnvironmentWalker::InstanceNode') @@ -28,7 +30,7 @@ it 'processes RBS class variables' do spec = Gem::Specification.find_by_name('rbs') - rbs_map = Solargraph::RbsMap.from_gemspec(spec, nil, nil) + rbs_map = described_class.from_gemspec(spec, nil, nil) store = Solargraph::ApiMap::Store.new(rbs_map.pins) class_variable_pins = store.pins_by_class(Solargraph::Pin::ClassVariable) count_pins = class_variable_pins.select do |pin| @@ -41,7 +43,7 @@ it 'processes RBS class instance variables' do spec = Gem::Specification.find_by_name('rbs') - rbs_map = Solargraph::RbsMap.from_gemspec(spec, nil, nil) + rbs_map = described_class.from_gemspec(spec, nil, nil) store = Solargraph::ApiMap::Store.new(rbs_map.pins) instance_variable_pins = store.pins_by_class(Solargraph::Pin::InstanceVariable) root_pins = instance_variable_pins.select do |pin| diff --git a/spec/source/chain/array_spec.rb b/spec/source/chain/array_spec.rb index 34eeafe01..bb93820cd 100644 --- a/spec/source/chain/array_spec.rb +++ b/spec/source/chain/array_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Array do it 'resolves an instance of an array' do literal = described_class.new([], nil) diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index 2cdca3de5..122cc2ed7 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Call do it 'recognizes core methods that return subtypes' do api_map = Solargraph::ApiMap.new diff --git a/spec/source/chain/class_variable_spec.rb b/spec/source/chain/class_variable_spec.rb index 2b01007e7..67a75e0c4 100644 --- a/spec/source/chain/class_variable_spec.rb +++ b/spec/source/chain/class_variable_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::ClassVariable do it 'resolves class variable pins' do foo_pin = Solargraph::Pin::ClassVariable.new(name: '@@foo') bar_pin = Solargraph::Pin::ClassVariable.new(name: '@@bar') api_map = instance_double(Solargraph::ApiMap, get_class_variable_pins: [foo_pin, bar_pin]) - link = Solargraph::Source::Chain::ClassVariable.new('@@bar') + link = described_class.new('@@bar') pins = link.resolve(api_map, Solargraph::Pin::ROOT_PIN, []) expect(pins.length).to eq(1) expect(pins.first.name).to eq('@@bar') diff --git a/spec/source/chain/constant_spec.rb b/spec/source/chain/constant_spec.rb index f809783ef..6fbf54bff 100644 --- a/spec/source/chain/constant_spec.rb +++ b/spec/source/chain/constant_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Constant do it 'resolves constants in the current context' do foo_pin = Solargraph::Pin::Constant.new(name: 'Foo', closure: Solargraph::Pin::ROOT_PIN) diff --git a/spec/source/chain/global_variable_spec.rb b/spec/source/chain/global_variable_spec.rb index 4d0ed4056..ec89fd599 100644 --- a/spec/source/chain/global_variable_spec.rb +++ b/spec/source/chain/global_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::GlobalVariable do it 'resolves instance variable pins' do closure = Solargraph::Pin::Namespace.new(name: 'Foo') @@ -5,7 +7,7 @@ not_pin = Solargraph::Pin::InstanceVariable.new(closure: closure, name: '@bar') api_map = Solargraph::ApiMap.new api_map.index [foo_pin, not_pin] - link = Solargraph::Source::Chain::GlobalVariable.new('$foo') + link = described_class.new('$foo') pins = link.resolve(api_map, Solargraph::ComplexType.parse('Foo'), []) expect(pins.length).to eq(1) expect(pins.first.name).to eq('$foo') diff --git a/spec/source/chain/head_spec.rb b/spec/source/chain/head_spec.rb index 7d4e5d9ea..6526d4747 100644 --- a/spec/source/chain/head_spec.rb +++ b/spec/source/chain/head_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Head do it 'returns self pins' do - head = Solargraph::Source::Chain::Head.new('self') + head = described_class.new('self') npin = Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.parse('Foo')) ipin = head.resolve(nil, npin, []).first expect(ipin.return_type.namespace).to eq('Foo') diff --git a/spec/source/chain/instance_variable_spec.rb b/spec/source/chain/instance_variable_spec.rb index fd956f770..29ef714ee 100644 --- a/spec/source/chain/instance_variable_spec.rb +++ b/spec/source/chain/instance_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::InstanceVariable do it 'resolves instance variable pins' do closure = Solargraph::Pin::Namespace.new(name: 'Foo', @@ -18,8 +20,8 @@ api_map = Solargraph::ApiMap.new api_map.index [closure, methpin, foo_pin, bar_pin] - link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil, - Solargraph::Location.new('test.rb', Solargraph::Range.from_to(2, 2, 2, 3))) + link = described_class.new('@foo', nil, + Solargraph::Location.new('test.rb', Solargraph::Range.from_to(2, 2, 2, 3))) pins = link.resolve(api_map, methpin, []) expect(pins.length).to eq(1) @@ -29,8 +31,8 @@ name_pin = Solargraph::Pin::ProxyType.anonymous(closure.binder, # Closure is the class closure: closure) - link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil, - Solargraph::Location.new('test.rb', Solargraph::Range.from_to(5, 1, 5, 2))) + link = described_class.new('@foo', nil, + Solargraph::Location.new('test.rb', Solargraph::Range.from_to(5, 1, 5, 2))) pins = link.resolve(api_map, name_pin, []) expect(pins.length).to eq(1) expect(pins.first.name).to eq('@foo') diff --git a/spec/source/chain/link_spec.rb b/spec/source/chain/link_spec.rb index c492d0ba3..b303ee5b8 100644 --- a/spec/source/chain/link_spec.rb +++ b/spec/source/chain/link_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Link do it 'is undefined by default' do link = described_class.new diff --git a/spec/source/chain/literal_spec.rb b/spec/source/chain/literal_spec.rb index d3f759ea0..ce75772c5 100644 --- a/spec/source/chain/literal_spec.rb +++ b/spec/source/chain/literal_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Literal do it 'resolves an instance of a literal' do literal = described_class.new('String', nil) diff --git a/spec/source/chain/or_spec.rb b/spec/source/chain/or_spec.rb index 084738fe3..4a36dfe7c 100644 --- a/spec/source/chain/or_spec.rb +++ b/spec/source/chain/or_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Or do it 'handles simple nil-removal' do source = Solargraph::Source.load_string(%( diff --git a/spec/source/chain/q_call_spec.rb b/spec/source/chain/q_call_spec.rb index a63568358..21994fb2d 100644 --- a/spec/source/chain/q_call_spec.rb +++ b/spec/source/chain/q_call_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::QCall do it 'understands &. in chains' do source = Solargraph::Source.load_string(%( diff --git a/spec/source/chain/z_super_spec.rb b/spec/source/chain/z_super_spec.rb index 4739ee195..fc6f5a554 100644 --- a/spec/source/chain/z_super_spec.rb +++ b/spec/source/chain/z_super_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::ZSuper do it 'resolves super' do - head = Solargraph::Source::Chain::ZSuper.new('super') + head = described_class.new('super') npin = Solargraph::Pin::Namespace.new(name: 'Substring') scpin = Solargraph::Pin::Reference::Superclass.new(closure: npin, name: 'String') mpin = Solargraph::Pin::Method.new(closure: npin, name: 'upcase', scope: :instance, visibility: :public) diff --git a/spec/source/chain_spec.rb b/spec/source/chain_spec.rb index 175e07946..4cccd285c 100644 --- a/spec/source/chain_spec.rb +++ b/spec/source/chain_spec.rb @@ -1,25 +1,25 @@ describe Solargraph::Source::Chain do - it 'gets empty definitions for undefined links' do + it "gets empty definitions for undefined links" do chain = described_class.new([Solargraph::Source::Chain::Link.new]) - expect(chain.define(nil, nil, nil)).to be_empty + expect(chain.define(nil, nil, [])).to be_empty end - it 'infers undefined types for undefined links' do + it "infers undefined types for undefined links" do chain = described_class.new([Solargraph::Source::Chain::Link.new]) - expect(chain.infer(nil, nil, nil)).to be_undefined + expect(chain.infer(nil, nil, [])).to be_undefined end - it 'calls itself undefined if any of its links are undefined' do + it "calls itself undefined if any of its links are undefined" do chain = described_class.new([Solargraph::Source::Chain::Link.new]) expect(chain).to be_undefined end - it 'returns undefined bases for single links' do + it "returns undefined bases for single links" do chain = described_class.new([Solargraph::Source::Chain::Link.new]) expect(chain.base).to be_undefined end - it 'defines constants from core classes' do + it "defines constants from core classes" do api_map = Solargraph::ApiMap.new chain = described_class.new([Solargraph::Source::Chain::Constant.new('String')]) pins = chain.define(api_map, Solargraph::Pin::ROOT_PIN, []) @@ -27,7 +27,7 @@ expect(pins.first.path).to eq('String') end - it 'infers types from core classes' do + it "infers types from core classes" do api_map = Solargraph::ApiMap.new chain = described_class.new([Solargraph::Source::Chain::Constant.new('String')]) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, []) @@ -35,26 +35,25 @@ expect(type.scope).to eq(:class) end - it 'infers types from core methods' do + it "infers types from core methods" do api_map = Solargraph::ApiMap.new - chain = described_class.new([Solargraph::Source::Chain::Constant.new('String'), - Solargraph::Source::Chain::Call.new('new', nil)]) + chain = described_class.new([Solargraph::Source::Chain::Constant.new('String'), Solargraph::Source::Chain::Call.new('new', nil)]) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, []) expect(type.namespace).to eq('String') expect(type.scope).to eq(:instance) end - it 'recognizes literals' do + it "recognizes literals" do chain = described_class.new([Solargraph::Source::Chain::Literal.new('String', nil)]) expect(chain.literal?).to be(true) end - it 'recognizes constants' do + it "recognizes constants" do chain = described_class.new([Solargraph::Source::Chain::Constant.new('String')]) expect(chain.constant?).to be(true) end - it 'recognizes unfinished constants' do + it "recognizes unfinished constants" do chain = described_class.new([Solargraph::Source::Chain::Constant.new('String'), Solargraph::Source::Chain::Constant.new('')]) expect(chain.constant?).to be(true) expect(chain.base.constant?).to be(true) @@ -62,7 +61,7 @@ expect(chain.base.undefined?).to be(false) end - it 'infers types from new subclass calls without a subclass initialize method' do + it "infers types from new subclass calls without a subclass initialize method" do code = %( class Sup def initialize; end @@ -81,7 +80,7 @@ def meth; end expect(type.name).to eq('Sub') end - it 'follows constant chains' do + it "follows constant chains" do source = Solargraph::Source.load_string(%( module Mixin; end module Container @@ -96,7 +95,7 @@ class Foo; end expect(pins).to be_empty end - it 'rebases inner constants chains' do + it "rebases inner constants chains" do source = Solargraph::Source.load_string(%( class Foo class Bar; end @@ -106,12 +105,11 @@ class Bar; end api_map = Solargraph::ApiMap.new api_map.map source chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(3, 16)) - pins = chain.define(api_map, - Solargraph::Pin::ProxyType.new(closure: Solargraph::Pin::Namespace.new(name: 'Foo'), return_type: Solargraph::ComplexType.parse('Class')), []) + pins = chain.define(api_map, Solargraph::Pin::ProxyType.new(closure: Solargraph::Pin::Namespace.new(name: 'Foo'), return_type: Solargraph::ComplexType.parse('Class')), []) expect(pins.first.path).to eq('Foo::Bar') end - it 'resolves relative constant paths' do + it "resolves relative constant paths" do source = Solargraph::Source.load_string(%( class Foo class Bar @@ -125,12 +123,11 @@ module Other api_map = Solargraph::ApiMap.new api_map.map source chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(6, 16)) - pins = chain.define(api_map, - Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.parse('Class')), []) + pins = chain.define(api_map, Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.parse('Class')), []) expect(pins.first.path).to eq('Foo::Bar::Baz') end - it 'avoids recursive variable assignments' do + it "avoids recursive variable assignments" do source = Solargraph::Source.load_string(%( @foo = @bar @bar = @foo.quz @@ -138,12 +135,12 @@ module Other api_map = Solargraph::ApiMap.new api_map.map source chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(2, 18)) - expect do + expect { chain.define(api_map, Solargraph::Pin::ROOT_PIN, []) - end.not_to raise_error + }.not_to raise_error end - it 'pulls types from multiple lines of code' do + it "pulls types from multiple lines of code" do source = Solargraph::Source.load_string(%( 123 'abc' @@ -155,7 +152,7 @@ module Other expect(type.simple_tags).to eq('String') end - it 'uses last line of a begin expression as return type' do + it "uses last line of a begin expression as return type" do source = Solargraph::Source.load_string(%( begin 123 @@ -169,7 +166,7 @@ module Other expect(type.simple_tags).to eq('String') end - it 'matches constants on complete symbols' do + it "matches constants on complete symbols" do source = Solargraph::Source.load_string(%( class Correct; end class NotCorrect; end diff --git a/spec/source/change_spec.rb b/spec/source/change_spec.rb index 7ef02984a..b9e019cfd 100644 --- a/spec/source/change_spec.rb +++ b/spec/source/change_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + describe Solargraph::Source::Change do it 'inserts a character' do text = 'var' range = Solargraph::Range.from_to(0, 3, 0, 3) new_text = '.' - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.write(text) expect(updated).to eq('var.') end @@ -12,7 +14,7 @@ text = 'var' range = Solargraph::Range.from_to(0, 3, 0, 3) new_text = '.' - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.write(text, true) expect(updated).to eq('var ') end @@ -21,14 +23,14 @@ text = 'var' range = Solargraph::Range.from_to(0, 3, 0, 3) new_text = '._(!' - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.repair(text) expect(updated).to eq('var ') end it 'repairs nil ranges' do text = 'original' - change = Solargraph::Source::Change.new(nil, '...') + change = described_class.new(nil, '...') updated = change.repair(text) expect(updated).to eq(' ') end @@ -36,7 +38,7 @@ it 'overwrites nil ranges' do text = 'foo' new_text = 'bar' - change = Solargraph::Source::Change.new(nil, new_text) + change = described_class.new(nil, new_text) updated = change.write(text) expect(updated).to eq('bar') end @@ -45,7 +47,7 @@ text = 'bar' new_text = ':' range = Solargraph::Range.from_to(0, 3, 0, 3) - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.write(text, true) expect(updated).to eq('bar ') end @@ -54,7 +56,7 @@ text = 'bar:' new_text = ':' range = Solargraph::Range.from_to(0, 4, 0, 4) - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.write(text, true) expect(updated).to eq('bar ') end @@ -63,7 +65,7 @@ text = 'bar.' new_text = ' ' range = Solargraph::Range.from_to(0, 4, 0, 4) - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.repair(text) expect(updated).to eq('bar ') end @@ -72,7 +74,7 @@ text = 'bar:' new_text = 'x' range = Solargraph::Range.from_to(0, 4, 0, 4) - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.repair(text) expect(updated).to eq('bar ') end diff --git a/spec/source/source_chainer_spec.rb b/spec/source/source_chainer_spec.rb index 61359963f..272cbb6ef 100644 --- a/spec/source/source_chainer_spec.rb +++ b/spec/source/source_chainer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Source::SourceChainer do it 'handles trailing colons that are not namespace separators' do source = Solargraph::Source.load_string('Foo:') @@ -111,14 +113,14 @@ ] ) source = orig.synchronize(updater) - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(0, 3)) + chain = described_class.chain(source, Solargraph::Position.new(0, 3)) expect(chain.links.first).to be_a(Solargraph::Source::Chain::Literal) expect(chain.links.length).to eq(2) end it 'chains incomplete constants' do source = Solargraph::Source.load_string('Foo::') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(0, 5)) + chain = described_class.chain(source, Solargraph::Position.new(0, 5)) expect(chain.links.length).to eq(2) expect(chain.links.first).to be_a(Solargraph::Source::Chain::Constant) expect(chain.links.last).to be_a(Solargraph::Source::Chain::Constant) @@ -132,7 +134,7 @@ ]) source = orig.synchronize(updater) expect do - Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 4)) + described_class.chain(source, Solargraph::Position.new(1, 4)) end.not_to raise_error end @@ -142,11 +144,11 @@ [bb2, 2, 3] {cc3, 2, 3} )) - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 10)) + chain = described_class.chain(source, Solargraph::Position.new(1, 10)) expect(chain.links.first.word).to eq('aa1') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(2, 10)) + chain = described_class.chain(source, Solargraph::Position.new(2, 10)) expect(chain.links.first.word).to eq('bb2') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(3, 10)) + chain = described_class.chain(source, Solargraph::Position.new(3, 10)) expect(chain.links.first.word).to eq('cc3') end @@ -162,7 +164,7 @@ error_ranges: [], node_at: nil, tree_at: []) - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(0, 5)) + chain = described_class.chain(source, Solargraph::Position.new(0, 5)) expect(chain.links.first.word).to eq('@foo') expect(chain.links.last.word).to eq('') end @@ -179,7 +181,7 @@ error_ranges: [], node_at: nil, tree_at: []) - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(0, 6)) + chain = described_class.chain(source, Solargraph::Position.new(0, 6)) expect(chain.links.first.word).to eq('@@foo') expect(chain.links.last.word).to eq('') end @@ -198,7 +200,7 @@ ) ] )) - chain = Solargraph::Source::SourceChainer.chain(source2, Solargraph::Position.new(1, 9)) + chain = described_class.chain(source2, Solargraph::Position.new(1, 9)) expect(chain.links.first).to be_a(Solargraph::Source::Chain::Literal) expect(chain.links.first.word).to eq('<::String>') expect(chain.links.last.word).to eq('') @@ -208,7 +210,7 @@ source = Solargraph::Source.load_string(%( if !t ), 'test.rb') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 11)) + chain = described_class.chain(source, Solargraph::Position.new(1, 11)) expect(chain.links.length).to eq(1) expect(chain.links.first.word).to eq('t') end @@ -228,7 +230,7 @@ it 'handles integers with dots at end of file' do source = Solargraph::Source.load_string('100.') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(0, 4)) + chain = described_class.chain(source, Solargraph::Position.new(0, 4)) expect(chain.links.length).to eq(2) expect(chain.links.first).to be_a(Solargraph::Source::Chain::Literal) expect(chain.links.last).to be_undefined @@ -242,7 +244,7 @@ class Inner end Outer::Inner.new ), 'test.rb') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(5, 13)) + chain = described_class.chain(source, Solargraph::Position.new(5, 13)) expect(chain.links.last.word).to eq('Outer::Inner') end @@ -256,7 +258,7 @@ class Inner2 end Outer::Inner1::Inner2.new ), 'test.rb') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(7, 21)) + chain = described_class.chain(source, Solargraph::Position.new(7, 21)) expect(chain.links.last.word).to eq('Outer::Inner1::Inner2') end @@ -264,7 +266,7 @@ class Inner2 source = Solargraph::Source.load_string(%( foo(*optargs, **kwargs) ), 'test.rb') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 7)) + chain = described_class.chain(source, Solargraph::Position.new(1, 7)) expect(chain.links.last.arguments.length).to eq(2) end @@ -276,7 +278,7 @@ class Inner2 api_map = Solargraph::ApiMap.new api_map.map source - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(2, 9)) + chain = described_class.chain(source, Solargraph::Position.new(2, 9)) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) expect(type.tag).to eq('Array') end @@ -288,7 +290,7 @@ class Inner2 api_map = Solargraph::ApiMap.new api_map.map source - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 16)) + chain = described_class.chain(source, Solargraph::Position.new(1, 16)) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) expect(type.tag).to eq('Array(String, Integer)') end @@ -302,7 +304,7 @@ class Inner2 api_map = Solargraph::ApiMap.new api_map.map source - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(3, 6)) + chain = described_class.chain(source, Solargraph::Position.new(3, 6)) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) expect(type.rooted_tag).to eq('::Boolean') end @@ -314,7 +316,7 @@ class Inner2 api_map = Solargraph::ApiMap.new api_map.map source - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 16)) + chain = described_class.chain(source, Solargraph::Position.new(1, 16)) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) expect(type.tag).to eq('Array') end @@ -334,7 +336,7 @@ def strings; end updated = source.synchronize(updater) api_map = Solargraph::ApiMap.new api_map.map updated - chain = Solargraph::Source::SourceChainer.chain(updated, Solargraph::Position.new(5, 14)) + chain = described_class.chain(updated, Solargraph::Position.new(5, 14)) expect(chain.node.type).to be(:send) expect(chain.node.children[1]).to be(:s) end @@ -345,7 +347,7 @@ def strings; end z end )) - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 9)) + chain = described_class.chain(source, Solargraph::Position.new(1, 9)) expect(chain.links.map(&:class)) .to eq([Solargraph::Source::Chain::Call, Solargraph::Source::Chain::Call]) end @@ -357,7 +359,7 @@ def strings; end api_map = Solargraph::ApiMap.new api_map.map source - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 20)) + chain = described_class.chain(source, Solargraph::Position.new(1, 20)) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) expect(type.tag).to eq('Array') end diff --git a/spec/source/updater_spec.rb b/spec/source/updater_spec.rb index 580d41521..373072e0d 100644 --- a/spec/source/updater_spec.rb +++ b/spec/source/updater_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Source::Updater do it 'applies changes' do text = 'foo' @@ -8,7 +10,7 @@ range = Solargraph::Range.from_to(0, 4, 0, 4) new_text = 'bar' changes.push Solargraph::Source::Change.new(range, new_text) - updater = Solargraph::Source::Updater.new('file.rb', 0, changes) + updater = described_class.new('file.rb', 0, changes) updated = updater.write(text) expect(updated).to eq('foo.bar') end @@ -22,7 +24,7 @@ range = Solargraph::Range.from_to(0, 4, 0, 4) new_text = 'bar' changes.push Solargraph::Source::Change.new(range, new_text) - updater = Solargraph::Source::Updater.new('file.rb', 0, changes) + updater = described_class.new('file.rb', 0, changes) updated = updater.repair(text) expect(updated).to eq('foo ') end @@ -33,7 +35,7 @@ range = nil new_text = 'bar' changes.push Solargraph::Source::Change.new(range, new_text) - updater = Solargraph::Source::Updater.new('file.rb', 0, changes) + updater = described_class.new('file.rb', 0, changes) updated = updater.write(text) expect(updated).to eq('bar') end diff --git a/spec/source_map/clip_spec.rb b/spec/source_map/clip_spec.rb index 9eac9aa25..67b3085b2 100644 --- a/spec/source_map/clip_spec.rb +++ b/spec/source_map/clip_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::SourceMap::Clip do let(:api_map) { Solargraph::ApiMap.new } @@ -356,7 +358,7 @@ def spec_for_require path map.map source clip = map.clip_at('test.rb', Solargraph::Position.new(10, 10)) type = clip.infer - expect(type.to_s.split(',').map(&:strip).to_set).to eq(Set.new(['Gem::Specification'])) + expect(type.to_s.split(',').to_set(&:strip)).to eq(Set.new(['Gem::Specification'])) end it 'infers return types from method calls' do diff --git a/spec/source_map/mapper_spec.rb b/spec/source_map/mapper_spec.rb index 26c9f80b9..02cd17e29 100644 --- a/spec/source_map/mapper_spec.rb +++ b/spec/source_map/mapper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::SourceMap::Mapper do it 'ignores include calls that are not attached to the current namespace' do source = Solargraph::Source.new(%( @@ -1230,7 +1232,7 @@ def bar; end # @!override Foo#bar # return [String] ), 'test.rb') - pins, _locals = Solargraph::SourceMap::Mapper.map(source) + pins, _locals = described_class.map(source) over = pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Override) }.first expect(over.name).to eq('Foo#bar') end @@ -1241,7 +1243,7 @@ class Foo def bar(**baz); end end )) - _pins, locals = Solargraph::SourceMap::Mapper.map(source) + _pins, locals = described_class.map(source) param = locals.select { |pin| pin.is_a?(Solargraph::Pin::Parameter) }.first expect(param).to be_kwrestarg end @@ -1252,7 +1254,7 @@ class Foo def bar(baz = {}); end end )) - _pins, locals = Solargraph::SourceMap::Mapper.map(source) + _pins, locals = described_class.map(source) param = locals.select { |pin| pin.is_a?(Solargraph::Pin::Parameter) }.first expect(param).to be_kwrestarg end @@ -1263,7 +1265,7 @@ def bar(baz = {}); end var = 'var' end )) - _pins, locals = Solargraph::SourceMap::Mapper.map(source) + _pins, locals = described_class.map(source) expect(locals).to be_one end @@ -1283,7 +1285,7 @@ class Foo alias_method end )) - pins, locals = Solargraph::SourceMap::Mapper.map(source) + pins, locals = described_class.map(source) expect(pins).to be_one expect(locals).to be_empty end diff --git a/spec/source_map/node_processor_spec.rb b/spec/source_map/node_processor_spec.rb index 7b8c8ccdc..ae34cd49c 100644 --- a/spec/source_map/node_processor_spec.rb +++ b/spec/source_map/node_processor_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Parser::NodeProcessor do it 'maps arg parameters' do map = Solargraph::SourceMap.load_string(%( diff --git a/spec/source_map_spec.rb b/spec/source_map_spec.rb index 8dba8c0b4..a5fcea154 100644 --- a/spec/source_map_spec.rb +++ b/spec/source_map_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::SourceMap do it 'locates named path pins' do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( class Foo def bar; end end @@ -10,7 +12,7 @@ def bar; end end it 'queries symbols using fuzzy matching' do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( class FooBar def baz_qux; end end @@ -23,7 +25,7 @@ def baz_qux; end end it 'returns all pins, except for references as document symbols' do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( class FooBar require 'foo' include SomeModule @@ -56,7 +58,7 @@ def local source_map Solargraph::Convention.register dummy_convention - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( class FooBar def baz_qux; end end @@ -68,7 +70,7 @@ def baz_qux; end end it 'locates block pins' do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( class Foo 100.times do end @@ -79,7 +81,7 @@ class Foo end it 'scopes local variables correctly from root def methods' do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( x = 'string' def foo x @@ -91,7 +93,7 @@ def foo end it 'scopes local variables correctly from class methods' do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( class Foo x = 'string' def foo @@ -110,7 +112,7 @@ def foo ENV['SOLARGRAPH_ASSERTS'] = 'on' expect do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( Foo.bar += baz ), 'test.rb') loc = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(3, 9, 3, 9)) @@ -127,7 +129,7 @@ def foo ENV['SOLARGRAPH_ASSERTS'] = 'on' expect do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( Foo.bar ||= baz ), 'test.rb') loc = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(3, 9, 3, 9)) @@ -144,7 +146,7 @@ def foo ENV['SOLARGRAPH_ASSERTS'] = 'on' expect do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( Foo.bar = baz ), 'test.rb') loc = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(3, 9, 3, 9)) @@ -156,7 +158,7 @@ def foo end it 'scopes local variables correctly in class_eval blocks' do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( class Foo; end x = 'y' Foo.class_eval do @@ -169,12 +171,12 @@ class Foo; end end it 'updates cached inference when the ApiMap changes' do - file1 = Solargraph::SourceMap.load_string(%( + file1 = described_class.load_string(%( def foo '' end ), 'file1.rb') - file2 = Solargraph::SourceMap.load_string(%( + file2 = described_class.load_string(%( foo ), 'file2.rb') @@ -186,7 +188,7 @@ def foo original_api_map_hash = api_map.hash original_source_map_hash = file1.hash - file1 = Solargraph::SourceMap.load_string(%( + file1 = described_class.load_string(%( def foo [] end diff --git a/spec/source_spec.rb b/spec/source_spec.rb index 53c7ba4d3..6357924f2 100644 --- a/spec/source_spec.rb +++ b/spec/source_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + describe Solargraph::Source do it 'parses code' do code = 'class Foo;def bar;end;end' source = described_class.new(code) expect(source.code).to eq(code) - expect(Solargraph::Parser.is_ast_node?(source.node)).to be_truthy + expect(Solargraph::Parser).to be_is_ast_node(source.node) expect(source).to be_parsed end @@ -101,7 +103,7 @@ def bar end it 'finds references' do - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( class Foo def bar end @@ -124,7 +126,7 @@ def bar= end it 'allows escape sequences incompatible with UTF-8' do - source = Solargraph::Source.new(' + source = described_class.new(' x = " Un bUen café \x92" puts x ') @@ -133,18 +135,18 @@ def bar= it 'fixes invalid byte sequences in UTF-8 encoding' do expect do - Solargraph::Source.load('spec/fixtures/invalid_byte.rb') + described_class.load('spec/fixtures/invalid_byte.rb') end.not_to raise_error end it 'loads files with Unicode characters' do expect do - Solargraph::Source.load('spec/fixtures/unicode.rb') + described_class.load('spec/fixtures/unicode.rb') end.not_to raise_error end it 'updates itself when code does not change' do - original = Solargraph::Source.load_string('x = y', 'test.rb') + original = described_class.load_string('x = y', 'test.rb') updater = Solargraph::Source::Updater.new('test.rb', 1, []) updated = original.synchronize(updater) expect(original).to be(updated) @@ -152,7 +154,7 @@ def bar= end it 'handles unparseable code' do - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( 100.times do |num| )) # @todo Unparseable code results in a nil node for now, but that could @@ -163,7 +165,7 @@ def bar= it 'finds foldable ranges' do # Of the 7 possible ranges, 2 are too short to be foldable - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( =begin Range 1 =end @@ -192,7 +194,7 @@ def range_2 end it 'folds multiline strings' do - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( a = 1 b = 2 c = 3 @@ -207,7 +209,7 @@ def range_2 end it 'folds multiline arrays' do - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( a = 1 b = 2 c = 3 @@ -222,7 +224,7 @@ def range_2 end it 'folds multiline hashes' do - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( a = 1 b = 2 c = 3 @@ -237,7 +239,7 @@ def range_2 end it 'finishes synchronizations for unbalanced lines' do - source1 = Solargraph::Source.load_string('x = 1', 'test.rb') + source1 = described_class.load_string('x = 1', 'test.rb') source2 = source1.synchronize Solargraph::Source::Updater.new( 'test.rb', 2, @@ -254,7 +256,7 @@ def range_2 it 'handles comment arrays that overlap lines' do # Fixes negative argument error (castwide/solargraph#141) - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( =begin =end y = 1 #foo @@ -266,7 +268,7 @@ def range_2 end it 'formats comments with multiple hash prefixes' do - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( ## # one # two @@ -278,7 +280,7 @@ class Foo; end end it 'does not include inner comments' do - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( # included class Foo # ignored @@ -291,12 +293,12 @@ class Foo end it 'handles long squiggly heredocs' do - source = Solargraph::Source.load('spec/fixtures/long_squiggly_heredoc.rb') + source = described_class.load('spec/fixtures/long_squiggly_heredoc.rb') expect(source.string_ranges).not_to be_empty end it 'handles string array substitutions' do - source = Solargraph::Source.load_string( + source = described_class.load_string( '%W[array of words #{\'with a substitution\'}]' ) expect(source.string_ranges.length).to eq(4) @@ -305,6 +307,6 @@ class Foo it 'handles errors in docstrings' do # YARD has a known problem with empty @overload tags comments = "@overload\n@return [String]" - expect { Solargraph::Source.parse_docstring(comments) }.not_to raise_error + expect { described_class.parse_docstring(comments) }.not_to raise_error end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4297bdc99..65d3bb7d4 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'bundler/setup' require 'webmock/rspec' WebMock.disable_net_connect!(allow_localhost: true) diff --git a/spec/type_checker/levels/normal_spec.rb b/spec/type_checker/levels/normal_spec.rb index 4ac006305..21243d161 100644 --- a/spec/type_checker/levels/normal_spec.rb +++ b/spec/type_checker/levels/normal_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::TypeChecker do context 'when checking at normal level' do def type_checker code @@ -609,7 +611,7 @@ def bar end it 'verifies block passes in arguments' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( class Foo def map(&block) block.call(100) @@ -624,7 +626,7 @@ def to_s end it 'verifies args and block passes' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( class Foo def map(x, &block) block.call(x) @@ -639,14 +641,14 @@ def to_s end it 'verifies extra block passes in chained calls' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( ''.to_s(&:nil?) ), 'test.rb') expect(checker.problems).to be_empty end it 'verifies extra block variables in calls with args' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( def foo(bar); end foo(1, &block) ), 'test.rb') @@ -654,7 +656,7 @@ def foo(bar); end end it 'verifies splats passed to arguments' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( def foo(bar, baz); end foo(*splat) ), 'test.rb') diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index 014b19c40..ea6515b80 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::TypeChecker do context 'when at strict level' do # @return [Solargraph::TypeChecker] @@ -105,7 +107,7 @@ def bar(a); end ), 'test.rb') 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) + checker = described_class.new('test.rb', api_map: api_map, level: :strict) expect(checker.problems).to be_empty end diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index 80ea8a371..5435058d5 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::TypeChecker do context 'with level set to strong' do def type_checker code diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index eedc5a604..4b9a3226a 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::TypeChecker do context 'when level set to typed' do def type_checker code diff --git a/spec/type_checker/rules_spec.rb b/spec/type_checker/rules_spec.rb index ded5302fa..1ecc30714 100644 --- a/spec/type_checker/rules_spec.rb +++ b/spec/type_checker/rules_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::TypeChecker::Rules do it 'sets normal rules' do - rules = Solargraph::TypeChecker::Rules.new(:normal, {}) + rules = described_class.new(:normal, {}) expect(rules.ignore_all_undefined?).to be(true) expect(rules.must_tag_or_infer?).to be(false) expect(rules.require_type_tags?).to be(false) @@ -9,7 +11,7 @@ end it 'sets typed rules' do - rules = Solargraph::TypeChecker::Rules.new(:typed, {}) + rules = described_class.new(:typed, {}) expect(rules.ignore_all_undefined?).to be(true) expect(rules.must_tag_or_infer?).to be(false) expect(rules.require_type_tags?).to be(false) @@ -18,7 +20,7 @@ end it 'sets strict rules' do - rules = Solargraph::TypeChecker::Rules.new(:strict, {}) + rules = described_class.new(:strict, {}) expect(rules.ignore_all_undefined?).to be(false) expect(rules.must_tag_or_infer?).to be(true) expect(rules.require_type_tags?).to be(false) @@ -27,7 +29,7 @@ end it 'sets strong rules' do - rules = Solargraph::TypeChecker::Rules.new(:strong, {}) + rules = described_class.new(:strong, {}) expect(rules.ignore_all_undefined?).to be(false) expect(rules.must_tag_or_infer?).to be(true) expect(rules.require_type_tags?).to be(true) diff --git a/spec/type_checker_spec.rb b/spec/type_checker_spec.rb index 4597c6d75..3113896d2 100644 --- a/spec/type_checker_spec.rb +++ b/spec/type_checker_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'timeout' describe Solargraph::TypeChecker do it 'does not raise errors checking unparsed sources' do expect do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( foo{ )) checker.problems @@ -11,7 +13,7 @@ end it 'ignores tagged problems' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( NotAClass # @sg-ignore @@ -21,7 +23,7 @@ end it 'uses caching in Solargraph::Chain to handle a degenerate case' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( def documentation @documentation = "a" @documentation += "b" diff --git a/spec/workspace/config_spec.rb b/spec/workspace/config_spec.rb index 4d8245d1b..7bb04ba7a 100644 --- a/spec/workspace/config_spec.rb +++ b/spec/workspace/config_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'fileutils' require 'tmpdir' @@ -9,7 +11,7 @@ it 'includes .rb files by default' do file = File.join(dir_path, 'file.rb') File.write(file, 'exit') - config = Solargraph::Workspace::Config.new(dir_path) + config = described_class.new(dir_path) expect(config.calculated).to include(file) end @@ -17,7 +19,7 @@ Dir.mkdir(File.join(dir_path, 'lib')) file = File.join(dir_path, 'lib', 'file.rb') File.write(file, 'exit') - config = Solargraph::Workspace::Config.new(dir_path) + config = described_class.new(dir_path) expect(config.calculated).to include(file) end @@ -25,7 +27,7 @@ Dir.mkdir(File.join(dir_path, 'test')) file = File.join(dir_path, 'test', 'file.rb') File.write(file, 'exit') - config = Solargraph::Workspace::Config.new(dir_path) + config = described_class.new(dir_path) expect(config.calculated).not_to include(file) end @@ -33,7 +35,7 @@ Dir.mkdir(File.join(dir_path, 'spec')) file = File.join(dir_path, 'spec', 'file.rb') File.write(file, 'exit') - config = Solargraph::Workspace::Config.new(dir_path) + config = described_class.new(dir_path) expect(config.calculated).not_to include(file) end @@ -41,12 +43,12 @@ Dir.mkdir(File.join(dir_path, 'vendor')) file = File.join(dir_path, 'vendor', 'file.rb') File.write(file, 'exit') - config = Solargraph::Workspace::Config.new(dir_path) + config = described_class.new(dir_path) expect(config.calculated).not_to include(file) end it 'includes base reporters by default' do - config = Solargraph::Workspace::Config.new(dir_path) + config = described_class.new(dir_path) expect(config.reporters).to include('rubocop') expect(config.reporters).to include('require_not_found') end diff --git a/spec/workspace_spec.rb b/spec/workspace_spec.rb index 23535ed87..0924cd707 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'fileutils' require 'tmpdir' @@ -54,7 +56,7 @@ calculated: Array.new(Solargraph::Workspace::Config::MAX_FILES + 1), max_files: Solargraph::Workspace::Config::MAX_FILES) expect do - Solargraph::Workspace.new('.', config) + described_class.new('.', config) end.to raise_error(Solargraph::WorkspaceTooLargeError) end @@ -66,7 +68,7 @@ config = instance_double(Solargraph::Workspace::Config, calculated: calculated, max_files: 0, allow?: true, require_paths: [], plugins: []) expect do - Solargraph::Workspace.new('.', config) + described_class.new('.', config) end.not_to raise_error end @@ -125,14 +127,14 @@ end it 'uses configured require paths' do - workspace = Solargraph::Workspace.new('spec/fixtures/workspace') + workspace = described_class.new('spec/fixtures/workspace') 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 # vendor/**/* is excluded by default - workspace = Solargraph::Workspace.new('spec/fixtures/vendored') + workspace = described_class.new('spec/fixtures/vendored') expect(workspace.require_paths).to eq([File.absolute_path('spec/fixtures/vendored/lib')]) end @@ -140,7 +142,7 @@ config = instance_double(Solargraph::Workspace::Config, directory: './path', calculated: ['./path/does_not_exist.rb'], max_files: 5000, require_paths: [], plugins: []) expect do - Solargraph::Workspace.new('./path', config) + described_class.new('./path', config) end.not_to raise_error end diff --git a/spec/yard_map/mapper/to_method_spec.rb b/spec/yard_map/mapper/to_method_spec.rb index e406f00a4..c3a46b914 100644 --- a/spec/yard_map/mapper/to_method_spec.rb +++ b/spec/yard_map/mapper/to_method_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::YardMap::Mapper::ToMethod do let(:code_object) do namespace = YARD::CodeObjects::ModuleObject.new(nil, 'Example') @@ -6,7 +8,7 @@ it 'parses args' do code_object.parameters = [['bar', nil]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + pin = described_class.make(code_object) param = pin.parameters.first expect(param.decl).to be(:arg) expect(param.name).to eq('bar') @@ -15,7 +17,7 @@ it 'parses optargs' do code_object.parameters = [['bar', "'baz'"]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + pin = described_class.make(code_object) param = pin.parameters.first expect(param.decl).to be(:optarg) expect(param.name).to eq('bar') @@ -24,7 +26,7 @@ it 'parses kwargs' do code_object.parameters = [['bar:', nil]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + pin = described_class.make(code_object) param = pin.parameters.first expect(param.name).to eq('bar') expect(param.decl).to be(:kwarg) @@ -33,7 +35,7 @@ it 'parses kwoptargs' do code_object.parameters = [['bar:', "'baz'"]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + pin = described_class.make(code_object) param = pin.parameters.first expect(param.decl).to be(:kwoptarg) expect(param.name).to eq('bar') @@ -42,7 +44,7 @@ it 'parses restargs' do code_object.parameters = [['*bar', nil]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + pin = described_class.make(code_object) param = pin.parameters.first expect(param.decl).to be(:restarg) expect(param.name).to eq('bar') @@ -51,7 +53,7 @@ it 'parses kwrestargs' do code_object.parameters = [['**bar', nil]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + pin = described_class.make(code_object) param = pin.parameters.first expect(param.decl).to be(:kwrestarg) expect(param.name).to eq('bar') @@ -60,7 +62,7 @@ it 'parses blockargs' do code_object.parameters = [['&bar', nil]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + pin = described_class.make(code_object) param = pin.parameters.first expect(param.decl).to be(:blockarg) expect(param.name).to eq('bar') @@ -74,7 +76,7 @@ code_object.docstring = <<~EOF @yieldparam foo [Integer] EOF - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + pin = described_class.make(code_object) param = pin.parameters.first expect(param.decl).to be(:blockarg) expect(param.name).to eq('') diff --git a/spec/yard_map/mapper_spec.rb b/spec/yard_map/mapper_spec.rb index fb8378161..96f71ace6 100644 --- a/spec/yard_map/mapper_spec.rb +++ b/spec/yard_map/mapper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::YardMap::Mapper do before :all do # rubocop:disable RSpec/BeforeAfterAll @api_map = Solargraph::ApiMap.load('.') @@ -13,7 +15,7 @@ def pins_with require dir = File.absolute_path(File.join('spec', 'fixtures', 'yard_map')) Dir.chdir dir do YARD::Registry.load([File.join(dir, 'attr.rb')], true) - mapper = Solargraph::YardMap::Mapper.new(YARD::Registry.all) + mapper = described_class.new(YARD::Registry.all) pins = mapper.map pin = pins.select { |pin| pin.path == 'Foo#bar' }.first expect(pin.comments).to be_a(String) From 725e85df0893e51e45c75fa868edafe9e9f5f790 Mon Sep 17 00:00:00 2001 From: Vince Broz Date: Sat, 31 Jan 2026 15:46:01 -0500 Subject: [PATCH 930/930] Fix typechecking issues --- lib/solargraph/api_map/index.rb | 4 ---- lib/solargraph/bench.rb | 1 + lib/solargraph/doc_map.rb | 1 - lib/solargraph/language_server/request.rb | 2 ++ lib/solargraph/library.rb | 2 +- lib/solargraph/pin/conversions.rb | 1 - lib/solargraph/pin/method.rb | 2 -- lib/solargraph/source/chain/call.rb | 1 - lib/solargraph/type_checker.rb | 2 -- lib/solargraph/type_checker/rules.rb | 4 ++-- lib/solargraph/yard_map/mapper.rb | 7 +++---- 11 files changed, 9 insertions(+), 18 deletions(-) diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 881d048a6..df3e6984e 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -164,10 +164,6 @@ def map_overrides # received String, Symbol - delete_tags is ok with a # _ToS, but we should fix anyway pin.docstring.delete_tags tag - # @sg-ignore Wrong argument type for - # YARD::Docstring#delete_tags: name expected String, - # received String, Symbol - delete_tags is ok with a - # _ToS, but we should fix anyway new_pin&.docstring&.delete_tags tag end ovr.tags.each do |tag| diff --git a/lib/solargraph/bench.rb b/lib/solargraph/bench.rb index de50e3df0..dda2bbc88 100644 --- a/lib/solargraph/bench.rb +++ b/lib/solargraph/bench.rb @@ -29,6 +29,7 @@ def initialize source_maps: [], workspace: Workspace.new, live_map: nil, externa .to_set end + # @sg-ignore flow sensitive typing needs better handling of ||= on lvars # @return [Hash{String => SourceMap}] def source_map_hash # @todo Work around #to_h bug in current Ruby head (3.5) with #map#to_h diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 2a55f55c0..61589e016 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -134,7 +134,6 @@ def gemspecs def load_serialized_gem_pins out: @out serialized_pins = [] with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v } - # @sg-ignore Need better typing for Hash[] # @type [Array] missing_paths = without_gemspecs.to_h.keys # @type [Array] diff --git a/lib/solargraph/language_server/request.rb b/lib/solargraph/language_server/request.rb index e4ecc1e97..2a2a29221 100644 --- a/lib/solargraph/language_server/request.rb +++ b/lib/solargraph/language_server/request.rb @@ -10,6 +10,8 @@ def initialize id, &block @block = block end + # @sg-ignore Solargraph::LanguageServer::Request#process return + # type could not be inferred # @param result [Object] # @generic T # @yieldreturn [generic] diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 7e5b3623a..8cf806275 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -691,8 +691,8 @@ def report_cache_progress gem_name, pending @cache_progress.begin(message, pct) changed notify_observers @cache_progress - # @sg-ignore flow sensitive typing should be able to handle redefinition end + # @sg-ignore flow sensitive typing should be able to handle redefinition @cache_progress.report(message, pct) changed notify_observers @cache_progress diff --git a/lib/solargraph/pin/conversions.rb b/lib/solargraph/pin/conversions.rb index 0d45741d4..1c3aeac99 100644 --- a/lib/solargraph/pin/conversions.rb +++ b/lib/solargraph/pin/conversions.rb @@ -43,7 +43,6 @@ def completion_item data: { path: path, return_type: return_type.tag, - # @sg-ignore flow sensitive typing needs to handle attrs location: location&.to_hash, deprecated: deprecated? } diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 10f91e07d..f06a563ed 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -171,7 +171,6 @@ def generate_signature parameters, return_type comments: p.text, name: name, decl: decl, - # @sg-ignore flow sensitive typing needs to handle attrs presence: location&.range, return_type: ComplexType.try_parse(*p.types), source: source @@ -402,7 +401,6 @@ def overloads comments: tag.docstring.all.to_s, name: name, decl: decl, - # @sg-ignore flow sensitive typing needs to handle attrs presence: location&.range, return_type: param_type_from_name(tag, src.first), source: :overloads diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index e9e7bbfae..7d71077c1 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -63,7 +63,6 @@ def resolve api_map, name_pin, locals stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) [stack.first].compact end - # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array pin_groups = [] if !api_map.loose_unions && pin_groups.any?(&:empty?) pins = pin_groups.flatten.uniq(&:path) return [] if pins.empty? diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index 708f5b9a6..724340821 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -807,10 +807,8 @@ def optional_param_count parameters end # @param pin [Pin::Method] - # @sg-ignore need boolish support for ? methods def abstract? pin pin.docstring.has_tag?('abstract') || - # @sg-ignore of low sensitive typing needs to handle ivars pin.closure&.docstring&.has_tag?('abstract') end diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index a0af63aec..6ce414a93 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -83,11 +83,12 @@ def require_inferred_type_params? # @todo 13: Need to validate config # @todo 8: flow sensitive typing should support .class == .class # @todo 6: need boolish support for ? methods + # @todo 6: flow sensitive typing needs better handling of ||= on lvars # @todo 5: literal arrays in this module turn into ::Solargraph::Source::Chain::Array # @todo 5: flow sensitive typing needs to handle 'raise if' - # @todo 5: flow sensitive typing needs better handling of ||= on lvars # @todo 4: flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) # @todo 4: nil? support in flow sensitive typing + # @todo 3: flow sensitive typing ought to be able to handle 'when ClassName' # @todo 2: downcast output of Enumerable#select # @todo 2: flow sensitive typing should handle return nil if location&.name.nil? # @todo 2: flow sensitive typing should handle is_a? and next @@ -98,7 +99,6 @@ def require_inferred_type_params? # @todo 2: Need to handle duck-typed method calls on union types # @todo 2: Need better handling of #compact # @todo 2: flow sensitive typing should allow shadowing of Kernel#caller - # @todo 2: flow sensitive typing ought to be able to handle 'when ClassName' # @todo 1: flow sensitive typing not smart enough to handle this case # @todo 1: flow sensitive typing needs to handle if foo = bar # @todo 1: flow sensitive typing needs to handle "if foo.nil?" diff --git a/lib/solargraph/yard_map/mapper.rb b/lib/solargraph/yard_map/mapper.rb index c08021364..a0109189b 100644 --- a/lib/solargraph/yard_map/mapper.rb +++ b/lib/solargraph/yard_map/mapper.rb @@ -40,24 +40,22 @@ def generate_pins code_object nspin = ToNamespace.make(code_object, @spec, @namespace_pins[code_object.namespace.to_s]) @namespace_pins[code_object.path] = nspin result.push nspin - # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check if code_object.is_a?(YARD::CodeObjects::ClassObject) && !code_object.superclass.nil? # This method of superclass detection is a bit of a hack. If # the superclass is a Proxy, it is assumed to be undefined in its # yardoc and converted to a fully qualified namespace. - # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check superclass = if code_object.superclass.is_a?(YARD::CodeObjects::Proxy) - # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check "::#{code_object.superclass}" else - # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check code_object.superclass.to_s end result.push Solargraph::Pin::Reference::Superclass.new(name: superclass, closure: nspin, source: :yard_map) end + # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' code_object.class_mixins.each do |m| result.push Solargraph::Pin::Reference::Extend.new(closure: nspin, name: m.path, source: :yard_map) end + # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' code_object.instance_mixins.each do |m| result.push Solargraph::Pin::Reference::Include.new( closure: nspin, # @todo Fix this @@ -67,6 +65,7 @@ def generate_pins code_object end when YARD::CodeObjects::MethodObject closure = @namespace_pins[code_object.namespace.to_s] + # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' if code_object.name == :initialize && code_object.scope == :instance # @todo Check the visibility of .new result.push ToMethod.make(code_object, 'new', :class, :public, closure, @spec)