ruby-lsp 0.7.6 → 0.8.1

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +41 -33
  4. data/exe/ruby-lsp-check +2 -2
  5. data/lib/core_ext/uri.rb +40 -0
  6. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +91 -0
  7. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +122 -0
  8. data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +121 -0
  9. data/lib/ruby_indexer/ruby_indexer.rb +19 -0
  10. data/lib/ruby_indexer/test/classes_and_modules_test.rb +204 -0
  11. data/lib/ruby_indexer/test/configuration_test.rb +35 -0
  12. data/lib/ruby_indexer/test/constant_test.rb +108 -0
  13. data/lib/ruby_indexer/test/index_test.rb +94 -0
  14. data/lib/ruby_indexer/test/test_case.rb +42 -0
  15. data/lib/ruby_lsp/document.rb +3 -3
  16. data/lib/ruby_lsp/executor.rb +131 -24
  17. data/lib/ruby_lsp/extension.rb +24 -0
  18. data/lib/ruby_lsp/internal.rb +4 -0
  19. data/lib/ruby_lsp/listener.rb +15 -14
  20. data/lib/ruby_lsp/requests/code_actions.rb +3 -3
  21. data/lib/ruby_lsp/requests/code_lens.rb +10 -24
  22. data/lib/ruby_lsp/requests/definition.rb +55 -8
  23. data/lib/ruby_lsp/requests/diagnostics.rb +3 -2
  24. data/lib/ruby_lsp/requests/document_link.rb +4 -3
  25. data/lib/ruby_lsp/requests/formatting.rb +3 -2
  26. data/lib/ruby_lsp/requests/hover.rb +4 -18
  27. data/lib/ruby_lsp/requests/on_type_formatting.rb +4 -6
  28. data/lib/ruby_lsp/requests/support/dependency_detector.rb +5 -0
  29. data/lib/ruby_lsp/requests/support/formatter_runner.rb +1 -1
  30. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +2 -2
  31. data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +2 -3
  32. data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +2 -3
  33. data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +3 -2
  34. data/lib/ruby_lsp/server.rb +10 -2
  35. data/lib/ruby_lsp/setup_bundler.rb +28 -14
  36. data/lib/ruby_lsp/store.rb +20 -13
  37. data/lib/ruby_lsp/utils.rb +1 -1
  38. metadata +27 -3
