ruby-lsp-refactor 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +68 -0
- data/README.md +553 -115
- data/lib/ruby/lsp/refactor/version.rb +1 -1
- data/lib/ruby_lsp/ruby_lsp_refactor/addon.rb +54 -17
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/accessor_listener.rb +156 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/array_listener.rb +2 -2
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/conditional_listener.rb +14 -14
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/constant_listener.rb +118 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/early_return_listener.rb +105 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/enumerable_listener.rb +90 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/extract_include_file_listener.rb +172 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/extract_predicate_listener.rb +138 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/hash_listener.rb +7 -7
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/logical_operator_listener.rb +70 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/method_listener.rb +37 -109
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/raise_listener.rb +70 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/rescue_listener.rb +85 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/rspec_let_listener.rb +61 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/string_array_listener.rb +88 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/string_freeze_listener.rb +86 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/string_listener.rb +2 -2
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/super_listener.rb +95 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/tap_listener.rb +133 -0
- data/lib/ruby_lsp/ruby_lsp_refactor/listeners/variable_listener.rb +7 -49
- data/lib/ruby_lsp/ruby_lsp_refactor/support/node_helpers.rb +62 -12
- data/lib/ruby_lsp/test_helper.rb +5 -5
- data/test/ruby_lsp_refactor/accessor_listener_test.rb +91 -0
- data/test/ruby_lsp_refactor/constant_listener_test.rb +68 -0
- data/test/ruby_lsp_refactor/early_return_listener_test.rb +156 -0
- data/test/ruby_lsp_refactor/enumerable_listener_test.rb +80 -0
- data/test/ruby_lsp_refactor/extract_include_file_listener_test.rb +189 -0
- data/test/ruby_lsp_refactor/extract_predicate_listener_test.rb +113 -0
- data/test/ruby_lsp_refactor/logical_operator_listener_test.rb +66 -0
- data/test/ruby_lsp_refactor/method_listener_test.rb +3 -52
- data/test/ruby_lsp_refactor/raise_listener_test.rb +64 -0
- data/test/ruby_lsp_refactor/rescue_listener_test.rb +72 -0
- data/test/ruby_lsp_refactor/rspec_let_listener_test.rb +54 -0
- data/test/ruby_lsp_refactor/string_array_listener_test.rb +64 -0
- data/test/ruby_lsp_refactor/string_freeze_listener_test.rb +52 -0
- data/test/ruby_lsp_refactor/string_listener_test.rb +2 -2
- data/test/ruby_lsp_refactor/super_listener_test.rb +65 -0
- data/test/ruby_lsp_refactor/tap_listener_test.rb +144 -0
- data/test/ruby_lsp_refactor/variable_listener_test.rb +0 -23
- metadata +42 -13
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "ruby_lsp/test_helper"
|
|
5
|
+
|
|
6
|
+
module RubyLsp
|
|
7
|
+
module Refactor
|
|
8
|
+
class ExtractIncludeFileListenerTest < Minitest::Test
|
|
9
|
+
include RubyLsp::Refactor::TestHelper
|
|
10
|
+
|
|
11
|
+
def find_action(actions, title_pattern)
|
|
12
|
+
actions.find { |a| a.title.match?(title_pattern) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# ── core acceptance ────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
def test_extracts_module_to_new_file
|
|
18
|
+
source = <<~RUBY
|
|
19
|
+
module Greetable
|
|
20
|
+
def greet = "hello"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class User
|
|
24
|
+
include Greetable
|
|
25
|
+
end
|
|
26
|
+
RUBY
|
|
27
|
+
|
|
28
|
+
actions = code_actions_for(source, line: 0)
|
|
29
|
+
action = find_action(actions, /Extract to include file/)
|
|
30
|
+
refute_nil action
|
|
31
|
+
|
|
32
|
+
assert_match(/greetable\.rb/, action.title)
|
|
33
|
+
|
|
34
|
+
doc_changes = action.edit.document_changes
|
|
35
|
+
assert_equal 3, doc_changes.length,
|
|
36
|
+
"Expected CreateFile + 2 TextDocumentEdits, got #{doc_changes.length}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_create_file_operation_is_first
|
|
40
|
+
source = <<~RUBY
|
|
41
|
+
module Greetable
|
|
42
|
+
def greet = "hello"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class User; end
|
|
46
|
+
RUBY
|
|
47
|
+
|
|
48
|
+
actions = code_actions_for(source, line: 0)
|
|
49
|
+
action = find_action(actions, /Extract to include file/)
|
|
50
|
+
refute_nil action
|
|
51
|
+
|
|
52
|
+
create_op = action.edit.document_changes.first
|
|
53
|
+
assert_equal "create", create_op.kind
|
|
54
|
+
assert_match(/greetable\.rb/, create_op.uri)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_new_file_content_includes_frozen_comment_and_source
|
|
58
|
+
source = <<~RUBY
|
|
59
|
+
module Greetable
|
|
60
|
+
def greet = "hello"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class User; end
|
|
64
|
+
RUBY
|
|
65
|
+
|
|
66
|
+
actions = code_actions_for(source, line: 0)
|
|
67
|
+
action = find_action(actions, /Extract to include file/)
|
|
68
|
+
refute_nil action
|
|
69
|
+
|
|
70
|
+
# Second document_change is the TextDocumentEdit writing the new file.
|
|
71
|
+
write_edit = action.edit.document_changes[1]
|
|
72
|
+
content = write_edit.edits.first.new_text
|
|
73
|
+
|
|
74
|
+
assert_match(/# frozen_string_literal: true/, content)
|
|
75
|
+
assert_match(/module Greetable/, content)
|
|
76
|
+
assert_match(/def greet = "hello"/, content)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_source_file_is_replaced_with_require_relative
|
|
80
|
+
source = <<~RUBY
|
|
81
|
+
module Greetable
|
|
82
|
+
def greet = "hello"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class User; end
|
|
86
|
+
RUBY
|
|
87
|
+
|
|
88
|
+
actions = code_actions_for(source, line: 0)
|
|
89
|
+
action = find_action(actions, /Extract to include file/)
|
|
90
|
+
refute_nil action
|
|
91
|
+
|
|
92
|
+
# Third document_change edits the source file.
|
|
93
|
+
source_edit = action.edit.document_changes[2]
|
|
94
|
+
new_text = source_edit.edits.first.new_text
|
|
95
|
+
|
|
96
|
+
assert_match(/require_relative "greetable"/, new_text)
|
|
97
|
+
refute_match(/module Greetable/, new_text)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_new_file_uri_is_in_same_directory_as_source
|
|
101
|
+
source = <<~RUBY
|
|
102
|
+
module Greetable
|
|
103
|
+
def greet = "hello"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class User; end
|
|
107
|
+
RUBY
|
|
108
|
+
|
|
109
|
+
actions = code_actions_for(source, line: 0)
|
|
110
|
+
action = find_action(actions, /Extract to include file/)
|
|
111
|
+
refute_nil action
|
|
112
|
+
|
|
113
|
+
create_op = action.edit.document_changes.first
|
|
114
|
+
source_dir = File.dirname(URI("file:///test/fixture.rb").path)
|
|
115
|
+
expected_path = File.join(source_dir, "greetable.rb")
|
|
116
|
+
|
|
117
|
+
assert_equal "file://#{expected_path}", create_op.uri
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def test_converts_camel_case_module_name_to_snake_case_filename
|
|
121
|
+
source = <<~RUBY
|
|
122
|
+
module MyHelperModule
|
|
123
|
+
def help = true
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
class App; end
|
|
127
|
+
RUBY
|
|
128
|
+
|
|
129
|
+
actions = code_actions_for(source, line: 0)
|
|
130
|
+
action = find_action(actions, /Extract to include file/)
|
|
131
|
+
refute_nil action
|
|
132
|
+
|
|
133
|
+
assert_match(/my_helper_module\.rb/, action.title)
|
|
134
|
+
create_op = action.edit.document_changes.first
|
|
135
|
+
assert_match(/my_helper_module\.rb/, create_op.uri)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def test_works_for_class_nodes_too
|
|
139
|
+
source = <<~RUBY
|
|
140
|
+
class AdminUser
|
|
141
|
+
def admin? = true
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
class User; end
|
|
145
|
+
RUBY
|
|
146
|
+
|
|
147
|
+
actions = code_actions_for(source, line: 0)
|
|
148
|
+
action = find_action(actions, /Extract to include file/)
|
|
149
|
+
refute_nil action
|
|
150
|
+
|
|
151
|
+
assert_match(/admin_user\.rb/, action.title)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# ── negative cases ─────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
def test_does_not_offer_when_module_is_only_top_level_node
|
|
157
|
+
# Nothing else in the file — extraction would leave an empty source.
|
|
158
|
+
source = <<~RUBY
|
|
159
|
+
module Greetable
|
|
160
|
+
def greet = "hello"
|
|
161
|
+
end
|
|
162
|
+
RUBY
|
|
163
|
+
|
|
164
|
+
actions = code_actions_for(source, line: 0)
|
|
165
|
+
assert_nil find_action(actions, /Extract to include file/)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def test_does_not_offer_for_nested_module
|
|
169
|
+
source = <<~RUBY
|
|
170
|
+
class User
|
|
171
|
+
module Callbacks
|
|
172
|
+
def before_save = nil
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
RUBY
|
|
176
|
+
|
|
177
|
+
# Cursor on the nested module (line 1)
|
|
178
|
+
actions = code_actions_for(source, line: 1)
|
|
179
|
+
assert_nil find_action(actions, /Extract to include file/)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# ── resilience ─────────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
def test_does_not_raise_on_empty_source
|
|
185
|
+
assert_silent { code_actions_for("", line: 0) }
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "ruby_lsp/test_helper"
|
|
5
|
+
|
|
6
|
+
module RubyLsp
|
|
7
|
+
module Refactor
|
|
8
|
+
class ExtractPredicateListenerTest < Minitest::Test
|
|
9
|
+
include RubyLsp::Refactor::TestHelper
|
|
10
|
+
|
|
11
|
+
def find_action(actions, title)
|
|
12
|
+
actions.find { |a| a.title == title }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def all_edits(action)
|
|
16
|
+
action.edit.changes.values.flatten
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# ── core acceptance ────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
def test_extracts_and_compound_into_predicate_methods
|
|
22
|
+
source = <<~RUBY
|
|
23
|
+
def eligible_for_return?
|
|
24
|
+
expired_orders.exclude?(self) && self.value > MINIMUM_RETURN_VALUE
|
|
25
|
+
end
|
|
26
|
+
RUBY
|
|
27
|
+
|
|
28
|
+
actions = code_actions_for(source, line: 1)
|
|
29
|
+
action = find_action(actions, "Extract predicate methods")
|
|
30
|
+
refute_nil action
|
|
31
|
+
|
|
32
|
+
edits = all_edits(action)
|
|
33
|
+
assert_equal 2, edits.size
|
|
34
|
+
|
|
35
|
+
replace_edit = edits.find { |e| e.new_text.include?("predicate_1?") && e.new_text.include?("&&") }
|
|
36
|
+
insert_edit = edits.find { |e| e.new_text.include?("def predicate_1?") }
|
|
37
|
+
|
|
38
|
+
refute_nil replace_edit
|
|
39
|
+
refute_nil insert_edit
|
|
40
|
+
|
|
41
|
+
assert_match(/predicate_1\? && predicate_2\?/, replace_edit.new_text)
|
|
42
|
+
assert_match(/def predicate_1\?/, insert_edit.new_text)
|
|
43
|
+
assert_match(/expired_orders\.exclude\?\(self\)/, insert_edit.new_text)
|
|
44
|
+
assert_match(/def predicate_2\?/, insert_edit.new_text)
|
|
45
|
+
assert_match(/self\.value > MINIMUM_RETURN_VALUE/, insert_edit.new_text)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_extracts_or_compound_into_predicate_methods
|
|
49
|
+
source = <<~RUBY
|
|
50
|
+
def should_notify?
|
|
51
|
+
user.admin? || user.subscribed?
|
|
52
|
+
end
|
|
53
|
+
RUBY
|
|
54
|
+
|
|
55
|
+
actions = code_actions_for(source, line: 1)
|
|
56
|
+
action = find_action(actions, "Extract predicate methods")
|
|
57
|
+
refute_nil action
|
|
58
|
+
|
|
59
|
+
edits = all_edits(action)
|
|
60
|
+
replace_edit = edits.find { |e| e.new_text.include?("||") }
|
|
61
|
+
insert_edit = edits.find { |e| e.new_text.include?("def predicate_1?") }
|
|
62
|
+
|
|
63
|
+
refute_nil replace_edit
|
|
64
|
+
refute_nil insert_edit
|
|
65
|
+
assert_match(/predicate_1\? \|\| predicate_2\?/, replace_edit.new_text)
|
|
66
|
+
assert_match(/user\.admin\?/, insert_edit.new_text)
|
|
67
|
+
assert_match(/user\.subscribed\?/, insert_edit.new_text)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_inserts_private_section_after_def
|
|
71
|
+
source = <<~RUBY
|
|
72
|
+
def eligible?
|
|
73
|
+
a? && b?
|
|
74
|
+
end
|
|
75
|
+
RUBY
|
|
76
|
+
|
|
77
|
+
actions = code_actions_for(source, line: 1)
|
|
78
|
+
action = find_action(actions, "Extract predicate methods")
|
|
79
|
+
refute_nil action
|
|
80
|
+
|
|
81
|
+
edits = all_edits(action)
|
|
82
|
+
insert_edit = edits.find { |e| e.new_text.include?("def predicate_1?") }
|
|
83
|
+
assert_match(/private/, insert_edit.new_text)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# ── negative cases ─────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
def test_does_not_offer_when_method_has_multiple_statements
|
|
89
|
+
source = <<~RUBY
|
|
90
|
+
def process
|
|
91
|
+
validate!
|
|
92
|
+
a? && b?
|
|
93
|
+
end
|
|
94
|
+
RUBY
|
|
95
|
+
|
|
96
|
+
actions = code_actions_for(source, line: 2)
|
|
97
|
+
assert_nil find_action(actions, "Extract predicate methods")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_does_not_offer_outside_a_method
|
|
101
|
+
source = "a? && b?\n"
|
|
102
|
+
actions = code_actions_for(source, line: 0)
|
|
103
|
+
assert_nil find_action(actions, "Extract predicate methods")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# ── resilience ─────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
def test_does_not_raise_on_empty_source
|
|
109
|
+
assert_silent { code_actions_for("", line: 0) }
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "ruby_lsp/test_helper"
|
|
5
|
+
|
|
6
|
+
module RubyLsp
|
|
7
|
+
module Refactor
|
|
8
|
+
class LogicalOperatorListenerTest < Minitest::Test
|
|
9
|
+
include RubyLsp::Refactor::TestHelper
|
|
10
|
+
|
|
11
|
+
def find_action(actions, title_pattern)
|
|
12
|
+
actions.find { |a| a.title.match?(title_pattern) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def single_edit(action)
|
|
16
|
+
edits = action.edit.changes.values.flatten
|
|
17
|
+
assert_equal 1, edits.size
|
|
18
|
+
edits.first
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_converts_double_ampersand_to_and
|
|
22
|
+
source = "user.valid? && user.save\n"
|
|
23
|
+
actions = code_actions_for(source, line: 0)
|
|
24
|
+
action = find_action(actions, /&&.*and/)
|
|
25
|
+
refute_nil action
|
|
26
|
+
|
|
27
|
+
edit = single_edit(action)
|
|
28
|
+
assert_equal "user.valid? and user.save", edit.new_text.strip
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_converts_and_to_double_ampersand
|
|
32
|
+
source = "user.valid? and user.save\n"
|
|
33
|
+
actions = code_actions_for(source, line: 0)
|
|
34
|
+
action = find_action(actions, /and.*&&/)
|
|
35
|
+
refute_nil action
|
|
36
|
+
|
|
37
|
+
edit = single_edit(action)
|
|
38
|
+
assert_equal "user.valid? && user.save", edit.new_text.strip
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_converts_double_pipe_to_or
|
|
42
|
+
source = "a || b\n"
|
|
43
|
+
actions = code_actions_for(source, line: 0)
|
|
44
|
+
action = find_action(actions, /\|\|.*or/)
|
|
45
|
+
refute_nil action
|
|
46
|
+
|
|
47
|
+
edit = single_edit(action)
|
|
48
|
+
assert_equal "a or b", edit.new_text.strip
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_converts_or_to_double_pipe
|
|
52
|
+
source = "a or b\n"
|
|
53
|
+
actions = code_actions_for(source, line: 0)
|
|
54
|
+
action = find_action(actions, /or.*\|\|/)
|
|
55
|
+
refute_nil action
|
|
56
|
+
|
|
57
|
+
edit = single_edit(action)
|
|
58
|
+
assert_equal "a || b", edit.new_text.strip
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_does_not_raise_on_empty_source
|
|
62
|
+
assert_silent { code_actions_for("", line: 0) }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -17,56 +17,7 @@ module RubyLsp
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# ===========================================================================
|
|
20
|
-
# 1.
|
|
21
|
-
# ===========================================================================
|
|
22
|
-
|
|
23
|
-
def test_extract_to_method_replaces_rhs_and_inserts_new_method
|
|
24
|
-
source = <<~RUBY
|
|
25
|
-
def process
|
|
26
|
-
result = expensive_computation
|
|
27
|
-
end
|
|
28
|
-
RUBY
|
|
29
|
-
|
|
30
|
-
actions = code_actions_for(source, line: 1)
|
|
31
|
-
action = find_action(actions, /Extract to method/)
|
|
32
|
-
refute_nil action
|
|
33
|
-
|
|
34
|
-
edits = all_edits(action)
|
|
35
|
-
assert_equal 2, edits.size
|
|
36
|
-
|
|
37
|
-
replace_edit = edits.find { |e| e.new_text.include?("result") && !e.new_text.include?("def") }
|
|
38
|
-
insert_edit = edits.find { |e| e.new_text.include?("def result") }
|
|
39
|
-
|
|
40
|
-
refute_nil replace_edit, "Expected an edit replacing the RHS with a method call"
|
|
41
|
-
refute_nil insert_edit, "Expected an edit inserting the new method definition"
|
|
42
|
-
|
|
43
|
-
assert_match(/def result/, insert_edit.new_text)
|
|
44
|
-
assert_match(/expensive_computation/, insert_edit.new_text)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def test_extract_to_method_passes_outer_variables_as_params
|
|
48
|
-
source = <<~RUBY
|
|
49
|
-
def process(data)
|
|
50
|
-
threshold = 10
|
|
51
|
-
result = data.select { |x| x > threshold }
|
|
52
|
-
end
|
|
53
|
-
RUBY
|
|
54
|
-
|
|
55
|
-
# Cursor on the `result =` line (line 2).
|
|
56
|
-
actions = code_actions_for(source, line: 2)
|
|
57
|
-
action = find_action(actions, /Extract to method/)
|
|
58
|
-
refute_nil action
|
|
59
|
-
|
|
60
|
-
edits = all_edits(action)
|
|
61
|
-
insert_edit = edits.find { |e| e.new_text.include?("def result") }
|
|
62
|
-
refute_nil insert_edit
|
|
63
|
-
|
|
64
|
-
# `threshold` was defined before the extraction point and is used in the RHS.
|
|
65
|
-
assert_match(/def result\(threshold\)/, insert_edit.new_text)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# ===========================================================================
|
|
69
|
-
# 2. Add parameter
|
|
20
|
+
# 1. Add parameter
|
|
70
21
|
# ===========================================================================
|
|
71
22
|
|
|
72
23
|
def test_add_parameter_appends_to_existing_params
|
|
@@ -106,7 +57,7 @@ module RubyLsp
|
|
|
106
57
|
end
|
|
107
58
|
|
|
108
59
|
# ===========================================================================
|
|
109
|
-
#
|
|
60
|
+
# 2. Convert to keyword arguments
|
|
110
61
|
# ===========================================================================
|
|
111
62
|
|
|
112
63
|
def test_converts_positional_params_to_kwargs
|
|
@@ -139,7 +90,7 @@ module RubyLsp
|
|
|
139
90
|
end
|
|
140
91
|
|
|
141
92
|
# ===========================================================================
|
|
142
|
-
#
|
|
93
|
+
# 3. Extract to let (RSpec)
|
|
143
94
|
# ===========================================================================
|
|
144
95
|
|
|
145
96
|
def test_extract_to_let_inserts_let_block_and_removes_assignment
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "ruby_lsp/test_helper"
|
|
5
|
+
|
|
6
|
+
module RubyLsp
|
|
7
|
+
module Refactor
|
|
8
|
+
class RaiseListenerTest < Minitest::Test
|
|
9
|
+
include RubyLsp::Refactor::TestHelper
|
|
10
|
+
|
|
11
|
+
def find_action(actions, title)
|
|
12
|
+
actions.find { |a| a.title == title }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def single_edit(action)
|
|
16
|
+
edits = action.edit.changes.values.flatten
|
|
17
|
+
assert_equal 1, edits.size
|
|
18
|
+
edits.first
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_simplifies_raise_runtime_error
|
|
22
|
+
source = "raise RuntimeError, \"something went wrong\"\n"
|
|
23
|
+
actions = code_actions_for(source, line: 0)
|
|
24
|
+
action = find_action(actions, "Simplify raise (remove redundant RuntimeError)")
|
|
25
|
+
refute_nil action
|
|
26
|
+
|
|
27
|
+
edit = single_edit(action)
|
|
28
|
+
assert_equal 'raise "something went wrong"', edit.new_text.strip
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_simplifies_fail_runtime_error
|
|
32
|
+
source = "fail RuntimeError, \"oops\"\n"
|
|
33
|
+
actions = code_actions_for(source, line: 0)
|
|
34
|
+
action = find_action(actions, "Simplify raise (remove redundant RuntimeError)")
|
|
35
|
+
refute_nil action
|
|
36
|
+
|
|
37
|
+
edit = single_edit(action)
|
|
38
|
+
assert_equal 'fail "oops"', edit.new_text.strip
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_does_not_offer_for_other_exception_classes
|
|
42
|
+
source = "raise ArgumentError, \"bad arg\"\n"
|
|
43
|
+
actions = code_actions_for(source, line: 0)
|
|
44
|
+
assert_nil find_action(actions, "Simplify raise (remove redundant RuntimeError)")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_does_not_offer_for_raise_with_string_only
|
|
48
|
+
source = "raise \"already simple\"\n"
|
|
49
|
+
actions = code_actions_for(source, line: 0)
|
|
50
|
+
assert_nil find_action(actions, "Simplify raise (remove redundant RuntimeError)")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_does_not_offer_for_runtime_error_new
|
|
54
|
+
source = "raise RuntimeError.new(\"msg\")\n"
|
|
55
|
+
actions = code_actions_for(source, line: 0)
|
|
56
|
+
assert_nil find_action(actions, "Simplify raise (remove redundant RuntimeError)")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_does_not_raise_on_empty_source
|
|
60
|
+
assert_silent { code_actions_for("", line: 0) }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "ruby_lsp/test_helper"
|
|
5
|
+
|
|
6
|
+
module RubyLsp
|
|
7
|
+
module Refactor
|
|
8
|
+
class RescueListenerTest < Minitest::Test
|
|
9
|
+
include RubyLsp::Refactor::TestHelper
|
|
10
|
+
|
|
11
|
+
def find_action(actions, title)
|
|
12
|
+
actions.find { |a| a.title == title }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def single_edit(action)
|
|
16
|
+
edits = action.edit.changes.values.flatten
|
|
17
|
+
assert_equal 1, edits.size
|
|
18
|
+
edits.first
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_wraps_method_body_in_rescue
|
|
22
|
+
source = <<~RUBY
|
|
23
|
+
def call
|
|
24
|
+
do_thing
|
|
25
|
+
end
|
|
26
|
+
RUBY
|
|
27
|
+
|
|
28
|
+
actions = code_actions_for(source, line: 0)
|
|
29
|
+
action = find_action(actions, "Wrap body in rescue")
|
|
30
|
+
refute_nil action
|
|
31
|
+
|
|
32
|
+
edit = single_edit(action)
|
|
33
|
+
assert_match(/rescue StandardError => e/, edit.new_text)
|
|
34
|
+
assert_match(/do_thing/, edit.new_text)
|
|
35
|
+
assert_match(/raise/, edit.new_text)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_preserves_all_body_statements
|
|
39
|
+
source = <<~RUBY
|
|
40
|
+
def process
|
|
41
|
+
step_one
|
|
42
|
+
step_two
|
|
43
|
+
step_three
|
|
44
|
+
end
|
|
45
|
+
RUBY
|
|
46
|
+
|
|
47
|
+
actions = code_actions_for(source, line: 0)
|
|
48
|
+
action = find_action(actions, "Wrap body in rescue")
|
|
49
|
+
refute_nil action
|
|
50
|
+
|
|
51
|
+
edit = single_edit(action)
|
|
52
|
+
assert_match(/step_one/, edit.new_text)
|
|
53
|
+
assert_match(/step_two/, edit.new_text)
|
|
54
|
+
assert_match(/step_three/, edit.new_text)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_does_not_offer_on_empty_method
|
|
58
|
+
source = <<~RUBY
|
|
59
|
+
def noop
|
|
60
|
+
end
|
|
61
|
+
RUBY
|
|
62
|
+
|
|
63
|
+
actions = code_actions_for(source, line: 0)
|
|
64
|
+
assert_nil find_action(actions, "Wrap body in rescue")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_does_not_raise_on_empty_source
|
|
68
|
+
assert_silent { code_actions_for("", line: 0) }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "ruby_lsp/test_helper"
|
|
5
|
+
|
|
6
|
+
module RubyLsp
|
|
7
|
+
module Refactor
|
|
8
|
+
class RspecLetListenerTest < Minitest::Test
|
|
9
|
+
include RubyLsp::Refactor::TestHelper
|
|
10
|
+
|
|
11
|
+
def find_action(actions, title)
|
|
12
|
+
actions.find { |a| a.title == title }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def single_edit(action)
|
|
16
|
+
edits = action.edit.changes.values.flatten
|
|
17
|
+
assert_equal 1, edits.size
|
|
18
|
+
edits.first
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_converts_let_to_let_bang
|
|
22
|
+
source = "let(:user) { User.new }\n"
|
|
23
|
+
actions = code_actions_for(source, line: 0)
|
|
24
|
+
action = find_action(actions, "Convert let to let!")
|
|
25
|
+
refute_nil action
|
|
26
|
+
|
|
27
|
+
edit = single_edit(action)
|
|
28
|
+
assert_match(/let!\(:user\)/, edit.new_text)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_converts_let_bang_to_let
|
|
32
|
+
source = "let!(:user) { User.new }\n"
|
|
33
|
+
actions = code_actions_for(source, line: 0)
|
|
34
|
+
action = find_action(actions, "Convert let! to let")
|
|
35
|
+
refute_nil action
|
|
36
|
+
|
|
37
|
+
edit = single_edit(action)
|
|
38
|
+
assert_match(/\blet\(:user\)/, edit.new_text)
|
|
39
|
+
refute_match(/let!/, edit.new_text)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_does_not_offer_for_non_let_calls
|
|
43
|
+
source = "subject { User.new }\n"
|
|
44
|
+
actions = code_actions_for(source, line: 0)
|
|
45
|
+
assert_nil find_action(actions, "Convert let to let!")
|
|
46
|
+
assert_nil find_action(actions, "Convert let! to let")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_does_not_raise_on_empty_source
|
|
50
|
+
assert_silent { code_actions_for("", line: 0) }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "ruby_lsp/test_helper"
|
|
5
|
+
|
|
6
|
+
module RubyLsp
|
|
7
|
+
module Refactor
|
|
8
|
+
class StringArrayListenerTest < Minitest::Test
|
|
9
|
+
include RubyLsp::Refactor::TestHelper
|
|
10
|
+
|
|
11
|
+
def find_action(actions, title)
|
|
12
|
+
actions.find { |a| a.title == title }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def single_edit(action)
|
|
16
|
+
edits = action.edit.changes.values.flatten
|
|
17
|
+
assert_equal 1, edits.size
|
|
18
|
+
edits.first
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_converts_bracket_string_array_to_percent_w
|
|
22
|
+
source = "[\"foo\", \"bar\", \"baz\"]\n"
|
|
23
|
+
actions = code_actions_for(source, line: 0)
|
|
24
|
+
action = find_action(actions, "Convert to string array (%w[])")
|
|
25
|
+
refute_nil action
|
|
26
|
+
|
|
27
|
+
edit = single_edit(action)
|
|
28
|
+
assert_equal "%w[foo bar baz]", edit.new_text
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_converts_percent_w_to_bracket_array
|
|
32
|
+
source = "%w[foo bar baz]\n"
|
|
33
|
+
actions = code_actions_for(source, line: 0)
|
|
34
|
+
action = find_action(actions, "Convert to bracket array")
|
|
35
|
+
refute_nil action
|
|
36
|
+
|
|
37
|
+
edit = single_edit(action)
|
|
38
|
+
assert_equal '["foo", "bar", "baz"]', edit.new_text
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_does_not_offer_percent_w_for_strings_with_spaces
|
|
42
|
+
source = "[\"hello world\", \"foo\"]\n"
|
|
43
|
+
actions = code_actions_for(source, line: 0)
|
|
44
|
+
assert_nil find_action(actions, "Convert to string array (%w[])")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_does_not_offer_percent_w_for_mixed_array
|
|
48
|
+
source = "[\"foo\", 42]\n"
|
|
49
|
+
actions = code_actions_for(source, line: 0)
|
|
50
|
+
assert_nil find_action(actions, "Convert to string array (%w[])")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_does_not_offer_percent_w_for_empty_array
|
|
54
|
+
source = "[]\n"
|
|
55
|
+
actions = code_actions_for(source, line: 0)
|
|
56
|
+
assert_nil find_action(actions, "Convert to string array (%w[])")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_does_not_raise_on_empty_source
|
|
60
|
+
assert_silent { code_actions_for("", line: 0) }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|