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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +68 -0
  3. data/README.md +553 -115
  4. data/lib/ruby/lsp/refactor/version.rb +1 -1
  5. data/lib/ruby_lsp/ruby_lsp_refactor/addon.rb +54 -17
  6. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/accessor_listener.rb +156 -0
  7. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/array_listener.rb +2 -2
  8. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/conditional_listener.rb +14 -14
  9. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/constant_listener.rb +118 -0
  10. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/early_return_listener.rb +105 -0
  11. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/enumerable_listener.rb +90 -0
  12. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/extract_include_file_listener.rb +172 -0
  13. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/extract_predicate_listener.rb +138 -0
  14. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/hash_listener.rb +7 -7
  15. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/logical_operator_listener.rb +70 -0
  16. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/method_listener.rb +37 -109
  17. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/raise_listener.rb +70 -0
  18. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/rescue_listener.rb +85 -0
  19. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/rspec_let_listener.rb +61 -0
  20. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/string_array_listener.rb +88 -0
  21. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/string_freeze_listener.rb +86 -0
  22. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/string_listener.rb +2 -2
  23. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/super_listener.rb +95 -0
  24. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/tap_listener.rb +133 -0
  25. data/lib/ruby_lsp/ruby_lsp_refactor/listeners/variable_listener.rb +7 -49
  26. data/lib/ruby_lsp/ruby_lsp_refactor/support/node_helpers.rb +62 -12
  27. data/lib/ruby_lsp/test_helper.rb +5 -5
  28. data/test/ruby_lsp_refactor/accessor_listener_test.rb +91 -0
  29. data/test/ruby_lsp_refactor/constant_listener_test.rb +68 -0
  30. data/test/ruby_lsp_refactor/early_return_listener_test.rb +156 -0
  31. data/test/ruby_lsp_refactor/enumerable_listener_test.rb +80 -0
  32. data/test/ruby_lsp_refactor/extract_include_file_listener_test.rb +189 -0
  33. data/test/ruby_lsp_refactor/extract_predicate_listener_test.rb +113 -0
  34. data/test/ruby_lsp_refactor/logical_operator_listener_test.rb +66 -0
  35. data/test/ruby_lsp_refactor/method_listener_test.rb +3 -52
  36. data/test/ruby_lsp_refactor/raise_listener_test.rb +64 -0
  37. data/test/ruby_lsp_refactor/rescue_listener_test.rb +72 -0
  38. data/test/ruby_lsp_refactor/rspec_let_listener_test.rb +54 -0
  39. data/test/ruby_lsp_refactor/string_array_listener_test.rb +64 -0
  40. data/test/ruby_lsp_refactor/string_freeze_listener_test.rb +52 -0
  41. data/test/ruby_lsp_refactor/string_listener_test.rb +2 -2
  42. data/test/ruby_lsp_refactor/super_listener_test.rb +65 -0
  43. data/test/ruby_lsp_refactor/tap_listener_test.rb +144 -0
  44. data/test/ruby_lsp_refactor/variable_listener_test.rb +0 -23
  45. 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. Extract to method
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
- # 3. Convert to keyword arguments
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
- # 4. Extract to let (RSpec)
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