Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/rspec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['3.1', '3.2', '3.3', '3.4', '4.0', 'head']
# @todo Restore 'head' once ruby/setup-ruby publishes it for ubuntu-24.04.
# It currently 404s ("Unavailable version head for ruby"), failing CI:
# https://github.com/castwide/solargraph/actions/runs/25863741955/job/76000137015?pr=1187
ruby-version: ['3.1', '3.2', '3.3', '3.4', '4.0']
rbs-version: ['3.10.0', '4.0.0', '4.0.1', '4.0.2']
exclude:
- ruby-version: '3.1'
Expand Down
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ AllCops:
- "spec/fixtures/invalid_byte.rb"
- "spec/fixtures/invalid_node_comment.rb"
- "spec/fixtures/invalid_utf8.rb"
- "spec/fixtures/gem-with-yard-macros/**/*"
- "vendor/**/*"
- "vendor/**/.*"
TargetRubyVersion: 3.1
Expand Down
2 changes: 1 addition & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Metrics/MethodLength:

# Configuration parameters: CountComments, CountAsOne.
Metrics/ModuleLength:
Max: 167
Max: 195

# Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters.
Metrics/ParameterLists:
Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ source 'https://rubygems.org'

gemspec name: 'solargraph'

# Test fixture gems
gem 'gem-with-yard-macros', path: 'spec/fixtures/gem-with-yard-macros'

# Local gemfile for development tools, etc.
local_gemfile = File.expand_path('.Gemfile', __dir__)
instance_eval File.read local_gemfile if File.exist? local_gemfile
36 changes: 34 additions & 2 deletions lib/solargraph/api_map.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def map source, live: false
# @return [self]
def catalog bench
@source_map_hash = bench.source_map_hash
# @type [Array<Pin::Base>]
iced_pins = bench.icebox.flat_map(&:pins)
live_pins = bench.live_map&.all_pins || []
conventions_environ.clear
Expand All @@ -123,11 +124,42 @@ def catalog bench
@doc_map = DocMap.new(unresolved_requires, bench.workspace, out: nil) # @todo Implement gem preferences
@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)
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
Solargraph.logger.info 'Cataloging ApiMap started'
@cache.clear if store.update(@@core_map.pins, @doc_map.pins, conventions_environ.pins, iced_pins, live_pins) { process_macros }
@missing_docs = [] # @todo Implement missing docs
Solargraph.logger.info "Cataloging ApiMap finished in #{Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time} seconds"
self
end

# @return [Array<Pin::Base>]
def process_macros
macro_pins = []
source_maps.each do |source_map|
source_map.macro_method_candidates(store.macro_method_names).each do |node|
closure = source_map.locate_closure_pin(node.location.line, node.location.column)
chain = Solargraph::Parser::ParserGem::NodeChainer.chain(node)
if node.children[0].nil? && store.macro_method_name_pins.key?(node.children[1].to_s)
match = store.macro_method_name_pins[node.children[1].to_s].find do |pin|
super_and_sub?(pin.namespace, closure.name)
end
if match
match.macros.each do |macro|
macro_pins.concat macro.generate_pins_from(chain, match, source_map)
end
next
end
end
pin = chain.define(self, closure, []).first
next unless pin&.macros&.any?
pin.macros.each do |macro|
macro_pins.concat macro.generate_pins_from(chain, pin, source_map)
end
end
end
macro_pins
end

# @return [DocMap]
def doc_map
@doc_map ||= DocMap.new([], Workspace.new('.'))
Expand All @@ -154,7 +186,7 @@ def core_pins
end

# @param name [String, nil]
# @return [YARD::Tags::MacroDirective, nil]
# @return [Solargraph::YardMap::Macro, nil]
def named_macro name
# @sg-ignore Need to add nil check here
store.named_macros[name]
Expand Down
15 changes: 13 additions & 2 deletions lib/solargraph/api_map/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ class ApiMap
class Index
include Logging

# @return [Array<String>]
attr_reader :macro_method_names

# @return [Hash{String => Array<Pin::Method>}]
attr_reader :macro_method_name_pins

# @param pins [Array<Pin::Base>]
def initialize pins = []
catalog pins
Expand Down Expand Up @@ -95,16 +101,18 @@ def merge pins
protected

attr_writer :pins, :pin_select_cache, :namespace_hash, :pin_class_hash, :path_pin_hash, :include_references,
:extend_references, :prepend_references, :superclass_references
:extend_references, :prepend_references, :superclass_references, :macro_method_names,
:macro_method_name_pins

