From 2f8eb3b9208cb029c58bea0d3334e07b6899b175 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Wed, 13 Sep 2023 15:02:49 -0400 Subject: [PATCH] Migrate semantic highlighting to YARP Co-authored-by: Alexandre Terrasa --- lib/ruby_lsp/event_emitter.rb | 158 +++++--- lib/ruby_lsp/internal.rb | 1 + lib/ruby_lsp/parameter_scope.rb | 33 ++ .../requests/semantic_highlighting.rb | 351 ++++++++++-------- lib/ruby_lsp/requests/support/sorbet.rb | 2 +- .../destructured_splat_block_arg.exp.json | 11 +- .../method_params.exp.json | 23 +- .../numbered_parameters.exp.json | 2 +- .../pinned_variable.exp.json | 9 +- test/parameter_scope_test.rb | 27 ++ ...semantic_highlighting_expectations_test.rb | 2 +- .../support/semantic_token_encoder_test.rb | 13 +- 12 files changed, 424 insertions(+), 208 deletions(-) create mode 100644 lib/ruby_lsp/parameter_scope.rb create mode 100644 test/parameter_scope_test.rb diff --git a/lib/ruby_lsp/event_emitter.rb b/lib/ruby_lsp/event_emitter.rb index 213815855..f4e67b112 100644 --- a/lib/ruby_lsp/event_emitter.rb +++ b/lib/ruby_lsp/event_emitter.rb @@ -88,24 +88,6 @@ def visit_call_node(node) @listeners[:after_call]&.each { |l| T.unsafe(l).after_call(node) } end - sig { override.params(node: YARP::ConstantPathWriteNode).void } - def visit_constant_path_write_node(node) - @listeners[:on_constant_path_write]&.each { |l| T.unsafe(l).on_constant_path_write(node) } - super - end - - sig { override.params(node: YARP::ConstantPathNode).void } - def visit_constant_path_node(node) - @listeners[:on_constant_path]&.each { |l| T.unsafe(l).on_constant_path(node) } - super - end - - sig { override.params(node: YARP::ConstantWriteNode).void } - def visit_constant_write_node(node) - @listeners[:on_constant_write]&.each { |l| T.unsafe(l).on_constant_write(node) } - super - end - sig { override.params(node: YARP::InstanceVariableWriteNode).void } def visit_instance_variable_write_node(node) @listeners[:on_instance_variable_write]&.each { |l| T.unsafe(l).on_instance_variable_write(node) } @@ -125,15 +107,16 @@ def visit_def_node(node) @listeners[:after_def]&.each { |l| T.unsafe(l).after_def(node) } end - sig { override.params(node: SyntaxTree::VarField).void } - def visit_var_field(node) - @listeners[:on_var_field]&.each { |l| T.unsafe(l).on_var_field(node) } + sig { override.params(node: YARP::BlockNode).void } + def visit_block_node(node) + @listeners[:on_block]&.each { |l| T.unsafe(l).on_block(node) } super + @listeners[:after_block]&.each { |l| T.unsafe(l).after_block(node) } end - sig { override.params(node: SyntaxTree::Comment).void } - def visit_comment(node) - @listeners[:on_comment]&.each { |l| T.unsafe(l).on_comment(node) } + sig { override.params(node: YARP::SelfNode).void } + def visit_self_node(node) + @listeners[:on_self]&.each { |l| T.unsafe(l).on_self(node) } super end @@ -143,52 +126,131 @@ def visit_rescue_node(node) super end - sig { override.params(node: SyntaxTree::Kw).void } - def visit_kw(node) - @listeners[:on_kw]&.each { |l| T.unsafe(l).on_kw(node) } + sig { override.params(node: YARP::BlockParameterNode).void } + def visit_block_parameter_node(node) + @listeners[:on_block_parameter]&.each { |l| T.unsafe(l).on_block_parameter(node) } super end - sig { override.params(node: SyntaxTree::Params).void } - def visit_params(node) - @listeners[:on_params]&.each { |l| T.unsafe(l).on_params(node) } + sig { override.params(node: YARP::KeywordParameterNode).void } + def visit_keyword_parameter_node(node) + @listeners[:on_keyword_parameter]&.each { |l| T.unsafe(l).on_keyword_parameter(node) } super end - sig { override.params(node: SyntaxTree::Field).void } - def visit_field(node) - @listeners[:on_field]&.each { |l| T.unsafe(l).on_field(node) } + sig { override.params(node: YARP::KeywordRestParameterNode).void } + def visit_keyword_rest_parameter_node(node) + @listeners[:on_keyword_rest_parameter]&.each { |l| T.unsafe(l).on_keyword_rest_parameter(node) } super end - sig { override.params(node: SyntaxTree::VarRef).void } - def visit_var_ref(node) - @listeners[:on_var_ref]&.each { |l| T.unsafe(l).on_var_ref(node) } + sig { override.params(node: YARP::OptionalParameterNode).void } + def visit_optional_parameter_node(node) + @listeners[:on_optional_parameter]&.each { |l| T.unsafe(l).on_optional_parameter(node) } super end - sig { override.params(node: SyntaxTree::BlockVar).void } - def visit_block_var(node) - @listeners[:on_block_var]&.each { |l| T.unsafe(l).on_block_var(node) } + sig { override.params(node: YARP::RequiredParameterNode).void } + def visit_required_parameter_node(node) + @listeners[:on_required_parameter]&.each { |l| T.unsafe(l).on_required_parameter(node) } super end - sig { override.params(node: SyntaxTree::LambdaVar).void } - def visit_lambda_var(node) - @listeners[:on_lambda_var]&.each { |l| T.unsafe(l).on_lambda_var(node) } + sig { override.params(node: YARP::RestParameterNode).void } + def visit_rest_parameter_node(node) + @listeners[:on_rest_parameter]&.each { |l| T.unsafe(l).on_rest_parameter(node) } super end - sig { override.params(node: SyntaxTree::Binary).void } - def visit_binary(node) - super - @listeners[:after_binary]&.each { |l| T.unsafe(l).after_binary(node) } - end - sig { override.params(node: YARP::ConstantReadNode).void } def visit_constant_read_node(node) @listeners[:on_constant_read]&.each { |l| T.unsafe(l).on_constant_read(node) } super end + + sig { override.params(node: YARP::ConstantPathNode).void } + def visit_constant_path_node(node) + @listeners[:on_constant_path]&.each { |l| T.unsafe(l).on_constant_path(node) } + super + end + + sig { override.params(node: YARP::ConstantPathWriteNode).void } + def visit_constant_path_write_node(node) + @listeners[:on_constant_path_write]&.each { |l| T.unsafe(l).on_constant_path_write(node) } + super + end + + sig { override.params(node: YARP::ConstantWriteNode).void } + def visit_constant_write_node(node) + @listeners[:on_constant_write]&.each { |l| T.unsafe(l).on_constant_write(node) } + super + end + + sig { override.params(node: YARP::ConstantAndWriteNode).void } + def visit_constant_and_write_node(node) + @listeners[:on_constant_and_write]&.each { |l| T.unsafe(l).on_constant_and_write(node) } + super + end + + sig { override.params(node: YARP::ConstantOperatorWriteNode).void } + def visit_constant_operator_write_node(node) + @listeners[:on_constant_operator_write]&.each { |l| T.unsafe(l).on_constant_operator_write(node) } + super + end + + sig { override.params(node: YARP::ConstantOrWriteNode).void } + def visit_constant_or_write_node(node) + @listeners[:on_constant_or_write]&.each { |l| T.unsafe(l).on_constant_or_write(node) } + super + end + + sig { override.params(node: YARP::ConstantTargetNode).void } + def visit_constant_target_node(node) + @listeners[:on_constant_target]&.each { |l| T.unsafe(l).on_constant_target(node) } + super + end + + sig { override.params(node: YARP::LocalVariableWriteNode).void } + def visit_local_variable_write_node(node) + @listeners[:on_local_variable_write]&.each { |l| T.unsafe(l).on_local_variable_write(node) } + super + end + + sig { override.params(node: YARP::LocalVariableReadNode).void } + def visit_local_variable_read_node(node) + @listeners[:on_local_variable_read]&.each { |l| T.unsafe(l).on_local_variable_read(node) } + super + end + + sig { override.params(node: YARP::LocalVariableAndWriteNode).void } + def visit_local_variable_and_write_node(node) + @listeners[:on_local_variable_and_write]&.each { |l| T.unsafe(l).on_local_variable_and_write(node) } + super + end + + sig { override.params(node: YARP::LocalVariableOperatorWriteNode).void } + def visit_local_variable_operator_write_node(node) + @listeners[:on_local_variable_operator_write]&.each { |l| T.unsafe(l).on_local_variable_operator_write(node) } + super + end + + sig { override.params(node: YARP::LocalVariableOrWriteNode).void } + def visit_local_variable_or_write_node(node) + @listeners[:on_local_variable_or_write]&.each { |l| T.unsafe(l).on_local_variable_or_write(node) } + super + end + + sig { override.params(node: YARP::LocalVariableTargetNode).void } + def visit_local_variable_target_node(node) + @listeners[:on_local_variable_target]&.each { |l| T.unsafe(l).on_local_variable_target(node) } + super + end + + sig { override.params(node: YARP::LambdaNode).void } + def visit_lambda_node(node) + @listeners[:on_lambda]&.each { |l| T.unsafe(l).on_lambda(node) } + super + @listeners[:after_lambda]&.each { |l| T.unsafe(l).after_lambda(node) } + end end end diff --git a/lib/ruby_lsp/internal.rb b/lib/ruby_lsp/internal.rb index d79e6866d..b54b96f8d 100644 --- a/lib/ruby_lsp/internal.rb +++ b/lib/ruby_lsp/internal.rb @@ -14,6 +14,7 @@ require "ruby_indexer/ruby_indexer" require "core_ext/uri" require "ruby_lsp/utils" +require "ruby_lsp/parameter_scope" require "ruby_lsp/server" require "ruby_lsp/executor" require "ruby_lsp/event_emitter" diff --git a/lib/ruby_lsp/parameter_scope.rb b/lib/ruby_lsp/parameter_scope.rb new file mode 100644 index 000000000..45d928734 --- /dev/null +++ b/lib/ruby_lsp/parameter_scope.rb @@ -0,0 +1,33 @@ +# typed: strict +# frozen_string_literal: true + +module RubyLsp + class ParameterScope + extend T::Sig + + sig { returns(T.nilable(ParameterScope)) } + attr_reader :parent + + sig { params(parent: T.nilable(ParameterScope)).void } + def initialize(parent = nil) + @parent = parent + @parameters = T.let(Set.new, T::Set[Symbol]) + end + + sig { params(name: T.any(String, Symbol)).void } + def <<(name) + @parameters << name.to_sym + end + + sig { params(name: T.any(Symbol, String)).returns(Symbol) } + def type_for(name) + parameter?(name) ? :parameter : :variable + end + + sig { params(name: T.any(Symbol, String)).returns(T::Boolean) } + def parameter?(name) + sym = name.to_sym + @parameters.include?(sym) || (!@parent.nil? && @parent.parameter?(sym)) + end + end +end diff --git a/lib/ruby_lsp/requests/semantic_highlighting.rb b/lib/ruby_lsp/requests/semantic_highlighting.rb index d7f840e92..9ba2e3704 100644 --- a/lib/ruby_lsp/requests/semantic_highlighting.rb +++ b/lib/ruby_lsp/requests/semantic_highlighting.rb @@ -83,7 +83,7 @@ class SemanticHighlighting < Listener class SemanticToken extend T::Sig - sig { returns(SyntaxTree::Location) } + sig { returns(YARP::Location) } attr_reader :location sig { returns(Integer) } @@ -95,7 +95,7 @@ class SemanticToken sig { returns(T::Array[Integer]) } attr_reader :modifier - sig { params(location: SyntaxTree::Location, length: Integer, type: Integer, modifier: T::Array[Integer]).void } + sig { params(location: YARP::Location, length: Integer, type: Integer, modifier: T::Array[Integer]).void } def initialize(location:, length:, type:, modifier:) @location = location @length = length @@ -120,56 +120,66 @@ def initialize(emitter, message_queue, range: nil) @_response = T.let([], ResponseType) @range = range @special_methods = T.let(nil, T.nilable(T::Array[String])) + @current_scope = T.let(ParameterScope.new, ParameterScope) emitter.register( self, - :after_binary, - :on_block_var, :on_call, :on_class, - :on_command, - :on_command_call, - :on_const, :on_def, - :on_field, - :on_kw, - :on_lambda_var, + :after_def, + :on_block, + :after_block, + :on_self, :on_module, - :on_params, - :on_var_field, - :on_var_ref, - :on_vcall, + :on_local_variable_write, + :on_local_variable_read, + :on_block_parameter, + :on_keyword_parameter, + :on_keyword_rest_parameter, + :on_optional_parameter, + :on_required_parameter, + :on_rest_parameter, + :on_constant_read, + :on_constant_write, + :on_constant_and_write, + :on_constant_operator_write, + :on_constant_or_write, + :on_constant_target, + :on_local_variable_and_write, + :on_local_variable_operator_write, + :on_local_variable_or_write, + :on_local_variable_target, + :after_lambda, ) end - sig { params(node: SyntaxTree::CallNode).void } + sig { params(node: YARP::CallNode).void } def on_call(node) return unless visible?(node, @range) message = node.message - if !message.is_a?(Symbol) && !special_method?(message.value) - type = Support::Sorbet.annotation?(node) ? :type : :method - add_token(message.location, type) - end - end + return unless message - sig { params(node: SyntaxTree::Command).void } - def on_command(node) - return unless visible?(node, @range) + # We can't push a semantic token for [] and []= because the argument inside the brackets is a part of + # the message_loc + return if message.start_with?("[") && (message.end_with?("]") || message.end_with?("]=")) - add_token(node.message.location, :method) unless special_method?(node.message.value) - end + return process_regexp_locals(node) if message == "=~" + return if special_method?(message) - sig { params(node: SyntaxTree::CommandCall).void } - def on_command_call(node) - return unless visible?(node, @range) + # Numbered parameters + if /_\d+/.match?(message) + add_token(T.must(node.message_loc), :parameter) + return + end - message = node.message - add_token(message.location, :method) unless message.is_a?(Symbol) + type = Support::Sorbet.annotation?(node) ? :type : :method + add_token(T.must(node.message_loc), type) end - sig { params(node: SyntaxTree::Const).void } - def on_const(node) + sig { params(node: YARP::ConstantReadNode).void } + def on_constant_read(node) return unless visible?(node, @range) # When finding a module or class definition, we will have already pushed a token related to this constant. We # need to look at the previous two tokens and if they match this locatione exactly, avoid pushing another token @@ -179,153 +189,195 @@ def on_const(node) add_token(node.location, :namespace) end - sig { params(node: SyntaxTree::DefNode).void } - def on_def(node) + sig { params(node: YARP::ConstantWriteNode).void } + def on_constant_write(node) return unless visible?(node, @range) - add_token(node.name.location, :method, [:declaration]) + add_token(node.name_loc, :namespace) end - sig { params(node: SyntaxTree::Kw).void } - def on_kw(node) + sig { params(node: YARP::ConstantAndWriteNode).void } + def on_constant_and_write(node) return unless visible?(node, @range) - case node.value - when "self" - add_token(node.location, :variable, [:default_library]) - end + add_token(node.name_loc, :namespace) end - sig { params(node: SyntaxTree::Params).void } - def on_params(node) + sig { params(node: YARP::ConstantOperatorWriteNode).void } + def on_constant_operator_write(node) return unless visible?(node, @range) - node.keywords.each do |keyword, *| - location = keyword.location - add_token(location_without_colon(location), :parameter) - end + add_token(node.name_loc, :namespace) + end - node.requireds.each do |required| - add_token(required.location, :parameter) - end + sig { params(node: YARP::ConstantOrWriteNode).void } + def on_constant_or_write(node) + return unless visible?(node, @range) - rest = node.keyword_rest - if rest && !rest.is_a?(SyntaxTree::ArgsForward) && !rest.is_a?(Symbol) - name = rest.name - add_token(name.location, :parameter) if name - end + add_token(node.name_loc, :namespace) end - sig { params(node: SyntaxTree::Field).void } - def on_field(node) + sig { params(node: YARP::ConstantTargetNode).void } + def on_constant_target(node) return unless visible?(node, @range) - add_token(node.name.location, :method) + add_token(node.location, :namespace) end - sig { params(node: SyntaxTree::VarField).void } - def on_var_field(node) + sig { params(node: YARP::DefNode).void } + def on_def(node) + @current_scope = ParameterScope.new(@current_scope) return unless visible?(node, @range) - value = node.value + add_token(node.name_loc, :method, [:declaration]) + end + + sig { params(node: YARP::DefNode).void } + def after_def(node) + @current_scope = T.must(@current_scope.parent) + end + + sig { params(node: YARP::BlockNode).void } + def on_block(node) + @current_scope = ParameterScope.new(@current_scope) + end + + sig { params(node: YARP::BlockNode).void } + def after_block(node) + process_lambda_or_block_locals(node) + + @current_scope = T.must(@current_scope.parent) + end + + sig { params(node: YARP::LambdaNode).void } + def after_lambda(node) + process_lambda_or_block_locals(node) + end + + sig { params(node: YARP::BlockParameterNode).void } + def on_block_parameter(node) + name = node.name + @current_scope << name.to_sym if name + end + + sig { params(node: YARP::KeywordParameterNode).void } + def on_keyword_parameter(node) + name = node.name + @current_scope << name.to_s.delete_suffix(":").to_sym if name + + return unless visible?(node, @range) + + location = node.name_loc + add_token(location.copy(length: location.length - 1), :parameter) + end + + sig { params(node: YARP::KeywordRestParameterNode).void } + def on_keyword_rest_parameter(node) + name = node.name + + if name + @current_scope << name.to_sym - case value - when SyntaxTree::Ident - type = type_for_local(value) - add_token(value.location, type) + add_token(T.must(node.name_loc), :parameter) if visible?(node, @range) end end - sig { params(node: SyntaxTree::VarRef).void } - def on_var_ref(node) + sig { params(node: YARP::OptionalParameterNode).void } + def on_optional_parameter(node) + @current_scope << node.name return unless visible?(node, @range) - value = node.value + add_token(node.name_loc, :parameter) + end + + sig { params(node: YARP::RequiredParameterNode).void } + def on_required_parameter(node) + @current_scope << node.name + return unless visible?(node, @range) + + add_token(node.location, :parameter) + end - case value - when SyntaxTree::Ident - type = type_for_local(value) - add_token(value.location, type) + sig { params(node: YARP::RestParameterNode).void } + def on_rest_parameter(node) + name = node.name + + if name + @current_scope << name.to_sym + + add_token(T.must(node.name_loc), :parameter) if visible?(node, @range) end end - # All block locals are variables. E.g.: [].each do |x; block_local| - sig { params(node: SyntaxTree::BlockVar).void } - def on_block_var(node) - node.locals.each { |local| add_token(local.location, :variable) } + sig { params(node: YARP::SelfNode).void } + def on_self(node) + return unless visible?(node, @range) + + add_token(node.location, :variable, [:default_library]) end - # All lambda locals are variables. E.g.: ->(x; lambda_local) {} - sig { params(node: SyntaxTree::LambdaVar).void } - def on_lambda_var(node) - node.locals.each { |local| add_token(local.location, :variable) } + sig { params(node: YARP::LocalVariableWriteNode).void } + def on_local_variable_write(node) + return unless visible?(node, @range) + + add_token(node.name_loc, @current_scope.type_for(node.name)) end - sig { params(node: SyntaxTree::VCall).void } - def on_vcall(node) + sig { params(node: YARP::LocalVariableReadNode).void } + def on_local_variable_read(node) return unless visible?(node, @range) - # A VCall may exist as a local in the current_scope. This happens when used named capture groups in a regexp - ident = node.value - value = ident.value - local = @emitter.current_scope.find_local(value) - return if local.nil? && special_method?(value) - - type = if local - :variable - elsif Support::Sorbet.annotation?(node) - :type - else - :method - end + add_token(node.location, @current_scope.type_for(node.name)) + end - add_token(node.value.location, type) + sig { params(node: YARP::LocalVariableAndWriteNode).void } + def on_local_variable_and_write(node) + return unless visible?(node, @range) + + add_token(node.name_loc, @current_scope.type_for(node.name)) end - sig { params(node: SyntaxTree::Binary).void } - def after_binary(node) - # You can only capture local variables with regexp by using the =~ operator - return unless node.operator == :=~ + sig { params(node: YARP::LocalVariableOperatorWriteNode).void } + def on_local_variable_operator_write(node) + return unless visible?(node, @range) - left = node.left - # The regexp needs to be on the left hand side of the =~ for local variable capture - return unless left.is_a?(SyntaxTree::RegexpLiteral) + add_token(node.name_loc, @current_scope.type_for(node.name)) + end - parts = left.parts - return unless parts.one? + sig { params(node: YARP::LocalVariableOrWriteNode).void } + def on_local_variable_or_write(node) + return unless visible?(node, @range) - content = parts.first - return unless content.is_a?(SyntaxTree::TStringContent) + add_token(node.name_loc, @current_scope.type_for(node.name)) + end - # For each capture name we find in the regexp, look for a local in the current_scope - Regexp.new(content.value, Regexp::FIXEDENCODING).names.each do |name| - local = @emitter.current_scope.find_local(name) - next unless local + sig { params(node: YARP::LocalVariableTargetNode).void } + def on_local_variable_target(node) + return unless visible?(node, @range) - local.definitions.each { |definition| add_token(definition, :variable) } - end + add_token(node.location, @current_scope.type_for(node.name)) end - sig { params(node: SyntaxTree::ClassDeclaration).void } + sig { params(node: YARP::ClassNode).void } def on_class(node) return unless visible?(node, @range) - add_token(node.constant.location, :class, [:declaration]) + add_token(node.constant_path.location, :class, [:declaration]) superclass = node.superclass add_token(superclass.location, :class) if superclass end - sig { params(node: SyntaxTree::ModuleDeclaration).void } + sig { params(node: YARP::ModuleNode).void } def on_module(node) return unless visible?(node, @range) - add_token(node.constant.location, :namespace, [:declaration]) + add_token(node.constant_path.location, :namespace, [:declaration]) end - sig { params(location: SyntaxTree::Location, type: Symbol, modifiers: T::Array[Symbol]).void } + sig { params(location: YARP::Location, type: Symbol, modifiers: T::Array[Symbol]).void } def add_token(location, type, modifiers = []) - length = location.end_char - location.start_char + length = location.end_offset - location.start_offset modifiers_indices = modifiers.filter_map { |modifier| TOKEN_MODIFIERS[modifier] } @_response.push( SemanticToken.new( @@ -339,38 +391,47 @@ def add_token(location, type, modifiers = []) private - # Exclude the ":" symbol at the end of a location - # We use it on keyword parameters to be consistent - # with the rest of the parameters - sig { params(location: T.untyped).returns(SyntaxTree::Location) } - def location_without_colon(location) - SyntaxTree::Location.new( - start_line: location.start_line, - start_column: location.start_column, - start_char: location.start_char, - end_char: location.end_char - 1, - end_column: location.end_column - 1, - end_line: location.end_line, - ) - end - - # Textmate provides highlighting for a subset - # of these special Ruby-specific methods. - # We want to utilize that highlighting, so we - # avoid making a semantic token for it. + # Textmate provides highlighting for a subset of these special Ruby-specific methods. We want to utilize that + # highlighting, so we avoid making a semantic token for it. sig { params(method_name: String).returns(T::Boolean) } def special_method?(method_name) SPECIAL_RUBY_METHODS.include?(method_name) end - sig { params(value: SyntaxTree::Ident).returns(Symbol) } - def type_for_local(value) - local = @emitter.current_scope.find_local(value.value) + sig { params(node: YARP::CallNode).void } + def process_regexp_locals(node) + receiver = node.receiver + + # The regexp needs to be the receiver of =~ for local variable capture + return unless receiver.is_a?(YARP::RegularExpressionNode) + + content = receiver.content + loc = receiver.content_loc + + # For each capture name we find in the regexp, look for a local in the current_scope + Regexp.new(content, Regexp::FIXEDENCODING).names.each do |name| + # The +3 is to compensate for the "(?<" part of the capture name + capture_name_offset = T.must(content.index("(?<#{name}>")) + 3 + local_var_loc = loc.copy(start_offset: loc.start_offset + capture_name_offset, length: name.length) + + add_token(local_var_loc, @current_scope.type_for(name)) + end + end + + sig { params(node: T.any(YARP::BlockNode, YARP::LambdaNode)).void } + def process_lambda_or_block_locals(node) + parameters = node.parameters + return unless parameters + + node.locals.each do |local| + next if @current_scope.parameter?(local) + + loc = parameters.location + offset = loc.slice.index(local.to_s) + next unless offset - if local.nil? || local.type == :variable - :variable - else - :parameter + local_var_loc = loc.copy(start_offset: loc.start_offset + offset, length: local.length) + add_token(local_var_loc, :variable) end end end diff --git a/lib/ruby_lsp/requests/support/sorbet.rb b/lib/ruby_lsp/requests/support/sorbet.rb index b8b5c5dcf..1dcd96e5c 100644 --- a/lib/ruby_lsp/requests/support/sorbet.rb +++ b/lib/ruby_lsp/requests/support/sorbet.rb @@ -46,7 +46,7 @@ class << self ).returns(T::Boolean) end def annotation?(node) - ANNOTATIONS[node.name]&.match?(node) + !!ANNOTATIONS[node.name]&.match?(node) end end end diff --git a/test/expectations/semantic_highlighting/destructured_splat_block_arg.exp.json b/test/expectations/semantic_highlighting/destructured_splat_block_arg.exp.json index 6a981c13e..686ac1dea 100644 --- a/test/expectations/semantic_highlighting/destructured_splat_block_arg.exp.json +++ b/test/expectations/semantic_highlighting/destructured_splat_block_arg.exp.json @@ -9,8 +9,15 @@ }, { "delta_line": 0, - "delta_start_char": 9, - "length": 10, + "delta_start_char": 10, + "length": 1, + "token_type": 7, + "token_modifiers": 0 + }, + { + "delta_line": 0, + "delta_start_char": 4, + "length": 4, "token_type": 7, "token_modifiers": 0 } diff --git a/test/expectations/semantic_highlighting/method_params.exp.json b/test/expectations/semantic_highlighting/method_params.exp.json index b3cdbc2a8..05ffcf886 100644 --- a/test/expectations/semantic_highlighting/method_params.exp.json +++ b/test/expectations/semantic_highlighting/method_params.exp.json @@ -16,7 +16,28 @@ }, { "delta_line": 0, - "delta_start_char": 17, + "delta_start_char": 3, + "length": 1, + "token_type": 7, + "token_modifiers": 0 + }, + { + "delta_line": 0, + "delta_start_char": 8, + "length": 1, + "token_type": 7, + "token_modifiers": 0 + }, + { + "delta_line": 0, + "delta_start_char": 3, + "length": 1, + "token_type": 7, + "token_modifiers": 0 + }, + { + "delta_line": 0, + "delta_start_char": 3, "length": 1, "token_type": 7, "token_modifiers": 0 diff --git a/test/expectations/semantic_highlighting/numbered_parameters.exp.json b/test/expectations/semantic_highlighting/numbered_parameters.exp.json index 234c757cb..5ac2c83f9 100644 --- a/test/expectations/semantic_highlighting/numbered_parameters.exp.json +++ b/test/expectations/semantic_highlighting/numbered_parameters.exp.json @@ -4,7 +4,7 @@ "delta_line": 1, "delta_start_char": 7, "length": 2, - "token_type": 8, + "token_type": 7, "token_modifiers": 0 } ] diff --git a/test/expectations/semantic_highlighting/pinned_variable.exp.json b/test/expectations/semantic_highlighting/pinned_variable.exp.json index 544b01c8d..b84ba009a 100644 --- a/test/expectations/semantic_highlighting/pinned_variable.exp.json +++ b/test/expectations/semantic_highlighting/pinned_variable.exp.json @@ -16,7 +16,14 @@ }, { "delta_line": 2, - "delta_start_char": 10, + "delta_start_char": 6, + "length": 1, + "token_type": 8, + "token_modifiers": 0 + }, + { + "delta_line": 0, + "delta_start_char": 4, "length": 4, "token_type": 8, "token_modifiers": 0 diff --git a/test/parameter_scope_test.rb b/test/parameter_scope_test.rb new file mode 100644 index 000000000..a9621d7a1 --- /dev/null +++ b/test/parameter_scope_test.rb @@ -0,0 +1,27 @@ +# typed: true +# frozen_string_literal: true + +require "test_helper" + +class ParameterScopeTest < Minitest::Test + def test_finding_parameter_in_immediate_scope + scope = RubyLsp::ParameterScope.new + scope << "foo" + + assert(scope.parameter?("foo")) + end + + def test_finding_parameter_in_parent_scope + parent = RubyLsp::ParameterScope.new + parent << "foo" + + scope = RubyLsp::ParameterScope.new(parent) + + assert(scope.parameter?("foo")) + end + + def test_not_finding_parameter + scope = RubyLsp::ParameterScope.new + refute(scope.parameter?("foo")) + end +end diff --git a/test/requests/semantic_highlighting_expectations_test.rb b/test/requests/semantic_highlighting_expectations_test.rb index 24e1ed672..9c183b97f 100644 --- a/test/requests/semantic_highlighting_expectations_test.rb +++ b/test/requests/semantic_highlighting_expectations_test.rb @@ -5,7 +5,7 @@ require "expectations/expectations_test_runner" class SemanticHighlightingExpectationsTest < ExpectationsTestRunner - # expectations_tests RubyLsp::Requests::SemanticHighlighting, "semantic_highlighting" + expectations_tests RubyLsp::Requests::SemanticHighlighting, "semantic_highlighting" def run_expectations(source) message_queue = Thread::Queue.new diff --git a/test/requests/support/semantic_token_encoder_test.rb b/test/requests/support/semantic_token_encoder_test.rb index 0fb5f0f8f..7f6dc0d7d 100644 --- a/test/requests/support/semantic_token_encoder_test.rb +++ b/test/requests/support/semantic_token_encoder_test.rb @@ -96,15 +96,12 @@ def test_encoded_modifiers_with_some_modifiers private def stub_token(start_line, start_column, length, type, modifier) + location = YARP::Location.new("", 123, 123) + location.expects(:start_line).returns(start_line).at_least_once + location.expects(:start_column).returns(start_column).at_least_once + RubyLsp::Requests::SemanticHighlighting::SemanticToken.new( - location: SyntaxTree::Location.new( - start_line: start_line, - start_column: start_column, - start_char: 0, - end_char: 0, - end_column: 0, - end_line: 0, - ), + location: location, length: length, type: type, modifier: modifier,