ruby-lsp 0.7.6 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
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))