# @return [self]
def deep_clone
Index.allocate.tap do |copy|
copy.pin_select_cache = {}
copy.pins = pins.clone
copy.macro_method_names = macro_method_names
%i[
namespace_hash pin_class_hash path_pin_hash include_references extend_references prepend_references
superclass_references
superclass_references macro_method_name_pins
].each do |sym|
copy.send("#{sym}=", send(sym).clone)
copy.send(sym)&.transform_values!(&:clone)
Expand Down Expand Up @@ -137,6 +145,9 @@ def catalog new_pins
map_references Pin::Reference::Prepend, prepend_references
map_references Pin::Reference::Extend, extend_references
map_references Pin::Reference::Superclass, superclass_references
macro_pins = pins_by_class(Pin::Method).select { |pin| pin.macros.any? }
@macro_method_names = macro_pins.to_set(&:name)
@macro_method_name_pins = macro_pins.to_set.classify(&:name)
map_overrides
pins_by_class(Pin::Reference::TypeAlias).each { |pin| alias_hash[pin.name] = pin.return_type }
self
Expand Down
27 changes: 21 additions & 6 deletions lib/solargraph/api_map/store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ def pins
# - pinsets[0] = core Ruby pins
# - pinsets[1] = documentation/gem pins
# - pinsets[2] = convention pins
# - pinsets[3] = workspace source pins
# - pinsets[3] = workspace source pins (aka. "iced_pins")
# - pinsets[4] = currently open file pins
# @return [Boolean] True if the index was updated
def update *pinsets
return catalog(pinsets) if pinsets.length != @pinsets.length
def update *pinsets, &block
return catalog(pinsets, &block) if pinsets.length != @pinsets.length

changed = pinsets.find_index.with_index { |pinset, idx| @pinsets[idx] != pinset }
return false unless changed
Expand All @@ -43,6 +43,9 @@ def update *pinsets
@indexes[changed + idx - 1].merge(pins)
end
end
# @type [Index]
@index = @indexes.last.clone
@index = @index.merge(block.call) if block
constants.clear
cached_qualify_superclass.clear
true
Expand Down Expand Up @@ -174,7 +177,7 @@ def domains fqns
result
end

# @return [Hash{String => YARD::Tags::MacroDirective}]
# @return [Hash{String => YardMap::Macro}]
def named_macros
@named_macros ||= begin
result = {}
Expand Down Expand Up @@ -276,17 +279,27 @@ def unalias name
index.alias_hash[name]
end

# @return [Array<String>]
def macro_method_names
index.macro_method_names
end

# @return [Hash{String => Array<Pin::Method>}]
def macro_method_name_pins
index.macro_method_name_pins
end

private

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

# @param pinsets [Array<Array<Pin::Base>>]
#
# @return [true]
def catalog pinsets
def catalog pinsets, &block
@pinsets = pinsets
# @type [Array<Index>]
@indexes = []
Expand All @@ -297,6 +310,8 @@ def catalog pinsets
@indexes.push(@indexes.last&.merge(pins) || Solargraph::ApiMap::Index.new(pins))
end
end
@index = @indexes.last.clone
@index = @index.merge(block.call) if block
constants.clear
cached_qualify_superclass.clear
true
Expand Down
4 changes: 4 additions & 0 deletions lib/solargraph/complex_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,10 @@ def transform new_name = nil, &transform_type
ComplexType.new(map { |ut| ut.transform(new_name, &transform_type) })
end

def expand named_types
ComplexType.new(map { |ut| ut.expand(named_types) })
end

# @return [self]
def force_rooted
transform do |t|
Expand Down
4 changes: 4 additions & 0 deletions lib/solargraph/complex_type/unique_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,10 @@ def transform new_name = nil, &transform_type
yield new_type
end

def expand named_types
named_types[name] || self
end

# Generate a ComplexType that fully qualifies this type's namespaces.
#
# @param api_map [ApiMap] The ApiMap that performs qualification
Expand Down
42 changes: 42 additions & 0 deletions lib/solargraph/parser/parser_gem/node_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,48 @@ def drill_signature node, signature
signature
end

# Convert a DSL method call argument with directly inferrable simple params.
# @param node [Parser::AST::Node]
# @return [String, Integer, Float, Symbol, Array, Hash, Source::Chain, nil]
# @sg-ignore "does not match inferred type ::String, ::Parser::AST::Node" - this probably comes from the
# `.children[0]` call, which is not recognized as returning a literal value.
def simple_convert node
return nil unless Parser.is_ast_node?(node)