@@ -0,0 +1,204 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "test_case"
5
+
6
+ module RubyIndexer
7
+ class ClassesAndModulesTest < TestCase
8
+ def test_empty_statements_class
9
+ index(<<~RUBY)
10
+ class Foo
11
+ end
12
+ RUBY
13
+
14
+ assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-2")
15
+ end
16
+
17
+ def test_class_with_statements
18
+ index(<<~RUBY)
19
+ class Foo
20
+ def something; end
21
+ end
22
+ RUBY
23
+
24
+ assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:2-2")
25
+ end
26
+
27
+ def test_colon_colon_class
28
+ index(<<~RUBY)
29
+ class ::Foo
30
+ end
31
+ RUBY
32
+
33
+ assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-2")
34
+ end
35
+
36
+ def test_colon_colon_class_inside_class
37
+ index(<<~RUBY)
38
+ class Bar
39
+ class ::Foo
40
+ end
41
+ end
42
+ RUBY
43
+
44
+ assert_entry("Bar", Index::Entry::Class, "/fake/path/foo.rb:0-0:3-2")
45
+ assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:1-2:2-4")
46
+ end
47
+
48
+ def test_namespaced_class
49
+ index(<<~RUBY)
50
+ class Foo::Bar
51
+ end
52
+ RUBY
53
+
54
+ assert_entry("Foo::Bar", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-2")
55
+ end
56
+
57
+ def test_dynamically_namespaced_class
58
+ index(<<~RUBY)
59
+ class self::Bar
60
+ end
61
+ RUBY
62
+
63
+ refute_entry("self::Bar")
64
+ end
65
+
66
+ def test_empty_statements_module
67
+ index(<<~RUBY)
68
+ module Foo
69
+ end
70
+ RUBY
71
+
72
+ assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-2")
73
+ end
74
+
75
+ def test_module_with_statements
76
+ index(<<~RUBY)
77
+ module Foo
78
+ def something; end
79
+ end
80
+ RUBY
81
+
82
+ assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:2-2")
83
+ end
84
+
85
+ def test_colon_colon_module
86
+ index(<<~RUBY)
87
+ module ::Foo
88
+ end
89
+ RUBY
90
+
91
+ assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-2")
92
+ end
93
+
94
+ def test_namespaced_module
95
+ index(<<~RUBY)
96
+ module Foo::Bar
97
+ end
98
+ RUBY
99
+
100
+ assert_entry("Foo::Bar", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-2")
101
+ end
102
+
103
+ def test_dynamically_namespaced_module
104
+ index(<<~RUBY)
105
+ module self::Bar
106
+ end
107
+ RUBY
108
+
109
+ refute_entry("self::Bar")
110
+ end
111
+
112
+ def test_nested_modules_and_classes
113
+ index(<<~RUBY)
114
+ module Foo
115
+ class Bar
116
+ end
117
+
118
+ module Baz
119
+ class Qux
120
+ class Something
121
+ end
122
+ end
123
+ end
124
+ end
125
+ RUBY
126
+
127
+ assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:10-2")
128
+ assert_entry("Foo::Bar", Index::Entry::Class, "/fake/path/foo.rb:1-2:2-4")
129
+ assert_entry("Foo::Baz", Index::Entry::Module, "/fake/path/foo.rb:4-2:9-4")
130
+ assert_entry("Foo::Baz::Qux", Index::Entry::Class, "/fake/path/foo.rb:5-4:8-6")
131
+ assert_entry("Foo::Baz::Qux::Something", Index::Entry::Class, "/fake/path/foo.rb:6-6:7-8")
132
+ end
133
+
134
+ def test_deleting_from_index_based_on_file_path
135
+ index(<<~RUBY)
136
+ class Foo
137
+ end
138
+ RUBY
139
+
140
+ assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-2")
141
+
142
+ @index.delete("/fake/path/foo.rb")
143
+ refute_entry("Foo")
144
+ assert_empty(@index.instance_variable_get(:@files_to_entries))
145
+ end
146
+
147
+ def test_comments_can_be_attached_to_a_class
148
+ index(<<~RUBY)
149
+ # This is method comment
150
+ def foo; end
151
+ # This is a Foo comment
152
+ # This is another Foo comment
153
+ class Foo
154
+ # This should not be attached
155
+ end
156
+
157
+ # Ignore me
158
+
159
+ # This Bar comment has 1 line padding
160
+
161
+ class Bar; end
162
+ RUBY
163
+
164
+ foo_entry = @index["Foo"].first
165
+ assert_equal("# This is a Foo comment\n# This is another Foo comment\n", foo_entry.comments.join)
166
+
167
+ bar_entry = @index["Bar"].first
168
+ assert_equal("# This Bar comment has 1 line padding\n", bar_entry.comments.join)
169
+ end
170
+
171
+ def test_comments_can_be_attached_to_a_namespaced_class
172
+ index(<<~RUBY)
173
+ # This is a Foo comment
174
+ # This is another Foo comment
175
+ class Foo
176
+ # This is a Bar comment
177
+ class Bar; end
178
+ end
179
+ RUBY
180
+
181
+ foo_entry = @index["Foo"].first
182
+ assert_equal("# This is a Foo comment\n# This is another Foo comment\n", foo_entry.comments.join)
183
+
184
+ bar_entry = @index["Foo::Bar"].first
185
+ assert_equal("# This is a Bar comment\n", bar_entry.comments.join)
186
+ end
187
+
188
+ def test_comments_can_be_attached_to_a_reopened_class
189
+ index(<<~RUBY)
190
+ # This is a Foo comment
191
+ class Foo; end
192
+
193
+ # This is another Foo comment
194
+ class Foo; end
195
+ RUBY
196
+
197
+ first_foo_entry = @index["Foo"][0]
198
+ assert_equal("# This is a Foo comment\n", first_foo_entry.comments.join)
199
+
200
+ second_foo_entry = @index["Foo"][1]
201
+ assert_equal("# This is another Foo comment\n", second_foo_entry.comments.join)
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,35 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "test_helper"
5
+
6
+ module RubyIndexer
7
+ class ConfigurationTest < Minitest::Test
8
+ def setup
9
+ @config = Configuration.new
10
+ end
11
+
12
+ def test_load_configuration_executes_configure_block
13
+ @config.load_config
14
+ files_to_index = @config.files_to_index
15
+
16
+ assert(files_to_index.none? { |path| path.include?("test/fixtures") })
17
+ assert(files_to_index.none? { |path| path.include?("minitest-reporters") })
18
+ end
19
+
20
+ def test_paths_are_unique
21
+ @config.load_config
22
+ files_to_index = @config.files_to_index
23
+
24
+ assert_equal(files_to_index.uniq.length, files_to_index.length)
25
+ end
26
+
27
+ def test_configuration_raises_for_unknown_keys
28
+ Psych::Nodes::Document.any_instance.expects(:to_ruby).returns({ "unknown_config" => 123 })
29
+
30
+ assert_raises(ArgumentError) do
31
+ @config.load_config
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,108 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "test_case"
5
+
6
+ module RubyIndexer
7
+ class ConstantTest < TestCase
8
+ def test_constant_writes
9
+ index(<<~RUBY)
10
+ FOO = 1
11
+
12
+ class ::Bar
13
+ FOO = 2
14
+ end
15
+ RUBY
16
+
17
+ assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-6")
18
+ assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-8")
19
+ end
20
+
21
+ def test_constant_or_writes
22
+ index(<<~RUBY)
23
+ FOO ||= 1
24
+
25
+ class ::Bar
26
+ FOO ||= 2
27
+ end
28
+ RUBY
29
+
30
+ assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-8")
31
+ assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-10")
32
+ end
33
+
34
+ def test_constant_path_writes
35
+ index(<<~RUBY)
36
+ class A
37
+ FOO = 1
38
+ ::BAR = 1
39
+
40
+ module B
41
+ FOO = 1
42
+ end
43
+ end
44
+
45
+ A::BAZ = 1
46
+ RUBY
47
+
48
+ assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-8")
49
+ assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-10")
50
+ assert_entry("A::B::FOO", Index::Entry::Constant, "/fake/path/foo.rb:5-4:5-10")
51
+ assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:9-0:9-9")
52
+ end
53
+
54
+ def test_constant_path_or_writes
55
+ index(<<~RUBY)
56
+ class A
57
+ FOO ||= 1
58
+ ::BAR ||= 1
59
+ end
60
+
61
+ A::BAZ ||= 1
62
+ RUBY
63
+
64
+ assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-10")
65
+ assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-12")
66
+ assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:5-0:5-11")
67
+ end
68
+
69
+ def test_comments_for_constants
70
+ index(<<~RUBY)
71
+ # FOO comment
72
+ FOO = 1
73
+
74
+ class A
75
+ # A::FOO comment
76
+ FOO = 1
77
+
78
+ # ::BAR comment
79
+ ::BAR = 1
80
+ end
81
+
82
+ # A::BAZ comment
83
+ A::BAZ = 1
84
+ RUBY
85
+
86
+ foo_comment = @index["FOO"].first.comments.join("\n")
87
+ assert_equal("# FOO comment\n", foo_comment)
88
+
89
+ a_foo_comment = @index["A::FOO"].first.comments.join("\n")
90
+ assert_equal("# A::FOO comment\n", a_foo_comment)
91
+
92
+ bar_comment = @index["BAR"].first.comments.join("\n")
93
+ assert_equal("# ::BAR comment\n", bar_comment)
94
+
95
+ a_baz_comment = @index["A::BAZ"].first.comments.join("\n")
96
+ assert_equal("# A::BAZ comment\n", a_baz_comment)
97
+ end
98
+
99
+ def test_variable_path_constants_are_ignored
100
+ index(<<~RUBY)
101
+ var::FOO = 1
102
+ self.class::FOO = 1
103
+ RUBY
104
+
105
+ assert_no_entry
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,94 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "test_case"
5
+
6
+ module RubyIndexer
7
+ class IndexTest < TestCase
8
+ def test_deleting_one_entry_for_a_class
9
+ @index.index_single("/fake/path/foo.rb", <<~RUBY)
10
+ class Foo
11
+ end
12
+ RUBY
13
+ @index.index_single("/fake/path/other_foo.rb", <<~RUBY)
14
+ class Foo
15
+ end
16
+ RUBY
17
+
18
+ entries = @index["Foo"]
19
+ assert_equal(2, entries.length)
20
+
21
+ @index.delete("/fake/path/other_foo.rb")
22
+ entries = @index["Foo"]
23
+ assert_equal(1, entries.length)
24
+ end
25
+
26
+ def test_deleting_all_entries_for_a_class
27
+ @index.index_single("/fake/path/foo.rb", <<~RUBY)
28
+ class Foo
29
+ end
30
+ RUBY
31
+
32
+ entries = @index["Foo"]
33
+ assert_equal(1, entries.length)
34
+
35
+ @index.delete("/fake/path/foo.rb")
36
+ entries = @index["Foo"]
37
+ assert_nil(entries)
38
+ end
39
+
40
+ def test_index_resolve
41
+ @index.index_single("/fake/path/foo.rb", <<~RUBY)
42
+ class Bar; end
43
+
44
+ module Foo
45
+ class Bar
46
+ end
47
+
48
+ class Baz
49
+ class Something
50
+ end
51
+ end
52
+ end
53
+ RUBY
54
+
55
+ entries = @index.resolve("Something", ["Foo", "Baz"])
56
+ refute_empty(entries)
57
+ assert_equal("Foo::Baz::Something", entries.first.name)
58
+
59
+ entries = @index.resolve("Bar", ["Foo"])
60
+ refute_empty(entries)
61
+ assert_equal("Foo::Bar", entries.first.name)
62
+
63
+ entries = @index.resolve("Bar", ["Foo", "Baz"])
64
+ refute_empty(entries)
65
+ assert_equal("Foo::Bar", entries.first.name)
66
+
67
+ entries = @index.resolve("Foo::Bar", ["Foo", "Baz"])
68
+ refute_empty(entries)
69
+ assert_equal("Foo::Bar", entries.first.name)
70
+
71
+ assert_nil(@index.resolve("DoesNotExist", ["Foo"]))
72
+ end
73
+
74
+ def test_accessing_with_colon_colon_prefix
75
+ @index.index_single("/fake/path/foo.rb", <<~RUBY)
76
+ class Bar; end
77
+
78
+ module Foo
79
+ class Bar
80
+ end
81
+
82
+ class Baz
83
+ class Something
84
+ end
85
+ end
86
+ end
87
+ RUBY
88
+
89
+ entries = @index["::Foo::Baz::Something"]
90
+ refute_empty(entries)
91
+ assert_equal("Foo::Baz::Something", entries.first.name)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,42 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "test_helper"
5
+
6
+ module RubyIndexer
7
+ class TestCase < Minitest::Test
8
+ def setup
9
+ @index = Index.new
10
+ end
11
+
12
+ private
13
+
14
+ def index(source)
15
+ @index.index_single("/fake/path/foo.rb", source)
16
+ end
17
+
18
+ def assert_entry(expected_name, type, expected_location)
19
+ entries = @index[expected_name]
20
+ refute_empty(entries, "Expected #{expected_name} to be indexed")
21
+
22
+ entry = entries.first
23
+ assert_instance_of(type, entry, "Expected #{expected_name} to be a #{type}")
24
+
25
+ location = entry.location
26
+ location_string =
27
+ "#{entry.file_path}:#{location.start_line - 1}-#{location.start_column}" \
28
+ ":#{location.end_line - 1}-#{location.end_column}"
29
+
30
+ assert_equal(expected_location, location_string)
31
+ end
32
+
33
+ def refute_entry(expected_name)
34
+ entries = @index[expected_name]
35
+ assert_nil(entries, "Expected #{expected_name} to not be indexed")
36
+ end
37
+
38
+ def assert_no_entry
39
+ assert_empty(@index.instance_variable_get(:@entries), "Expected nothing to be indexed")
40
+ end
41
+ end
42
+ end
@@ -18,16 +18,16 @@ module RubyLsp
18
18
  sig { returns(Integer) }
19
19
  attr_reader :version
20
20
 
21
- sig { returns(String) }
21
+ sig { returns(URI::Generic) }
22
22
  attr_reader :uri
23
23
 
24
- sig { params(source: String, version: Integer, uri: String, encoding: String).void }
24
+ sig { params(source: String, version: Integer, uri: URI::Generic, encoding: String).void }
25
25
  def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind::UTF8)
26
26
  @cache = T.let({}, T::Hash[String, T.untyped])
27
27
  @encoding = T.let(encoding, String)
28
28
  @source = T.let(source, String)
29
29
  @version = T.let(version, Integer)
30
- @uri = T.let(uri, String)
30
+ @uri = T.let(uri, URI::Generic)
31
31
  @unparsed_edits = T.let([], T::Array[EditShape])
32
32
  @syntax_error = T.let(false, T::Boolean)
33
33
  @tree = T.let(SyntaxTree.parse(@source), T.nilable(SyntaxTree::Node))