Skip to content

Commit

Permalink
code actions for attribute accessor creation
Browse files Browse the repository at this point in the history
  • Loading branch information
rogancodes committed Oct 20, 2024
1 parent ef1a27d commit 41cc532
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 1 deletion.
91 changes: 91 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 @@ -329,6 +333,93 @@ 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)
return Error::EmptySelection if source_range[:start] == source_range[:end]

node = @document.locate_first_within_range(
@code_action.dig(:data, :range),
node_types: [
Prism::InstanceVariableAndWriteNode,
Prism::InstanceVariableOperatorWriteNode,
Prism::InstanceVariableOrWriteNode,
Prism::InstanceVariableReadNode,
Prism::InstanceVariableTargetNode,
Prism::InstanceVariableWriteNode,
],
)
return Error::EmptySelection if node.nil?

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

scanner = @document.create_scanner
start_index = scanner.find_char_position(
line: node.location.start_line,
character: node.location.start_character_column,
)
node_context = RubyDocument.locate(
@document.parse_result.value,
start_index,
node_types: [
Prism::ClassNode,
Prism::ModuleNode,
Prism::SingletonClassNode,
],
code_units_cache: @document.code_units_cache,
)
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
18 changes: 18 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,9 @@ 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"

class << self
extend T::Sig
Expand Down Expand Up @@ -65,6 +68,21 @@ def perform
kind: Constant::CodeActionKind::REFACTOR_REWRITE,
data: { range: @range, uri: @uri.to_s },
)
code_actions << Interface::CodeAction.new(
title: CREATE_ATTRIBUTE_READER,
kind: Constant::CodeActionKind::EMPTY,
data: { range: @range, uri: @uri.to_s },
)
code_actions << Interface::CodeAction.new(
title: CREATE_ATTRIBUTE_WRITER,
kind: Constant::CodeActionKind::EMPTY,
data: { range: @range, uri: @uri.to_s },
)
code_actions << Interface::CodeAction.new(
title: CREATE_ATTRIBUTE_ACCESSOR,
kind: Constant::CodeActionKind::EMPTY,
data: { range: @range, uri: @uri.to_s },
)
end

code_actions
Expand Down
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 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_accessor :foo\n\n"
}
]
}
]
}
}
}
51 changes: 51 additions & 0 deletions test/expectations/code_actions/aref_field.exp.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,57 @@
},
"uri": "file:///fake"
}
},
{
"title": "Create Attribute Reader",
"kind": "",
"data": {
"range": {
"start": {
"line": 2,
"character": 9
},
"end": {
"line": 2,
"character": 13
}
},
"uri": "file:///fake"
}
},
{
"title": "Create Attribute Writer",
"kind": "",
"data": {
"range": {
"start": {
"line": 2,
"character": 9
},
"end": {
"line": 2,
"character": 13
}
},
"uri": "file:///fake"
}
},
{
"title": "Create Attribute Accessor",
"kind": "",
"data": {
"range": {
"start": {
"line": 2,
"character": 9
},
"end": {
"line": 2,
"character": 13
}
},
"uri": "file:///fake"
}
}
]
}
5 changes: 5 additions & 0 deletions test/fixtures/create_attribute_accessor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Foo
def bar
@foo = "foo"
end
end
5 changes: 5 additions & 0 deletions test/fixtures/create_attribute_reader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Foo
def bar
@foo = "foo"
end
end
7 changes: 7 additions & 0 deletions test/fixtures/create_attribute_writer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Foo
class << self
def bar
@foo = "foo"
end
end
end
16 changes: 15 additions & 1 deletion test/requests/code_actions_expectations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ def map_actions(expectation)
refactors = expectation
.select { |action| action["kind"].start_with?("refactor") }
.map { |action| code_action_for_refactor(action) }
result = [*quickfixes, *refactors]
empty_kind = expectation
.select { |action| action["kind"] == "" }
.map { |action| code_action_for_refactor(action) }
result = [*quickfixes, *refactors, *empty_kind]

JSON.parse(result.to_json)
end
Expand Down Expand Up @@ -83,4 +86,15 @@ def code_action_for_refactor(refactor)
},
)
end

def code_action_for_empty_kind(expectations)
LanguageServer::Protocol::Interface::CodeAction.new(
title: expectations["title"],
kind: expectations["kind"],
data: {
range: expectations.dig("data", "range"),
uri: expectations.dig("data", "uri"),
},
)
end
end

0 comments on commit 41cc532

Please sign in to comment.