Skip to content
6 changes: 6 additions & 0 deletions lib/solargraph/api_map/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ def pins
@pins ||= []
end

# @return [Set<String>]
attr_reader :namespaces

# @return [Hash{String => Array<Pin::Namespace>}]
def namespace_hash
@namespace_hash ||= Hash.new { |h, k| h[k] = [] }
Expand Down Expand Up @@ -60,6 +63,7 @@ def superclass_references
end

# @param pins [Array<Pin::Base>]
# @return [self]
def merge pins
deep_clone.catalog pins
end
Expand All @@ -69,6 +73,7 @@ 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 [self]
def deep_clone
Index.allocate.tap do |copy|
copy.pin_select_cache = {}
Expand All @@ -84,6 +89,7 @@ def deep_clone
end

# @param new_pins [Array<Pin::Base>]
# @return [self]
def catalog new_pins
@pin_select_cache = {}
pins.concat new_pins
Expand Down
8 changes: 6 additions & 2 deletions lib/solargraph/api_map/source_to_yard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ def rake_yard store
end
store.get_extends(pin.path).each do |ref|
extend_object = code_object_at(pin.path, YARD::CodeObjects::ClassObject)
extend_object.instance_mixins.push code_object_map[ref] unless extend_object.nil? or extend_object.nil?
extend_object.class_mixins.push code_object_map[ref] unless extend_object.nil? or extend_object.nil?
next unless extend_object
code_object = code_object_map[ref]
next unless code_object
extend_object.class_mixins.push code_object
# @todo add spec showing why this next line is necessary
extend_object.instance_mixins.push code_object
end
end
store.method_pins.each do |pin|
Expand Down
10 changes: 7 additions & 3 deletions lib/solargraph/api_map/store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class ApiMap
class Store
# @param pinsets [Array<Enumerable<Pin::Base>>]
def initialize *pinsets
@pinsets = pinsets
catalog pinsets
end

Expand Down Expand Up @@ -200,10 +201,13 @@ def fqns_pins fqns

private

# @return [Index]
def index
@indexes.last
end

# @param pinsets [Array<Enumerable<Pin::Base>>]
# @return [Boolean]
def catalog pinsets
@pinsets = pinsets
@indexes = []
Expand Down Expand Up @@ -235,17 +239,17 @@ def superclass_references
index.superclass_references
end

# @return [Hash{String => Array<String>}]
# @return [Hash{String => Array<Pin::Reference::Include>}]
def include_references
index.include_references
end

# @return [Hash{String => Array<String>}]
# @return [Hash{String => Array<Pin::Reference::Prepend>}]
def prepend_references
index.prepend_references
end

# @return [Hash{String => Array<String>}]
# @return [Hash{String => Array<Pin::Reference::Extend>}]
def extend_references
index.extend_references
end
Expand Down
3 changes: 2 additions & 1 deletion lib/solargraph/convention/data_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def process
comments: comments_for(node)
)

# TODO: Support both arg and kwarg initializers for Data.define
# @todo Support both arg and kwarg initializers for Data.define
# 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

Expand Down Expand Up @@ -88,6 +88,7 @@ def data_definition_node
end

# @param attribute_node [Parser::AST::Node]
# @param attribute_name [String]
# @return [String, nil]
def attribute_comments(attribute_node, attribute_name)
data_comments = comments_for(attribute_node)
Expand Down
9 changes: 5 additions & 4 deletions lib/solargraph/convention/struct_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ def process
)

pins.push Pin::InstanceVariable.new(name: "@#{attribute_name}",
closure: method_pin,
location: get_node_location(attribute_node),
comments: attribute_comment(docs, false))
closure: method_pin,
location: get_node_location(attribute_node),
comments: attribute_comment(docs, false))
end

pins.push method_pin
Expand Down Expand Up @@ -113,7 +113,7 @@ def parse_comments

# We should support specific comments for an attribute, and that can be either a @return on an @param
# But since we merge into the struct_comments, then we should interpret either as a param
comment = '@param ' + attr_name + comment[7..] if comment.start_with?('@return')
comment = "@param #{attr_name}#{comment[7..]}" if comment.start_with?('@return')

struct_comments += "\n#{comment}"
end
Expand All @@ -123,6 +123,7 @@ def parse_comments

# @param tag [YARD::Tags::Tag, nil] The param tag for this attribute. If nil, this method is a no-op
# @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?

Expand Down
1 change: 1 addition & 0 deletions lib/solargraph/diagnostics/rubocop_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module RubocopHelpers
# @return [void]
def require_rubocop(version = nil)
begin
# @type [String]
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)
Expand Down
38 changes: 28 additions & 10 deletions lib/solargraph/doc_map.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require 'pathname'
require 'benchmark'
require 'open3'

module Solargraph
# A collection of pins generated from required gems.
Expand Down Expand Up @@ -32,8 +33,10 @@ def uncached_gemspecs
# @return [Array<Gem::Specification>]
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]
Expand All @@ -56,6 +59,8 @@ def initialize(requires, preferences, workspace = nil)
pins.concat @environ.pins
end

# @param out [IO]
# @return [void]
def cache_all!(out)
# if we log at debug level:
if logger.info?
Expand All @@ -73,12 +78,18 @@ def cache_all!(out)
@uncached_yard_gemspecs = []
end

# @param gemspec [Gem::Specification]
# @param out [IO]
# @return [void]
def cache_yard_pins(gemspec, out)
pins = GemPins.build_yard_pins(gemspec)
PinCache.serialize_yard_gem(gemspec, pins)
logger.info { "Cached #{pins.length} YARD pins for gem #{gemspec.name}:#{gemspec.version}" } unless pins.empty?
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
Expand All @@ -90,6 +101,9 @@ def cache_rbs_collection_pins(gemspec, out)
end

