Skip to content

Code actions for attribute accessor creation #2739

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
88 changes: 88 additions & 0 deletions lib/ruby_lsp/requests/code_action_resolve.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ def perform
refactor_method
when CodeActions::TOGGLE_BLOCK_STYLE_TITLE
switch_block_style
when CodeActions::CREATE_ATTRIBUTE_READER,
CodeActions::CREATE_ATTRIBUTE_WRITER,
CodeActions::CREATE_ATTRIBUTE_ACCESSOR
create_attribute_accessor
else
Error::UnknownCodeAction
end
Expand Down Expand Up @@ -325,6 +329,90 @@ def switch_block_body(body, indentation)

indentation ? body_content.gsub(";", "\n") : "#{body_content.gsub("\n", ";")} "
end

sig { returns(T.any(Interface::CodeAction, Error)) }
def create_attribute_accessor
source_range = @code_action.dig(:data, :range)

node = if source_range[:start] != source_range[:end]
@document.locate_first_within_range(
@code_action.dig(:data, :range),
node_types: CodeActions::INSTANCE_VARIABLE_NODES,
)
end

if node.nil?
node_context = @document.locate_node(
source_range[:start],
node_types: CodeActions::INSTANCE_VARIABLE_NODES,
)
node = node_context.node

return Error::EmptySelection unless CodeActions::INSTANCE_VARIABLE_NODES.include?(node.class)
end

node = T.cast(
node,
T.any(
Prism::InstanceVariableAndWriteNode,
Prism::InstanceVariableOperatorWriteNode,
Prism::InstanceVariableOrWriteNode,
Prism::InstanceVariableReadNode,
Prism::InstanceVariableTargetNode,
Prism::InstanceVariableWriteNode,
),
)

node_context = @document.locate_node(
{
line: node.location.start_line,
character: node.location.start_character_column,
},
node_types: [
Prism::ClassNode,
Prism::ModuleNode,
Prism::SingletonClassNode,
],
)
closest_node = node_context.node
return Error::InvalidTargetRange if closest_node.nil?

attribute_name = node.name[1..]
indentation = " " * (closest_node.location.start_column + 2)
attribute_accessor_source = T.must(
case @code_action[:title]
when CodeActions::CREATE_ATTRIBUTE_READER
"#{indentation}attr_reader :#{attribute_name}\n\n"
when CodeActions::CREATE_ATTRIBUTE_WRITER
"#{indentation}attr_writer :#{attribute_name}\n\n"
when CodeActions::CREATE_ATTRIBUTE_ACCESSOR
"#{indentation}attr_accessor :#{attribute_name}\n\n"
end,
)

target_start_line = closest_node.location.start_line
target_range = {
start: { line: target_start_line, character: 0 },
end: { line: target_start_line, character: 0 },
}

Interface::CodeAction.new(
title: @code_action[:title],
edit: Interface::WorkspaceEdit.new(
document_changes: [
Interface::TextDocumentEdit.new(
text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
uri: @code_action.dig(:data, :uri),
version: nil,
),
edits: [
create_text_edit(target_range, attribute_accessor_source),
],
),
],
),
)
end
end
end
end
56 changes: 56 additions & 0 deletions lib/ruby_lsp/requests/code_actions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ 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 = T.let(
[
Prism::InstanceVariableAndWriteNode,
Prism::InstanceVariableOperatorWriteNode,
Prism::InstanceVariableOrWriteNode,
Prism::InstanceVariableReadNode,
Prism::InstanceVariableTargetNode,
Prism::InstanceVariableWriteNode,
],
T::Array[T.class_of(Prism::Node)],
)

class << self
extend T::Sig
Expand Down Expand Up @@ -66,9 +81,50 @@ def perform
data: { range: @range, uri: @uri.to_s },
)
end
code_actions.concat(attribute_actions)

code_actions
end

private

sig { returns(T::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
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"params": {
"kind": "",
"title": "Create Attribute Accessor",
"data": {
"range": {
"start": {
"line": 2,
"character": 0
},
"end": {
"line": 2,
"character": 16
}
},
"uri": "file:///fake"
}
},
"result": {
"title": "Create Attribute Accessor",
"edit": {
"documentChanges": [
{
"textDocument": {
"uri": "file:///fake",
"version": null
},
"edits": [
{
"range": {
"start": {
"line": 1,
"character": 0
},
"end": {
"line": 1,
"character": 0
}
},
"newText": " attr_accessor :foo\n\n"
}
]
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"params": {
"kind": "",
"title": "Create Attribute Accessor",
"data": {
"range": {
"start": {
"line": 0,
"character": 1
},
"end": {
"line": 0,
"character": 1
}
},
"uri": "file:///fake"
}
},
"result": {
"title": "Create Attribute Accessor",
"edit": {
"documentChanges": [
{
"textDocument": {
"uri": "file:///fake",
"version": null
},
"edits": [
{
"range": {
"start": {
"line": 1,
"character": 0
},
"end": {
"line": 1,
"character": 0
}
},
"newText": " attr_accessor :foo\n\n"
}
]
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"params": {
"kind": "",
"title": "Create Attribute Reader",
"data": {
"range": {
"start": {
"line": 2,
"character": 0
},
"end": {
"line": 2,
"character": 16
}
},
"uri": "file:///fake"
}
},
"result": {
"title": "Create Attribute Reader",
"edit": {
"documentChanges": [
{
"textDocument": {
"uri": "file:///fake",
"version": null
},
"edits": [
{
"range": {
"start": {
"line": 1,
"character": 0
},
"end": {
"line": 1,
"character": 0
}
},
"newText": " attr_reader :foo\n\n"
}
]
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"params": {
"kind": "",
"title": "Create Attribute Writer",
"data": {
"range": {
"start": {
"line": 3,
"character": 0
},
"end": {
"line": 4,
"character": 0
}
},
"uri": "file:///fake"
}
},
"result": {
"title": "Create Attribute Writer",
"edit": {
"documentChanges": [
{
"textDocument": {
"uri": "file:///fake",
"version": null
},
"edits": [
{
"range": {
"start": {
"line": 2,
"character": 0
},
"end": {
"line": 2,
"character": 0
}
},
"newText": " attr_writer :foo\n\n"
}
]
}
]
}
}
}
Loading