lib/ruby_lsp/requests/code_actions.rb
# typed: strict
# frozen_string_literal: true
module RubyLsp
module Requests
# The [code actions](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction)
# request informs the editor of RuboCop quick fixes that can be applied. These are accessible by hovering over a
# specific diagnostic.
class CodeActions < Request
EXTRACT_TO_VARIABLE_TITLE = "Refactor: Extract Variable"
EXTRACT_TO_METHOD_TITLE = "Refactor: Extract Method"
TOGGLE_BLOCK_STYLE_TITLE = "Refactor: Toggle block style"
CREATE_ATTRIBUTE_READER = "Create Attribute Reader"
CREATE_ATTRIBUTE_WRITER = "Create Attribute Writer"
CREATE_ATTRIBUTE_ACCESSOR = "Create Attribute Accessor"
INSTANCE_VARIABLE_NODES = [
Prism::InstanceVariableAndWriteNode,
Prism::InstanceVariableOperatorWriteNode,
Prism::InstanceVariableOrWriteNode,
Prism::InstanceVariableReadNode,
Prism::InstanceVariableTargetNode,
Prism::InstanceVariableWriteNode,
] #: Array[singleton(Prism::Node)]
class << self
#: -> Interface::CodeActionRegistrationOptions
def provider
Interface::CodeActionRegistrationOptions.new(
document_selector: nil,
resolve_provider: true,
)
end
end
#: ((RubyDocument | ERBDocument) document, Hash[Symbol, untyped] range, Hash[Symbol, untyped] context) -> void
def initialize(document, range, context)
super()
@document = document
@uri = document.uri #: URI::Generic
@range = range
@context = context
end
# @override
#: -> (Array[Interface::CodeAction] & Object)?
def perform
diagnostics = @context[:diagnostics]
code_actions = diagnostics.flat_map do |diagnostic|
diagnostic.dig(:data, :code_actions) || []
end
# Only add refactor actions if there's a non empty selection in the editor
unless @range.dig(:start) == @range.dig(:end)
code_actions << Interface::CodeAction.new(
title: EXTRACT_TO_VARIABLE_TITLE,
kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
data: { range: @range, uri: @uri.to_s },
)
code_actions << Interface::CodeAction.new(
title: EXTRACT_TO_METHOD_TITLE,
kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
data: { range: @range, uri: @uri.to_s },
)
code_actions << Interface::CodeAction.new(
title: TOGGLE_BLOCK_STYLE_TITLE,
kind: Constant::CodeActionKind::REFACTOR_REWRITE,
data: { range: @range, uri: @uri.to_s },
)
end
code_actions.concat(attribute_actions)
code_actions
end
private
#: -> Array[Interface::CodeAction]
def attribute_actions
return [] unless @document.is_a?(RubyDocument)
node = if @range.dig(:start) != @range.dig(:end)
@document.locate_first_within_range(
@range,
node_types: INSTANCE_VARIABLE_NODES,
)
end
if node.nil?
node_context = @document.locate_node(
@range[:start],
node_types: CodeActions::INSTANCE_VARIABLE_NODES,
)
return [] unless INSTANCE_VARIABLE_NODES.include?(node_context.node.class)
end
[
Interface::CodeAction.new(
title: CREATE_ATTRIBUTE_READER,
kind: Constant::CodeActionKind::EMPTY,
data: { range: @range, uri: @uri.to_s },
),
Interface::CodeAction.new(
title: CREATE_ATTRIBUTE_WRITER,
kind: Constant::CodeActionKind::EMPTY,
data: { range: @range, uri: @uri.to_s },
),
Interface::CodeAction.new(
title: CREATE_ATTRIBUTE_ACCESSOR,
kind: Constant::CodeActionKind::EMPTY,
data: { range: @range, uri: @uri.to_s },
),
]
end
end
end
end