diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 038e7bccf..e0647a58e 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -49,6 +49,7 @@ class InvalidRubocopVersionError < RuntimeError; end autoload :RbsMap, 'solargraph/rbs_map' autoload :GemPins, 'solargraph/gem_pins' autoload :PinCache, 'solargraph/pin_cache' + autoload :RbsTranslator, 'solargraph/rbs_translator' dir = File.dirname(__FILE__) VIEWS_PATH = File.join(dir, 'solargraph', 'views') diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 86bf1cd09..2a947e205 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -4,6 +4,7 @@ module Solargraph module Pin # The base class for method and attribute pins. # + # rubocop:disable Metrics/ClassLength class Method < Callable include Solargraph::Parser::NodeMethods @@ -175,7 +176,7 @@ def symbol_kind end def return_type - @return_type ||= ComplexType.new(signatures.map(&:return_type).flat_map(&:items)) + @return_type ||= return_type_from_inline_rbs || ComplexType.new(signatures.map(&:return_type).flat_map(&:items)) end # @param parameters [::Array] @@ -219,13 +220,10 @@ def generate_signature(parameters, return_type) # @return [::Array] def signatures - @signatures ||= begin - 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? - result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? - result + @signatures ||= if inline_rbs.empty? + signatures_from_yard + else + signatures_from_inline_rbs end end @@ -468,6 +466,8 @@ def rest_of_stack api_map attr_writer :documentation + attr_writer :return_type + def dodgy_visibility_source? # as of 2025-03-12, the RBS generator used for # e.g. activesupport did not understand 'private' markings @@ -664,9 +664,41 @@ def concat_example_tags .concat("```\n") end - protected + # @return [ComplexType, nil] + def return_type_from_inline_rbs + return nil if inline_rbs.empty? + method_type = RBS::Parser.parse_method_type(inline_rbs) + RbsTranslator.to_complex_type(method_type.type.return_type) + rescue RBS::ParsingError + nil + end - attr_writer :return_type + # @return [Array] + def signatures_from_inline_rbs + method_type = RBS::Parser.parse_method_type(inline_rbs) + [RbsTranslator.to_signature(method_type, self, parameter_names)] + rescue RBS::ParsingError + signatures_from_yard + end + + # @return [Array] + def signatures_from_yard + 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? + result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? + result + end + + # @return [String] + def inline_rbs + comments.lines + .select { |line| line.start_with?(': ') } + .map { |line| line[2..].strip } + .join("\n") + end end + # rubocop:enable Metrics/ClassLength end end diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 54bca0f73..a63722864 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -160,8 +160,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 + complex_type = RbsTranslator.to_complex_type(param.default_type).force_rooted + generic_defaults[param.name.to_s] = complex_type end end class_name = decl.name.relative!.to_s @@ -287,7 +287,7 @@ 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) + tag = RbsTranslator.to_complex_type(decl.type).to_s pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) end @@ -303,7 +303,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 = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -457,74 +457,10 @@ def location_decl_to_pin_location(location) # @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)], - ComplexType.try_parse(method_type_to_tag(type)).force_rooted - ] - end - - 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) - 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)) - 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| - # @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) - 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, - source: :rbs, type_location: type_location) - end - - rooted_tag = method_type_to_tag(type) - return_type = ComplexType.try_parse(rooted_tag).force_rooted - [parameters, return_type] + [ + RbsTranslator.to_parameter_pins(type, pin, pin.parameter_names), + extract_method_type_return_type(type).force_rooted + ] end # @param decl [RBS::AST::Members::AttrReader,RBS::AST::Members::AttrAccessor] @@ -545,7 +481,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 = RbsTranslator.to_complex_type(decl.type).force_rooted.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 @@ -574,12 +510,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: RbsTranslator.to_complex_type(decl.type).force_rooted, source: :rbs, closure: pin, type_location: type_location ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) pins.push pin end @@ -604,7 +540,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 = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -621,7 +557,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 = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -638,7 +574,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 = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -705,13 +641,17 @@ def alias_to_pin decl, closure 'NilClass' => 'nil' } + # Extract a ComplexType from a MethodType's return type. + # + # This method will convert type aliases to concrete types. + # # @param type [RBS::MethodType] - # @return [String] - def method_type_to_tag type + # @return [ComplexType] + def extract_method_type_return_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) + RbsTranslator.to_complex_type(type_aliases[type.type.return_type.to_s].type) else - other_type_to_tag type.type.return_type + RbsTranslator.to_complex_type(type.type.return_type) end end @@ -720,9 +660,7 @@ def method_type_to_tag type # @return [ComplexType::UniqueType] def build_type(type_name, type_args = []) base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s - params = type_args.map { |a| other_type_to_tag(a) }.map do |t| - ComplexType.try_parse(t).force_rooted - end + params = type_args.map { |arg| RbsTranslator.to_complex_type(arg).force_rooted } if base == 'Hash' && params.length == 2 ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) else @@ -730,75 +668,6 @@ def build_type(type_name, type_args = []) 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] - # @return [String] - def other_type_to_tag type - if type.is_a?(RBS::Types::Optional) - "#{other_type_to_tag(type.type)}, 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(', ')})" - 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(', ') - 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_tag(member) }.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 - Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" - 'undefined' - end - end - # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Module] # @param namespace [Pin::Namespace] # @return [void] diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb new file mode 100644 index 000000000..a070de1e1 --- /dev/null +++ b/lib/solargraph/rbs_translator.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +module Solargraph + # Convert RBS types to complex types and pins. + # + module RbsTranslator + RBS_TO_YARD_TYPE = { + 'bool' => 'Boolean', + 'string' => 'String', + 'int' => 'Integer', + 'untyped' => '', + 'NilClass' => 'nil' + } + + # @param type [RBS::Types::Bases::Base] + # @return [ComplexType] + def self.to_complex_type(type) + tag = type_to_tag(type) + ComplexType.try_parse(tag).force_rooted + end + + # @param param_type [RBS::Types::Function::Param] + # @param name [String] + # @param decl [Symbol] + # @param closure [Pin::Closure] + # @return [Pin::Parameter] + def self.to_parameter_pin(param_type, name, decl, closure) + return_type = if decl == :restarg + ComplexType.parse('Array') + elsif decl == :kwrestarg + ComplexType.parse('Hash{Symbol => Object}') + else + RbsTranslator.to_complex_type(param_type.type) + end + Solargraph::Pin::Parameter.new(decl: decl, name: name, closure: closure, return_type: return_type, source: :rbs, type_location: to_sg_location(param_type.location) || closure.type_location) + end + + # @param method_type [RBS::MethodType] + # @param closure [Pin::Closure] + # @param parameter_names [Array] + # @return [Array] + def self.to_parameter_pins method_type, closure, parameter_names = [] + if defined?(RBS::Types::UntypedFunction) && method_type.type.is_a?(RBS::Types::UntypedFunction) + return [ + Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: closure, source: :rbs) + ] + end + + arg_num = 0 + params = [] + method_type.type.required_positionals.each do |param| + params.push RbsTranslator.to_parameter_pin(param, param.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :arg, closure) + arg_num += 1 + end + method_type.type.optional_positionals.each do |param| + params.push RbsTranslator.to_parameter_pin(param, param.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :optarg, closure) + arg_num += 1 + end + if method_type.type.rest_positionals + params.push RbsTranslator.to_parameter_pin(method_type.type.rest_positionals, method_type.type.rest_positionals.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :restarg, closure) + arg_num += 1 + end + method_type.type.required_keywords.each do |param| + params.push RbsTranslator.to_parameter_pin(param.last, param.first.to_s, :kwarg, closure) + arg_num += 1 + end + method_type.type.optional_keywords.each do |param| + params.push RbsTranslator.to_parameter_pin(param.last, param.first.to_s, :kwoptarg, closure) + arg_num += 1 + end + if method_type.type.rest_keywords + params.push RbsTranslator.to_parameter_pin(method_type.type.rest_keywords, method_type.type.rest_keywords.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :kwrestarg, closure) + end + params + end + + # @param method_type [RBS::MethodType] + # @param closure [Pin::Closure] + # @param parameter_names [Array] + # @return [Pin::Signature] + def self.to_signature method_type, closure, parameter_names = [] + # There may be edge cases here around different signatures + # having different type params / orders - we may need to match + # this data model and have generics live in signatures to + # handle those correctly + generics = method_type.type_params.map(&:name).map(&:to_s).uniq + parameters = to_parameter_pins(method_type, closure, parameter_names) + return_type = to_complex_type(method_type.type.return_type) + block = if method_type.block + block_parameters = to_parameter_pins(method_type.block, closure) + block_return_type = to_complex_type(method_type.block.type.return_type) + Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, type_location: closure.location, closure: closure) + end + Pin::Signature.new(generics: generics, parameters: parameters, return_type: return_type, block: block, source: :rbs, type_location: closure.location, closure: closure) + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [ComplexType::UniqueType] + def self.build_unique_type(type_name, type_args = []) + base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s + params = type_args.map do |a| + RbsTranslator.to_complex_type(a) + end + if base == 'Hash' && params.length == 2 + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) + else + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) + end + end + + # @param location [RBS::Location, nil] + # @return [Solargraph::Location, nil] + def self.to_sg_location(location) + return nil if location&.name.nil? + + start_pos = Position.new(location.start_line - 1, location.start_column) + end_pos = Position.new(location.end_line - 1, location.end_column) + range = Range.new(start_pos, end_pos) + Location.new(location.name.to_s, range) + end + + class << self + private + + # @param type [RBS::Types::Bases::Base] + # @return [String] + def type_to_tag type + case type + when RBS::Types::Optional + "#{type_to_tag(type.type)}, nil" + when RBS::Types::Bases::Bool + 'Boolean' + when RBS::Types::Tuple + "Array(#{type.types.map { |t| type_to_tag(t) }.join(', ')})" + when RBS::Types::Literal + type.literal.inspect + when RBS::Types::Union + type.types.map { |t| type_to_tag(t) }.join(', ') + when RBS::Types::Record + # @todo Better record support + 'Hash' + when RBS::Types::Bases::Nil + 'nil' + when RBS::Types::Bases::Void + 'void' + when RBS::Types::Variable + "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" + when RBS::Types::Bases::Self, RBS::Types::Bases::Instance + 'self' + when RBS::Types::Bases::Top + # `Top` is the most super superclass + 'BasicObject' + when RBS::Types::Intersection + type.types.map { |member| type_to_tag(member) }.join(', ') + when RBS::Types::Proc + 'Proc' + when RBS::Types::ClassInstance, RBS::Types::Alias, RBS::Types::Interface + # `Alias` is a top-level type alias, e.g., 'bool' in "type bool = true | false" + # @todo ensure these get resolved after processing all aliases + # @todo handle recursive aliases + # + # `Interface represents a mix-in module which can be considered a + # subtype of a consumer of it + # + type_tag(type.name, type.args) + when RBS::Types::ClassSingleton + # e.g., singleton(String) + type_tag(type.name) + when RBS::Types::Bases::Any, 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' + else + Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" + 'undefined' + 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_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| type_to_tag(a) }.map do |t| + ComplexType.try_parse(t) + end + if base == 'Hash' && params.length == 2 + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) + else + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) + end + end + end + end +end diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index 2699dad73..5c69d4a2b 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -111,12 +111,10 @@ def cache gem, version = nil PinCache.serialize_yard_gem(gemspec, pins) end - workspace = Solargraph::Workspace.new(Dir.pwd) - rbs_map = RbsMap.from_gemspec(gemspec, workspace.rbs_collection_path, workspace.rbs_collection_config_path) + workspace = Solargraph::Workspace.new(Dir.pwd) if File.exist?('rbs_collection.yaml') + rbs_map = RbsMap.from_gemspec(gemspec, workspace&.rbs_collection_path, workspace&.rbs_collection_config_path) if options[:rebuild] || !PinCache.has_rbs_collection?(gemspec, rbs_map.cache_key) - # cache pins even if result is zero, so we don't retry building pins - pins = rbs_map.pins || [] - PinCache.serialize_rbs_collection_gem(gemspec, rbs_map.cache_key, pins) + PinCache.serialize_rbs_collection_gem(gemspec, rbs_map.cache_key, rbs_map.pins) end end diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index 283ef6d51..d554326cd 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -626,4 +626,154 @@ def bar expect(pin.overloads).to be_empty end end + + context 'with inline rbs' do + it 'sets instance return types' do + source = Solargraph::Source.load_string(%( + #: () -> String + def foo; end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.return_type.to_s).to eq('String') + end + + it 'sets parameterized instance return types' do + source = Solargraph::Source.load_string(%( + #: () -> Array[String] + def foo; end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.return_type.to_s).to eq('Array') + end + + it 'sets YARD conventional return types' do + source = Solargraph::Source.load_string(%( + #: () -> bool + def foo; end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.return_type.to_s).to eq('Boolean') + end + + it 'sets required positional parameters' do + source = Solargraph::Source.load_string(%( + #: (String) -> bool + def foo(bar); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:arg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') + end + + it 'sets optional positional parameters' do + source = Solargraph::Source.load_string(%( + #: (?String) -> bool + def foo(bar = 'default'); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:optarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') + end + + it 'sets rest positional parameters' do + source = Solargraph::Source.load_string(%( + #: (*bar) -> bool + def foo(*bar); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:restarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('Array') + end + + it 'sets required keyword parameters' do + source = Solargraph::Source.load_string(%( + #: (bar: String) -> bool + def foo(bar:); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:kwarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') + end + + it 'sets optional keyword parameters' do + source = Solargraph::Source.load_string(%( + #: (?bar: String) -> bool + def foo(bar: 'default'); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:kwoptarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') + end + + it 'sets rest keyword parameters' do + source = Solargraph::Source.load_string(%( + #: (**bar) -> bool + def foo(**bar); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:kwrestarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('Hash{Symbol => Object}') + end + + it 'sets block parameters' do + source = Solargraph::Source.load_string(%( + #: (String) { (Integer) -> void } -> bool + def foo(bar); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.block.parameters).to be_one + expect(pin.signatures.first.block.parameters.first.return_type.to_s).to eq('Integer') + expect(pin.signatures.first.block.return_type.to_s).to eq('void') + end + + it 'rescues parsing errors' do + source = Solargraph::Source.load_string(%[ + #: (* -> broke + def foo(**bar); end + ]) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect { pin.signatures }.not_to raise_error + end + end end