case node.type
when :const
unpack_name(node)
when :str, :dstr, :int, :float, :sym, true, false
node.children[0]
when :array
simple_convert_array(node)
when :hash
simple_convert_hash(node)
else
Solargraph::Parser.chain(node)
end
end

# @param node [Parser::AST::Node]
# @return [Array]
def simple_convert_array node
return [] unless Parser.is_ast_node?(node) && node.type == :array
node.children.compact.map { |c| simple_convert(c) }
end

# @param node [Parser::AST::Node]
# @return [Hash]
def simple_convert_hash node
return {} unless Parser.is_ast_node?(node) && node.type == :hash
result = {}
# @param pair [Parser::AST::Node]
node.children.each do |pair|
next unless Parser.is_ast_node?(pair) && pair.children[0]
result[pair.children[0].children[0]] = simple_convert(pair.children[1])
end
result
end

# @param node [Parser::AST::Node, nil]
# @return [Hash{Symbol => Chain}]
def convert_hash node
Expand Down
34 changes: 31 additions & 3 deletions lib/solargraph/pin/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -534,11 +534,20 @@
@directives
end

# @return [::Array<YARD::Tags::MacroDirective>]
# @return [::Array<Solargraph::YardMap::Macro>]
def macros
@macros ||= collect_macros
end

def macro_names
parse_comments unless @macro_names
@macro_names ||= collect_macro_names
end

def macro_names?
macro_names.any?
end

# Perform a quick check to see if this pin possibly includes YARD
# directives. This method does not require parsing the comments.
#
Expand Down Expand Up @@ -569,6 +578,15 @@
return_type.qualify(api_map, *(closure&.gates || ['']))
end

# @todo Candidate for deprecation (see ApiMap#process_macros)
def maybe_macros?
comments.include?('@macro')

Check warning on line 583 in lib/solargraph/pin/base.rb

View workflow job for this annotation

GitHub Actions / rubocop

[rubocop] reported by reviewdog 🐶 Trailing whitespace detected. Raw Output: lib/solargraph/pin/base.rb:583:36: C: Layout/TrailingWhitespace: Trailing whitespace detected.
end

def macros_names?
macro_names.any?
end

# Infer the pin's return type via static code analysis.
#
# @param api_map [ApiMap]
Expand Down Expand Up @@ -619,6 +637,8 @@
result = dup
result.return_type = return_type
result.proxied = true
# Macros should have been processed already
result.macro_names.clear
result
end

Expand Down Expand Up @@ -717,12 +737,14 @@
if comments.nil? || comments.empty? || comments.strip.end_with?('@overload')
@docstring = nil
@directives = []
@macro_names = []
else
# HACK: Pass a dummy code object to the parser for plugins that
# expect it not to be nil
parse = Solargraph::Source.parse_docstring(comments)
@docstring = parse.to_docstring
@directives = parse.directives
@macro_names = collect_macro_names
end
end

Expand Down Expand Up @@ -762,11 +784,17 @@
tag1.types == tag2.types
end

# @return [::Array<YARD::Tags::Handlers::Directive>]
# @return [::Array<Solargraph::YardMap::Macro>]
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' }.map do |macro_directive|
Solargraph::YardMap::Macro.from_directive macro_directive, self
end
end

def collect_macro_names
"#{comments}\n".scan(/\s*?@macro +(\S+).*?[\n]/).map { |match| match[0] }

Check warning on line 797 in lib/solargraph/pin/base.rb

View workflow job for this annotation

GitHub Actions / rubocop

[rubocop] reported by reviewdog 🐶 Redundant single-element character class, `[\n]` can be replaced with `\n`. Raw Output: lib/solargraph/pin/base.rb:797:51: C: Style/RedundantRegexpCharacterClass: Redundant single-element character class, `[\n]` can be replaced with `\n`.
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/solargraph/pin/callable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ def resolve_generics_from_context generics_to_resolve,
end

def typify api_map
type = super
return type if type.defined?
type = return_type
return type.qualify(api_map, *gates) if type.defined?
if method_name.end_with?('?')
logger.debug { "Callable#typify(self=#{self}) => Boolean (? suffix)" }
ComplexType::BOOLEAN
Expand Down
12 changes: 12 additions & 0 deletions lib/solargraph/pin/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ def path
@path ||= name.empty? ? context.namespace : "#{context.namespace}::#{name}"
end

# @yieldparam [Pin::Base]
# @return [void, Enumerator<Pin::Base>]
def each_closure &block
return enum_for(:each_closure) unless block_given?

here = closure
until here.nil?
yield here
here = here&.closure
end
end

protected

attr_writer :context
Expand Down
Loading
Loading