rucoa 0.4.0 → 0.6.0

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +12 -2
  5. data/README.md +47 -17
  6. data/data/definitions_ruby_3_1 +0 -0
  7. data/images/diagnostics.gif +0 -0
  8. data/images/document-formatting.gif +0 -0
  9. data/images/document-symbol.gif +0 -0
  10. data/images/selection-ranges.gif +0 -0
  11. data/lib/rucoa/cli.rb +3 -2
  12. data/lib/rucoa/configuration.rb +111 -32
  13. data/lib/rucoa/definition_archiver.rb +29 -0
  14. data/lib/rucoa/definition_builders/rbs_constant_definition_builder.rb +44 -0
  15. data/lib/rucoa/definition_builders/rbs_method_definition_builder.rb +106 -0
  16. data/lib/rucoa/definition_builders/yard_method_definition_builder.rb +218 -0
  17. data/lib/rucoa/definition_builders.rb +9 -0
  18. data/lib/rucoa/definition_store.rb +63 -0
  19. data/lib/rucoa/definitions/base.rb +12 -0
  20. data/lib/rucoa/definitions/constant_definition.rb +51 -0
  21. data/lib/rucoa/definitions/method_definition.rb +126 -0
  22. data/lib/rucoa/definitions/method_parameter_definition.rb +30 -0
  23. data/lib/rucoa/definitions.rb +9 -0
  24. data/lib/rucoa/handlers/base.rb +8 -0
  25. data/lib/rucoa/handlers/exit_handler.rb +11 -0
  26. data/lib/rucoa/handlers/initialize_handler.rb +13 -0
  27. data/lib/rucoa/handlers/initialized_handler.rb +10 -0
  28. data/lib/rucoa/handlers/shutdown_handler.rb +12 -0
  29. data/lib/rucoa/handlers/text_document_completion_handler.rb +216 -0
  30. data/lib/rucoa/handlers/text_document_did_change_handler.rb +1 -20
  31. data/lib/rucoa/handlers/text_document_did_open_handler.rb +11 -4
  32. data/lib/rucoa/handlers/text_document_document_symbol_handler.rb +5 -4
  33. data/lib/rucoa/handlers/text_document_hover_handler.rb +76 -0
  34. data/lib/rucoa/handlers/text_document_range_formatting_handler.rb +1 -1
  35. data/lib/rucoa/handlers/text_document_selection_range_handler.rb +3 -3
  36. data/lib/rucoa/handlers/text_document_signature_help_handler.rb +68 -0
  37. data/lib/rucoa/handlers.rb +5 -0
  38. data/lib/rucoa/node_inspector.rb +111 -0
  39. data/lib/rucoa/nodes/base.rb +30 -0
  40. data/lib/rucoa/nodes/def_node.rb +59 -0
  41. data/lib/rucoa/nodes/lvar_node.rb +25 -0
  42. data/lib/rucoa/nodes/send_node.rb +43 -0
  43. data/lib/rucoa/nodes.rb +1 -0
  44. data/lib/rucoa/parser_builder.rb +1 -0
  45. data/lib/rucoa/position.rb +14 -3
  46. data/lib/rucoa/range.rb +64 -14
  47. data/lib/rucoa/rbs_document_loader.rb +43 -0
  48. data/lib/rucoa/rubocop_autocorrector.rb +1 -1
  49. data/lib/rucoa/rubocop_investigator.rb +1 -1
  50. data/lib/rucoa/server.rb +63 -13
  51. data/lib/rucoa/source.rb +59 -4
  52. data/lib/rucoa/source_store.rb +6 -10
  53. data/lib/rucoa/types/method_type.rb +23 -0
  54. data/lib/rucoa/types.rb +7 -0
  55. data/lib/rucoa/version.rb +1 -1
  56. data/lib/rucoa/yard_glob_document_loader.rb +47 -0
  57. data/lib/rucoa/yard_string_document_loader.rb +70 -0
  58. data/lib/rucoa.rb +9 -0
  59. data/rucoa.gemspec +2 -0
  60. metadata +58 -2
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yard'
4
+
5
+ module Rucoa
6
+ module DefinitionBuilders
7
+ class YardMethodDefinitionBuilder
8
+ class << self
9
+ # @param code_object [YARD::CodeObjects::MethodObject]
10
+ # @param path [String]
11
+ # @return [Rucoa::Definitions::Base]
12
+ def call(code_object:, path:)
13
+ new(
14
+ code_object: code_object,
15
+ path: path
16
+ ).call
17
+ end
18
+ end
19
+
20
+ # @param code_object [YARD::CodeObjects::Base]
21
+ # @param path [String]
22
+ def initialize(code_object:, path:)
23
+ @code_object = code_object
24
+ @path = path
25
+ end
26
+
27
+ # @return [Rucoa::Definitions::Base]
28
+ def call
29
+ ::Rucoa::Definitions::MethodDefinition.new(
30
+ description: description,
31
+ kind: kind,
32
+ method_name: method_name,
33
+ namespace: namespace,
34
+ source_path: @path,
35
+ types: types
36
+ )
37
+ end
38
+
39
+ private
40
+
41
+ # @return [String]
42
+ def description
43
+ @code_object.docstring.to_s
44
+ end
45
+
46
+ # @return [Symbol]
47
+ def kind
48
+ if @code_object.scope == :instance
49
+ :instance
50
+ else
51
+ :singleton
52
+ end
53
+ end
54
+
55
+ # @return [String
56
+ def method_name
57
+ @code_object.name
58
+ end
59
+
60
+ # @return [String]
61
+ def namespace
62
+ @code_object.namespace.to_s
63
+ end
64
+
65
+ # @return [Array<Rucoa::Types::MethodType>]
66
+ def types
67
+ return_types.map do |return_type|
68
+ ::Rucoa::Types::MethodType.new(
69
+ parameters_string: parameters_string,
70
+ return_type: return_type
71
+ )
72
+ end
73
+ end
74
+
75
+ # @return [String]
76
+ # @example
77
+ # definitions = Rucoa::YardStringDocumentLoader.call(
78
+ # content: <<~RUBY,
79
+ # class Foo
80
+ # def bar(
81
+ # argument1,
82
+ # argument2 = nil,
83
+ # *arguments,
84
+ # keyword1:,
85
+ # keyword2: nil,
86
+ # **keywords,
87
+ # &block
88
+ # )
89
+ # end
90
+ # end
91
+ # RUBY
92
+ # path: '/path/to/foo.rb'
93
+ # )
94
+ # expect(definitions.first.signatures).to eq(
95
+ # [
96
+ # 'Foo#bar(argument1, argument2 = nil, *arguments, keyword1:, keyword2: nil, **keywords, &block) -> Object'
97
+ # ]
98
+ # )
99
+ def parameters_string
100
+ @code_object.parameters.map do |parameter_name, default_value|
101
+ default_value_part =
102
+ if default_value.nil?
103
+ nil
104
+ elsif parameter_name.end_with?(':')
105
+ " #{default_value}"
106
+ else
107
+ " = #{default_value}"
108
+ end
109
+ [
110
+ parameter_name,
111
+ default_value_part
112
+ ].join
113
+ end.join(', ')
114
+ end
115
+
116
+ # @return [Array<Rucoa::Definitions::MethodParameterDefinition>]
117
+ def parameters
118
+ parameter_tags.map do |parameter_tag|
119
+ ::Rucoa::Definitions::MethodParameterDefinition.new(
120
+ name: parameter_tag.name,
121
+ types: parameter_tag.types
122
+ )
123
+ end
124
+ end
125
+
126
+ # @return [Array<YARD::Tags::Tag>]
127
+ def parameter_tags
128
+ @code_object.tags(:param) + @code_object.tags(:overload).flat_map do |overload_tag|
129
+ overload_tag.tags(:param)
130
+ end
131
+ end
132
+
133
+ # @return [Array<String>]
134
+ # @return [String]
135
+ # @example returns return type annotated by YARD @return tags
136
+ # definitions = Rucoa::YardStringDocumentLoader.call(
137
+ # content: <<~RUBY,
138
+ # # @return [String]
139
+ # def foo
140
+ # 'foo'
141
+ # end
142
+ # RUBY
143
+ # path: '/path/to/foo.rb'
144
+ # )
145
+ # expect(definitions.first.return_types).to eq(
146
+ # %w[
147
+ # String
148
+ # ]
149
+ # )
150
+ # @example ignores empty @return tags
151
+ # definitions = Rucoa::YardStringDocumentLoader.call(
152
+ # content: <<~RUBY,
153
+ # # @return []
154
+ # def foo
155
+ # 'foo'
156
+ # end
157
+ # RUBY
158
+ # path: '/path/to/foo.rb'
159
+ # )
160
+ # expect(definitions.first.return_types).to eq([])
161
+ def return_types
162
+ return %w[Object] if return_tags.empty?
163
+
164
+ return_tags.flat_map(&:types).compact.map do |type|
165
+ YardType.new(type).to_rucoa_type
166
+ end
167
+ end
168
+
169
+ # @return [YARD::Tags::Tag]
170
+ def return_tags
171
+ @code_object.tags(:return) + @code_object.tags(:overload).flat_map do |overload_tag|
172
+ overload_tag.tags(:return)
173
+ end
174
+ end
175
+
176
+ class YardType
177
+ # @param type [String]
178
+ def initialize(type)
179
+ @type = type
180
+ end
181
+
182
+ # @return [String]
183
+ # @example scrubs "Array<String>" to "Array"
184
+ # yard_type = Rucoa::DefinitionBuilders::YardMethodDefinitionBuilder::YardType.new(
185
+ # 'Array<String>'
186
+ # )
187
+ # expect(yard_type.to_rucoa_type).to eq('Array')
188
+ # @example scrubs "Array(String, Integer)" to "Array"
189
+ # yard_type = Rucoa::DefinitionBuilders::YardMethodDefinitionBuilder::YardType.new(
190
+ # 'Array(String, Integer)'
191
+ # )
192
+ # expect(yard_type.to_rucoa_type).to eq('Array')
193
+ # @example scrubs "::Array" to "Array"
194
+ # yard_type = Rucoa::DefinitionBuilders::YardMethodDefinitionBuilder::YardType.new(
195
+ # '::Array'
196
+ # )
197
+ # expect(yard_type.to_rucoa_type).to eq('Array')
198
+ # @example scrubs "Hash{Symbol => Object}" to "Hash"
199
+ # yard_type = Rucoa::DefinitionBuilders::YardMethodDefinitionBuilder::YardType.new(
200
+ # 'Hash{Symbol => Object}'
201
+ # )
202
+ # expect(yard_type.to_rucoa_type).to eq('Hash')
203
+ # @example scrubs "Array<Array<Integer>>" to "Array"
204
+ # yard_type = Rucoa::DefinitionBuilders::YardMethodDefinitionBuilder::YardType.new(
205
+ # 'Array<Array<Integer>>'
206
+ # )
207
+ # expect(yard_type.to_rucoa_type).to eq('Array')
208
+ def to_rucoa_type
209
+ @type
210
+ .delete_prefix('::')
211
+ .gsub(/<.+>/, '')
212
+ .gsub(/\{.+\}/, '')
213
+ .gsub(/\(.+\)/, '')
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module DefinitionBuilders
5
+ autoload :RbsConstantDefinitionBuilder, 'rucoa/definition_builders/rbs_constant_definition_builder'
6
+ autoload :RbsMethodDefinitionBuilder, 'rucoa/definition_builders/rbs_method_definition_builder'
7
+ autoload :YardMethodDefinitionBuilder, 'rucoa/definition_builders/yard_method_definition_builder'
8
+ end
9
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ class DefinitionStore
5
+ # @return [Array<Rucoa::Definition::Base>]
6
+ attr_accessor :definitions
7
+
8
+ def initialize
9
+ @definitions = []
10
+ end
11
+
12
+ # @param source_path [String]
13
+ # @return [Array<Rucoa::Definition::Base>]
14
+ def update_definitions_defined_in(source_path, definitions:)
15
+ delete_definitions_defined_in(source_path)
16
+ @definitions += definitions
17
+ end
18
+
19
+ # @param full_qualified_name [String]
20
+ # @return [Array<Rucoa::Definitions::Base>]
21
+ def select_by_full_qualified_name(full_qualified_name)
22
+ @definitions.select do |definition|
23
+ definition.full_qualified_name == full_qualified_name
24
+ end
25
+ end
26
+
27
+ # @param namespace [String]
28
+ # @return [Array<Rucoa::Definitions::MethodDefinition>]
29
+ def method_definitions_of(namespace)
30
+ method_definitions.select do |method_definition|
31
+ method_definition.namespace == namespace
32
+ end
33
+ end
34
+
35
+ # @param namespace [String]
36
+ # @return [Array<Rucoa::Definitions::ConstantDefinition>] e.g. File::Separator, File::SEPARATOR, etc.
37
+ def constant_definitions_under(namespace)
38
+ constant_definitions.select do |constant_definition|
39
+ constant_definition.namespace == namespace
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ # @return [Array<Rucoa::Definition::ConstantDefinition>]
46
+ def constant_definitions
47
+ @definitions.grep(Definitions::ConstantDefinition)
48
+ end
49
+
50
+ # @return [Array<Rucoa::Definition::MethodDefinition>]
51
+ def method_definitions
52
+ @definitions.grep(Definitions::MethodDefinition)
53
+ end
54
+
55
+ # @param source_path [String]
56
+ # @return [Array<Rucoa::Definition::Base>]
57
+ def delete_definitions_defined_in(source_path)
58
+ @definitions.delete_if do |definition|
59
+ definition.source_path == source_path
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Definitions
5
+ class Base
6
+ # @return [String]
7
+ def source_path
8
+ raise ::NotImplementedError
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Definitions
5
+ # Represents class definition, module definition, or constant assignment.
6
+ class ConstantDefinition < Base
7
+ # @return [String]
8
+ attr_reader :full_qualified_name
9
+
10
+ # @return [String]
11
+ attr_reader :source_path
12
+
13
+ # @param full_qualified_name [String]
14
+ # @param source_path [String]
15
+ def initialize(full_qualified_name:, source_path:)
16
+ super()
17
+ @full_qualified_name = full_qualified_name
18
+ @source_path = source_path
19
+ end
20
+
21
+ # @return [String]
22
+ # @example returns non-full-qualified name
23
+ # definition = Rucoa::Definitions::ConstantDefinition.new(
24
+ # full_qualified_name: 'Foo::Bar::Baz',
25
+ # source_path: '/path/to/foo/bar/baz.rb'
26
+ # )
27
+ # expect(definition.name).to eq('Baz')
28
+ def name
29
+ names.last
30
+ end
31
+
32
+ # @return [String]
33
+ # @example returns namespace
34
+ # definition = Rucoa::Definitions::ConstantDefinition.new(
35
+ # full_qualified_name: 'Foo::Bar::Baz',
36
+ # source_path: '/path/to/foo/bar/baz.rb'
37
+ # )
38
+ # expect(definition.namespace).to eq('Foo::Bar')
39
+ def namespace
40
+ names[..-2].join('::')
41
+ end
42
+
43
+ private
44
+
45
+ # @return [Array<String>]
46
+ def names
47
+ @names ||= full_qualified_name.split('::')
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Definitions
5
+ class MethodDefinition < Base
6
+ # @return [String, nil]
7
+ attr_reader :description
8
+
9
+ # @return [String]
10
+ attr_reader :method_name
11
+
12
+ # @return [String]
13
+ attr_reader :namespace
14
+
15
+ # @return [String]
16
+ attr_reader :source_path
17
+
18
+ # @param description [String, nil]
19
+ # @param kind [Symbol]
20
+ # @param method_name [String]
21
+ # @param namespace [String]
22
+ # @param source_path [String]
23
+ # @param types [Array<Rucoa::Types::MethodType>]
24
+ def initialize(
25
+ description:,
26
+ kind:,
27
+ method_name:,
28
+ namespace:,
29
+ source_path:,
30
+ types:
31
+ )
32
+ super()
33
+ @description = description
34
+ @kind = kind
35
+ @method_name = method_name
36
+ @namespace = namespace
37
+ @source_path = source_path
38
+ @types = types
39
+ end
40
+
41
+ # @return [String]
42
+ # @example returns qualified name of method
43
+ # method_definition = Rucoa::Definitions::MethodDefinition.new(
44
+ # description: nil,
45
+ # kind: :instance,
46
+ # method_name: 'foo',
47
+ # namespace: 'Foo::Bar',
48
+ # source_path: '/path/to/foo/bar.rb',
49
+ # types: []
50
+ # )
51
+ # expect(method_definition.full_qualified_name).to eq('Foo::Bar#foo')
52
+ def full_qualified_name
53
+ [
54
+ @namespace,
55
+ method_kind_symbol,
56
+ @method_name
57
+ ].join
58
+ end
59
+
60
+ # @todo
61
+ # @return [Array<Rucoa::Definitions::MethodParameter>]
62
+ def parameters
63
+ []
64
+ end
65
+
66
+ # @return [Array<String>]
67
+ # @example returns return types
68
+ # method_definition = Rucoa::Definitions::MethodDefinition.new(
69
+ # description: nil,
70
+ # kind: :instance,
71
+ # method_name: 'foo',
72
+ # namespace: 'Foo::Bar',
73
+ # source_path: '/path/to/foo/bar.rb',
74
+ # types: [
75
+ # Rucoa::Types::MethodType.new(
76
+ # parameters_string: '',
77
+ # return_type: 'String'
78
+ # )
79
+ # ]
80
+ # )
81
+ # expect(method_definition.return_types).to eq(%w[String])
82
+ def return_types
83
+ @types.map(&:return_type)
84
+ end
85
+
86
+ # @return [Array<String>]
87
+ # @example returns signature
88
+ # method_definition = Rucoa::Definitions::MethodDefinition.new(
89
+ # description: nil,
90
+ # kind: :instance,
91
+ # method_name: 'foo',
92
+ # namespace: 'Foo::Bar',
93
+ # source_path: '/path/to/foo/bar.rb',
94
+ # types: [
95
+ # Rucoa::Types::MethodType.new(
96
+ # parameters_string: '?::int base',
97
+ # return_type: 'String'
98
+ # )
99
+ # ]
100
+ # )
101
+ # expect(method_definition.signatures).to eq(['Foo::Bar#foo(?::int base) -> String'])
102
+ def signatures
103
+ @types.map do |type|
104
+ format(
105
+ '%<full_qualified_name>s(%<parameters>s) -> %<return_types>s',
106
+ full_qualified_name: full_qualified_name,
107
+ parameters: type.parameters_string,
108
+ return_types: type.return_type
109
+ )
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ # @return [String]
116
+ def method_kind_symbol
117
+ case @kind
118
+ when :instance
119
+ '#'
120
+ else
121
+ '.'
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Definitions
5
+ # Represents method parameter definition.
6
+ class MethodParameterDefinition < Base
7
+ # @return [String]
8
+ attr_reader :name
9
+
10
+ # @return [Array<String>]
11
+ attr_reader :types
12
+
13
+ # @param name [String]
14
+ # @param types [Array<String>]
15
+ def initialize(name:, types:)
16
+ super()
17
+ @name = name
18
+ @types = types
19
+ end
20
+
21
+ # @return [Hash{Symbol => Object}]
22
+ def to_hash
23
+ {
24
+ name: name,
25
+ types: types
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Definitions
5
+ autoload :Base, 'rucoa/definitions/base'
6
+ autoload :ConstantDefinition, 'rucoa/definitions/constant_definition'
7
+ autoload :MethodDefinition, 'rucoa/definitions/method_definition'
8
+ end
9
+ end
@@ -32,6 +32,9 @@ module Rucoa
32
32
  # @return [Hash]
33
33
  attr_reader :request
34
34
 
35
+ # @return [Rucoa::Server]
36
+ attr_reader :server
37
+
35
38
  # @param message [Hash]
36
39
  # @return [void]
37
40
  def respond(message)
@@ -46,6 +49,11 @@ module Rucoa
46
49
  @server.configuration
47
50
  end
48
51
 
52
+ # @return [Rucoa::DefinitionStore]
53
+ def definition_store
54
+ @server.definition_store
55
+ end
56
+
49
57
  # @return [Rucoa::SourceStore]
50
58
  def source_store
51
59
  @server.source_store
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class ExitHandler < Base
6
+ def call
7
+ server.finish
8
+ end
9
+ end
10
+ end
11
+ end
@@ -7,10 +7,23 @@ module Rucoa
7
7
  respond(
8
8
  capabilities: {
9
9
  codeActionProvider: true,
10
+ completionProvider: {
11
+ resolveProvider: true,
12
+ triggerCharacters: %w[
13
+ .
14
+ ]
15
+ },
10
16
  documentFormattingProvider: true,
11
17
  documentRangeFormattingProvider: true,
12
18
  documentSymbolProvider: true,
19
+ hoverProvider: true,
13
20
  selectionRangeProvider: true,
21
+ signatureHelpProvider: {
22
+ triggerCharacters: %w[
23
+ (
24
+ ,
25
+ ]
26
+ },
14
27
  textDocumentSync: {
15
28
  change: 1, # Full
16
29
  openClose: true
@@ -7,6 +7,16 @@ module Rucoa
7
7
 
8
8
  def call
9
9
  request_workspace_configuration
10
+ load_definitions
11
+ end
12
+
13
+ private
14
+
15
+ # @return [void]
16
+ def load_definitions
17
+ YardGlobDocumentLoader.call(
18
+ globs: ::YARD::Parser::SourceParser::DEFAULT_PATH_GLOB
19
+ )
10
20
  end
11
21
  end
12
22
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class ShutdownHandler < Base
6
+ def call
7
+ server.shutting_down = true
8
+ respond(nil)
9
+ end
10
+ end
11
+ end
12
+ end