# @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)
build_yard = uncached_yard_gemspecs.include?(gemspec) || rebuild
build_rbs_collection = uncached_rbs_collection_gemspecs.include?(gemspec) || rebuild
Expand All @@ -113,26 +127,33 @@ def unresolved_requires
@unresolved_requires ||= required_gems_map.select { |_, gemspecs| gemspecs.nil? }.keys
end

# @return [Hash{Array(String, String) => Array<Gem::Specification>}] Indexed by gemspec name and version
def self.all_yard_gems_in_memory
@yard_gems_in_memory ||= {}
end

# @return [Hash{String => Array<Pin::Base>}] 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<Pin::Base>}] 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<Pin::Base>}] 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<Pin::Base>}] Indexed by gemspec name and version
def self.all_combined_pins_in_memory
@combined_pins_in_memory ||= {}
end

# @todo this should also include an index by the hash of the RBS collection
# @return [Hash{Array(String, String) => Array<Pin::Base>}] Indexed by gemspec name and version
def combined_pins_in_memory
self.class.all_combined_pins_in_memory
end
Expand All @@ -150,7 +171,9 @@ def load_serialized_gem_pins
@uncached_yard_gemspecs = []
@uncached_rbs_collection_gemspecs = []
with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v }
# @type [Array<String>]
paths = Hash[without_gemspecs].keys
# @type [Array<Gem::Specification>]
gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a

paths.each do |path|
Expand Down Expand Up @@ -259,6 +282,8 @@ def deserialize_stdlib_rbs_map path
end
end

# @param gemspec [Gem::Specification]
# @param rbs_version_cache_key [String]
# @return [Array<Pin::Base>, nil]
def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key
return if rbs_collection_pins_in_memory.key?([gemspec, rbs_version_cache_key])
Expand All @@ -274,22 +299,13 @@ def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key
end
end

# @param gemspec [Gem::Specification]
# @return [Boolean]
def try_gem_in_memory gemspec
gempins = DocMap.gems_in_memory[gemspec]
return false unless gempins
Solargraph.logger.debug "Found #{gemspec.name} #{gemspec.version} in memory"
@pins.concat gempins
true
end

# @param path [String]
# @return [::Array<Gem::Specification>, 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
Expand Down Expand Up @@ -355,6 +371,7 @@ def inspect
self.class.inspect
end

# @return [Array<Gem::Specification>]
def gemspecs_required_from_bundler
# @todo Handle projects with custom Bundler/Gemfile setups
return unless workspace.gemfile?
Expand All @@ -377,6 +394,7 @@ def gemspecs_required_from_bundler
end
end

# @return [Array<Gem::Specification>]
def gemspecs_required_from_external_bundle
logger.info 'Fetching gemspecs required from external bundle'
return [] unless workspace&.directory
Expand Down
1 change: 0 additions & 1 deletion lib/solargraph/gem_pins.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ def self.build_yard_pins(gemspec)

# @param pins [Array<Pin::Base>]
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|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def process
private

# @param corrections [String]
# @return [void]
def log_corrections(corrections)
corrections = corrections&.strip
return if corrections&.empty?
Expand All @@ -45,14 +46,18 @@ def log_corrections(corrections)
end
end

# @param file_uri [String]
# @return [Hash{String => undefined}]
def config_for(file_uri)
conf = host.formatter_config(file_uri)
return {} unless conf.is_a?(Hash)

conf['rubocop'] || {}
end

# @param file_uri [String]
# @param config [Hash{String => String}]
# @return [Array<String>]
def cli_args file_uri, config
file = UriHelpers.uri_to_file(file_uri)
args = [
Expand All @@ -71,6 +76,8 @@ def cli_args file_uri, config
end

# @param config [Hash{String => String}]
# @sg-ignore
# @return [Class<RuboCop::Formatter::BaseFormatter>]
def formatter_class(config)
if self.class.const_defined?('BlankRubocopFormatter')
# @sg-ignore
Expand All @@ -83,6 +90,7 @@ def formatter_class(config)
end

# @param value [Array, String]
# @return [String]
def cop_list(value)
value = value.join(',') if value.respond_to?(:join)
return nil if value == '' || !value.is_a?(String)
Expand Down
3 changes: 3 additions & 0 deletions lib/solargraph/library.rb
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@ def cache_next_gemspec
end
end

# @return [Array<Gem::Specification>]
def cacheable_specs
cacheable = api_map.uncached_yard_gemspecs +
api_map.uncached_rbs_collection_gemspecs -
Expand All @@ -631,6 +632,7 @@ def cacheable_specs
queued_gemspec_cache
end

# @return [Array<Gem::Specification>]
def queued_gemspec_cache
@queued_gemspec_cache ||= []
end
Expand Down Expand Up @@ -672,6 +674,7 @@ def end_cache_progress
@total = nil
end

# @return [void]
def sync_catalog
return if @sync_count == 0

Expand Down
4 changes: 4 additions & 0 deletions lib/solargraph/page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ def initialize locals, render_method
end

# @param text [String]
# @sg-ignore
# @return [String]
def htmlify text
# @type [String]
YARD::Templates::Helpers::Markup::RDocMarkup.new(text).to_html
end

Expand Down Expand Up @@ -70,8 +72,10 @@ def initialize directory = VIEWS_PATH
# @param template [String]
# @param layout [Boolean]
# @param locals [Hash]
# @sg-ignore
# @return [String]
def render template, layout: true, locals: {}
# @type [String]
@render_method.call(template, layout: layout, locals: locals)
end

Expand Down
Loading