ruby-lsp-ree 0.1.14 → 0.1.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72573c4b291dbdfd4ed45bc81625bfc2dbc97839cdee93214e3af5358dfb8572
4
- data.tar.gz: 82849950d1fa5a0c7eb95f17b2417b97136df143bc871f2ce44e869e2a7c9721
3
+ metadata.gz: 4d1b2bf9c373c729b6412704fd6daf364ed4c8b278405f52b4c1fc32b256570c
4
+ data.tar.gz: 0bde5c4413ec6d4ccfd807dc6e6eb33d1e47ab018dfbfaad47200775b2c3badb
5
5
  SHA512:
6
- metadata.gz: 5aba139740ca71509e49885a4135cbc838fd1ce444b2525d15962126f558c09448caa1478a8df33669d223169e081901b87d8d12464ebd1555825735f8d95ec7
7
- data.tar.gz: 3e9673f5830791ce62a5dfbcb4e74cec6e3cd774fdfe7e447d9090cb4ae2063d881395bf39086e8158b697492f4e88eb68f35c55cd2b742fcba28d516075e853
6
+ metadata.gz: 6a97047d4924f208415ba6c5ed45e6abb57141e74fd44ad79368a9a7dd025a2ce0c36cfe2ca95db6fd0dab9586d9a1c5484be2e930ab129ab6acb7975fce310f
7
+ data.tar.gz: 17daba371512607775021704c56895bcc9e41666e598c9603c7da22ebac567de1cc7a2957f050a6657429057557dcbbc86654141eba98faa8c7ddde6e892a030
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## [0.1.16] - 2025-04-24
2
+
3
+ - formatter: add missing imports
4
+
5
+ ## [0.1.15] - 2025-04-18
6
+
7
+ - autocomplete - don't overwrite existing arguments
8
+ - unused links formatter - handle const aliases
9
+
1
10
  ## [0.1.14] - 2025-04-15
2
11
 
3
12
  - do not create error definition for defined class
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-lsp-ree (0.1.14)
4
+ ruby-lsp-ree (0.1.16)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -32,7 +32,7 @@ To switch off/on formatter features, use Ruby LSP addon settings:
32
32
  }
33
33
  }
34
34
  ```
35
- available formatters: `SortLinksFormatter`, `MissingErrorDefinitionsFormatter`, `MissingErrorContractsFormatter`, `MissingErrorLocalesFormatter`, `UnusedLinksFormatter`
35
+ available formatters: `SortLinksFormatter`, `MissingErrorDefinitionsFormatter`, `MissingErrorContractsFormatter`, `MissingErrorLocalesFormatter`, `UnusedLinksFormatter`, `MissingImportsFormatter`
36
36
 
37
37
  ## Functions
38
38
 
@@ -0,0 +1,183 @@
1
+ require_relative 'method_additional_text_edits_creator'
2
+ require_relative 'const_additional_text_edits_creator'
3
+
4
+ module RubyLsp
5
+ module Ree
6
+ class CompletionItemsMapper
7
+ include Requests::Support::Common
8
+ include RubyLsp::Ree::ReeLspUtils
9
+
10
+ def initialize(index)
11
+ @index = index
12
+ end
13
+
14
+ def map_ree_object_methods(ree_object_methods, location, node, description)
15
+ default_range = Interface::Range.new(
16
+ start: Interface::Position.new(line: location.start_line - 1, character: location.end_column + 1),
17
+ end: Interface::Position.new(line: location.start_line - 1, character: location.end_column + 1),
18
+ )
19
+
20
+ ree_object_methods.map do |object_method|
21
+ signature = object_method.signatures&.first
22
+
23
+ label_details = Interface::CompletionItemLabelDetails.new(
24
+ description: description,
25
+ detail: get_detail_string(signature)
26
+ )
27
+
28
+ if node.arguments && node.name.to_s == object_method.name
29
+ new_text = object_method.name
30
+ method_range = range_from_location(node.message_loc)
31
+ else
32
+ new_text = get_method_string(object_method.name, signature)
33
+ method_range = default_range
34
+ end
35
+
36
+ Interface::CompletionItem.new(
37
+ label: object_method.name,
38
+ label_details: label_details,
39
+ filter_text: object_method.name,
40
+ text_edit: Interface::TextEdit.new(
41
+ range: method_range,
42
+ new_text: new_text
43
+ ),
44
+ kind: Constant::CompletionItemKind::METHOD,
45
+ insert_text_format: Constant::InsertTextFormat::SNIPPET,
46
+ data: {
47
+ owner_name: "Object",
48
+ guessed_type: false,
49
+ }
50
+ )
51
+ end
52
+ end
53
+
54
+ def map_ree_objects(ree_objects, node, parsed_doc)
55
+ ree_objects.map do |ree_object|
56
+ ree_object_name = ree_object.name
57
+ package_name = package_name_from_uri(ree_object.uri)
58
+ signature = ree_object.signatures.first
59
+ ree_type = get_ree_type(ree_object)
60
+
61
+ label_details = Interface::CompletionItemLabelDetails.new(
62
+ description: "#{ree_type}, from: :#{package_name}",
63
+ detail: get_detail_string(signature)
64
+ )
65
+
66
+ if node.arguments && node.name.to_s == ree_object_name
67
+ new_text = ree_object_name
68
+ range = range_from_location(node.message_loc)
69
+ else
70
+ new_text = get_method_string(ree_object_name, signature)
71
+ range = range_from_location(node.location)
72
+ end
73
+
74
+ Interface::CompletionItem.new(
75
+ label: ree_object_name,
76
+ label_details: label_details,
77
+ filter_text: ree_object_name,
78
+ text_edit: Interface::TextEdit.new(
79
+ range: range,
80
+ new_text: new_text
81
+ ),
82
+ kind: Constant::CompletionItemKind::METHOD,
83
+ insert_text_format: Constant::InsertTextFormat::SNIPPET,
84
+ data: {
85
+ owner_name: "Object",
86
+ guessed_type: false,
87
+ },
88
+ additional_text_edits: MethodAdditionalTextEditsCreator.call(parsed_doc, ree_object_name, package_name)
89
+ )
90
+ end
91
+ end
92
+
93
+ def map_class_name_objects(class_name_objects, node, parsed_doc)
94
+ imported_consts = []
95
+ not_imported_consts = []
96
+
97
+ class_name_objects.each do |full_class_name|
98
+ entries = @index[full_class_name]
99
+
100
+ entries.each do |entry|
101
+ class_name = full_class_name.split('::').last
102
+ package_name = package_name_from_uri(entry.uri)
103
+ file_name = File.basename(entry.uri.to_s)
104
+ entry_comment = entry.comments && entry.comments.size > 0 ? " (#{entry.comments})" : ''
105
+
106
+ matched_import = parsed_doc.find_import_for_package(class_name, package_name)
107
+
108
+ if matched_import
109
+ label_details = Interface::CompletionItemLabelDetails.new(
110
+ description: "imported from: :#{package_name}",
111
+ detail: entry_comment
112
+ )
113
+
114
+ imported_consts << Interface::CompletionItem.new(
115
+ label: class_name,
116
+ label_details: label_details,
117
+ filter_text: class_name,
118
+ text_edit: Interface::TextEdit.new(
119
+ range: range_from_location(node.location),
120
+ new_text: class_name,
121
+ ),
122
+ kind: Constant::CompletionItemKind::CLASS,
123
+ additional_text_edits: []
124
+ )
125
+ else
126
+ label_details = Interface::CompletionItemLabelDetails.new(
127
+ description: "from: :#{package_name}",
128
+ detail: entry_comment + " #{file_name}"
129
+ )
130
+
131
+ not_imported_consts << Interface::CompletionItem.new(
132
+ label: class_name,
133
+ label_details: label_details,
134
+ filter_text: class_name,
135
+ text_edit: Interface::TextEdit.new(
136
+ range: range_from_location(node.location),
137
+ new_text: class_name,
138
+ ),
139
+ kind: Constant::CompletionItemKind::CLASS,
140
+ additional_text_edits: ConstAdditionalTextEditsCreator.call(parsed_doc, class_name, package_name, entry)
141
+ )
142
+ end
143
+ end
144
+ end
145
+
146
+ imported_consts + not_imported_consts
147
+ end
148
+
149
+ private
150
+
151
+ def get_detail_string(signature)
152
+ return '' unless signature
153
+
154
+ "(#{get_parameters_string(signature)})"
155
+ end
156
+
157
+ def get_parameters_string(signature)
158
+ return '' unless signature
159
+
160
+ signature.parameters.map(&:decorated_name).join(', ')
161
+ end
162
+
163
+ def get_method_string(fn_name, signature)
164
+ return fn_name unless signature
165
+
166
+ "#{fn_name}(#{get_parameters_placeholder(signature)})"
167
+ end
168
+
169
+ def get_parameters_placeholder(signature)
170
+ return '' unless signature
171
+
172
+ signature.parameters.to_enum.with_index.map do |signature_param, index|
173
+ case signature_param
174
+ when RubyIndexer::Entry::KeywordParameter, RubyIndexer::Entry::OptionalKeywordParameter
175
+ "#{signature_param.name}: ${#{index+1}:#{signature_param.name}}"
176
+ else
177
+ "${#{index+1}:#{signature_param.name}}"
178
+ end
179
+ end.join(', ')
180
+ end
181
+ end
182
+ end
183
+ end
@@ -33,7 +33,7 @@ module RubyLsp
33
33
  new_text = "\n" + link_text
34
34
 
35
35
  if @parsed_doc.has_blank_links_container?
36
- new_text = "\sdo#{link_text}\n\s\send\n"
36
+ new_text = "\sdo#{new_text}\n\s\send\n"
37
37
  end
38
38
 
39
39
  range = get_range_for_fn_insert(@parsed_doc, link_text)
@@ -3,6 +3,9 @@ module RubyLsp
3
3
  class BaseFormatter
4
4
  def self.call(source, uri, message_queue, index)
5
5
  new(message_queue, index).call(source, uri)
6
+ rescue => e
7
+ $stderr.puts("error in #{self}: #{e.message} : #{e.backtrace.first}")
8
+ source
6
9
  end
7
10
 
8
11
  def initialize(message_queue, index)
@@ -0,0 +1,63 @@
1
+ require_relative 'base_formatter'
2
+ require_relative "../ree_object_finder"
3
+
4
+ module RubyLsp
5
+ module Ree
6
+ class MissingImportsFormatter < BaseFormatter
7
+ include RubyLsp::Ree::ReeLspUtils
8
+
9
+ def call(source, uri)
10
+ return source unless @index
11
+
12
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_source(source)
13
+ return source if !parsed_doc || !parsed_doc.has_root_class?
14
+
15
+ finder = ReeObjectFinder.new(@index)
16
+ editor = RubyLsp::Ree::ReeSourceEditor.new(source)
17
+
18
+ current_package = package_name_from_uri(uri)
19
+
20
+ fn_calls = parsed_doc.parse_fn_calls
21
+ filtered_fn_calls = filter_fn_calls(parsed_doc, fn_calls)
22
+ objects_to_add = filtered_fn_calls.map{ |fn_call|
23
+ finder.find_object(fn_call.name.to_s)
24
+ }.compact
25
+
26
+ bean_calls = parsed_doc.parse_bean_calls
27
+ filtered_bean_calls = filter_bean_calls(parsed_doc, bean_calls)
28
+ objects_to_add += filtered_bean_calls.map{ |bean_call|
29
+ finder.find_object(bean_call.receiver_name.to_s)
30
+ }.compact
31
+
32
+ objects_to_add.uniq!{ |obj| obj.name }
33
+ objects_to_add.reject!{ |obj| parsed_doc.includes_linked_object?(obj.name) }
34
+ return editor.source if objects_to_add.size == 0
35
+
36
+ editor.add_links(parsed_doc, objects_to_add, current_package)
37
+ editor.source
38
+ end
39
+
40
+ private
41
+
42
+ def filter_fn_calls(parsed_doc, fn_calls)
43
+ parsed_doc.parse_instance_methods
44
+
45
+ fn_calls.reject{ |fn_call|
46
+ parsed_doc.doc_instance_methods.map(&:name).include?(fn_call.name)
47
+ }
48
+ end
49
+
50
+ def filter_bean_calls(parsed_doc, bean_calls)
51
+ bean_calls.select do |bean_call|
52
+ if !bean_call.method_name
53
+ true
54
+ else
55
+ method_obj = parsed_doc.doc_instance_methods.detect{ _1.name == bean_call.method_name }
56
+ local_variables = method_obj.parse_local_variables
57
+ !local_variables.map(&:name).include?(bean_call.receiver_name)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -7,38 +7,34 @@ module RubyLsp
7
7
  class UnusedLinksFormatter < BaseFormatter
8
8
  include RubyLsp::Ree::ReeLspUtils
9
9
 
10
+ attr_reader :editor, :dsl_parser
11
+
10
12
  def call(source, _uri)
11
13
  parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_source(source)
12
14
  return source if !parsed_doc
13
15
 
14
16
  parsed_doc.parse_links
15
17
 
16
- editor = RubyLsp::Ree::ReeSourceEditor.new(source)
17
- dsl_parser = RubyLsp::Ree::ReeDslParser.new(parsed_doc, @index)
18
+ @editor = RubyLsp::Ree::ReeSourceEditor.new(source)
19
+ @dsl_parser = RubyLsp::Ree::ReeDslParser.new(parsed_doc, @index)
18
20
 
19
21
  links_count = parsed_doc.link_nodes.size
20
22
 
21
23
  removed_links = 0
22
24
 
23
25
  parsed_doc.link_nodes.each do |link_node|
24
- removed_imports = 0
26
+ remove_imports = []
25
27
 
26
28
  if link_node.has_import_section?
27
- link_node.imports.each do |link_import|
28
- # TODO extract condition
29
- next if editor.contains_link_import_usage?(link_node, link_import) || dsl_parser.contains_object_usage?(link_import)
30
-
31
- editor.remove_link_import(link_node, link_import)
32
- removed_imports += 1
33
- end
29
+ remove_imports = link_node.imports.reject{ |imp| import_is_used?(link_node, imp) }
30
+ editor.remove_link_imports(link_node, remove_imports)
34
31
 
35
- if link_node.imports.size == removed_imports
32
+ if link_node.imports.size == remove_imports.size
36
33
  editor.remove_link_import_arg(link_node)
37
34
  end
38
35
  end
39
36
 
40
- # TODO extract condition
41
- next if editor.contains_link_usage?(link_node) || link_node.imports.size > removed_imports || dsl_parser.contains_object_usage?(link_node.name)
37
+ next if link_is_used?(link_node, remove_imports)
42
38
 
43
39
  editor.remove_link(link_node)
44
40
  removed_links += 1
@@ -51,6 +47,16 @@ module RubyLsp
51
47
 
52
48
  editor.source
53
49
  end
50
+
51
+ private
52
+
53
+ def import_is_used?(link_node, link_import)
54
+ editor.contains_link_import_usage?(link_node, link_import) || dsl_parser.contains_object_usage?(link_import)
55
+ end
56
+
57
+ def link_is_used?(link_node, remove_imports)
58
+ editor.contains_link_usage?(link_node) || link_node.imports.size > remove_imports.size || dsl_parser.contains_object_usage?(link_node.name)
59
+ end
54
60
  end
55
61
  end
56
62
  end
@@ -1,7 +1,6 @@
1
1
  require_relative "../utils/ree_lsp_utils"
2
2
  require_relative "../ree_object_finder"
3
- require_relative 'const_additional_text_edits_creator'
4
- require_relative 'method_additional_text_edits_creator'
3
+ require_relative '../completion/completion_items_mapper'
5
4
 
6
5
  module RubyLsp
7
6
  module Ree
@@ -18,6 +17,7 @@ module RubyLsp
18
17
  @node_context = node_context
19
18
  @root_node = @node_context.instance_variable_get(:@nesting_nodes).first
20
19
  @finder = ReeObjectFinder.new(@index)
20
+ @mapper = RubyLsp::Ree::CompletionItemsMapper.new(@index)
21
21
  end
22
22
 
23
23
  def get_ree_receiver(receiver_node)
@@ -31,115 +31,30 @@ module RubyLsp
31
31
 
32
32
  case @finder.object_type(ree_receiver)
33
33
  when :enum
34
- get_enum_values_completion_items(ree_receiver, location)
34
+ get_enum_values_completion_items(ree_receiver, location, node)
35
35
  when :bean, :async_bean
36
- get_bean_methods_completion_items(ree_receiver, location)
36
+ get_bean_methods_completion_items(ree_receiver, location, node)
37
37
  when :dao
38
- get_dao_filters_completion_items(ree_receiver, location)
38
+ get_dao_filters_completion_items(ree_receiver, location, node)
39
39
  else
40
40
  []
41
41
  end
42
42
  end
43
43
 
44
- def get_bean_methods_completion_items(bean_obj, location)
44
+ def get_bean_methods_completion_items(bean_obj, location, node)
45
45
  bean_node = RubyLsp::Ree::ParsedDocumentBuilder.build_from_uri(bean_obj.uri, :bean)
46
-
47
- range = Interface::Range.new(
48
- start: Interface::Position.new(line: location.start_line - 1, character: location.end_column + 1),
49
- end: Interface::Position.new(line: location.start_line - 1, character: location.end_column + 1),
50
- )
51
-
52
- bean_node.bean_methods.map do |bean_method|
53
- signature = bean_method.signatures.first
54
-
55
- label_details = Interface::CompletionItemLabelDetails.new(
56
- description: "method",
57
- detail: get_detail_string(signature)
58
- )
59
-
60
- Interface::CompletionItem.new(
61
- label: bean_method.name,
62
- label_details: label_details,
63
- filter_text: bean_method.name,
64
- text_edit: Interface::TextEdit.new(
65
- range: range,
66
- new_text: get_method_string(bean_method.name, signature)
67
- ),
68
- kind: Constant::CompletionItemKind::METHOD,
69
- insert_text_format: Constant::InsertTextFormat::SNIPPET,
70
- data: {
71
- owner_name: "Object",
72
- guessed_type: false,
73
- }
74
- )
75
- end
46
+ @mapper.map_ree_object_methods(bean_node.bean_methods, location, node, "method")
76
47
  end
77
48
 
78
- def get_dao_filters_completion_items(dao_obj, location)
49
+ def get_dao_filters_completion_items(dao_obj, location, node)
79
50
  dao_node = RubyLsp::Ree::ParsedDocumentBuilder.build_from_uri(dao_obj.uri, :dao)
80
-
81
- range = Interface::Range.new(
82
- start: Interface::Position.new(line: location.start_line - 1, character: location.end_column + 1),
83
- end: Interface::Position.new(line: location.start_line - 1, character: location.end_column + 1),
84
- )
85
-
86
- dao_node.filters.map do |filter|
87
- signature = filter.signatures.first
88
-
89
- label_details = Interface::CompletionItemLabelDetails.new(
90
- description: "filter",
91
- detail: get_detail_string(signature)
92
- )
93
-
94
- Interface::CompletionItem.new(
95
- label: filter.name,
96
- label_details: label_details,
97
- filter_text: filter.name,
98
- text_edit: Interface::TextEdit.new(
99
- range: range,
100
- new_text: get_method_string(filter.name, signature)
101
- ),
102
- kind: Constant::CompletionItemKind::METHOD,
103
- insert_text_format: Constant::InsertTextFormat::SNIPPET,
104
- data: {
105
- owner_name: "Object",
106
- guessed_type: false,
107
- }
108
- )
109
- end
51
+ @mapper.map_ree_object_methods(dao_node.filters, location, node, "filter")
110
52
  end
111
53
 
112
- def get_enum_values_completion_items(enum_obj, location)
54
+ def get_enum_values_completion_items(enum_obj, location, node)
113
55
  enum_node = RubyLsp::Ree::ParsedDocumentBuilder.build_from_uri(enum_obj.uri, :enum)
114
-
115
56
  class_name = enum_node.full_class_name
116
-
117
- label_details = Interface::CompletionItemLabelDetails.new(
118
- description: "from: #{class_name}",
119
- detail: ''
120
- )
121
-
122
- range = Interface::Range.new(
123
- start: Interface::Position.new(line: location.start_line - 1, character: location.end_column + 1),
124
- end: Interface::Position.new(line: location.start_line - 1, character: location.end_column + 1),
125
- )
126
-
127
- enum_node.values.map do |val|
128
- Interface::CompletionItem.new(
129
- label: val.name,
130
- label_details: label_details,
131
- filter_text: val.name,
132
- text_edit: Interface::TextEdit.new(
133
- range: range,
134
- new_text: val.name
135
- ),
136
- kind: Constant::CompletionItemKind::METHOD,
137
- data: {
138
- owner_name: "Object",
139
- guessed_type: false,
140
- }
141
- )
142
- end
57
+ @mapper.map_ree_object_methods(enum_node.values, location, node, "from: #{class_name}")
143
58
  end
144
59
 
145
60
  def get_class_name_completion_items(node)
@@ -150,59 +65,7 @@ module RubyLsp
150
65
 
151
66
  parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_ast(@root_node, @uri)
152
67
 
153
- imported_consts = []
154
- not_imported_consts = []
155
-
156
- class_name_objects.take(CANDIDATES_LIMIT).each do |full_class_name|
157
- entries = @index[full_class_name]
158
-
159
- entries.each do |entry|
160
- class_name = full_class_name.split('::').last
161
- package_name = package_name_from_uri(entry.uri)
162
- file_name = File.basename(entry.uri.to_s)
163
- entry_comment = entry.comments && entry.comments.size > 0 ? " (#{entry.comments})" : ''
164
-
165
- matched_import = parsed_doc.find_import_for_package(class_name, package_name)
166
-
167
- if matched_import
168
- label_details = Interface::CompletionItemLabelDetails.new(
169
- description: "imported from: :#{package_name}",
170
- detail: entry_comment
171
- )
172
-
173
- imported_consts << Interface::CompletionItem.new(
174
- label: class_name,
175
- label_details: label_details,
176
- filter_text: class_name,
177
- text_edit: Interface::TextEdit.new(
178
- range: range_from_location(node.location),
179
- new_text: class_name,
180
- ),
181
- kind: Constant::CompletionItemKind::CLASS,
182
- additional_text_edits: []
183
- )
184
- else
185
- label_details = Interface::CompletionItemLabelDetails.new(
186
- description: "from: :#{package_name}",
187
- detail: entry_comment + " #{file_name}"
188
- )
189
-
190
- not_imported_consts << Interface::CompletionItem.new(
191
- label: class_name,
192
- label_details: label_details,
193
- filter_text: class_name,
194
- text_edit: Interface::TextEdit.new(
195
- range: range_from_location(node.location),
196
- new_text: class_name,
197
- ),
198
- kind: Constant::CompletionItemKind::CLASS,
199
- additional_text_edits: ConstAdditionalTextEditsCreator.call(parsed_doc, class_name, package_name, entry)
200
- )
201
- end
202
- end
203
- end
204
-
205
- imported_consts + not_imported_consts
68
+ @mapper.map_class_name_objects(class_name_objects.take(CANDIDATES_LIMIT), node, parsed_doc)
206
69
  end
207
70
 
208
71
  def get_ree_objects_completions_items(node)
@@ -212,65 +75,7 @@ module RubyLsp
212
75
 
213
76
  parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_ast(@root_node, @uri)
214
77
 
215
- ree_objects.map do |ree_object|
216
- ree_object_name = ree_object.name
217
- package_name = package_name_from_uri(ree_object.uri)
218
- signature = ree_object.signatures.first
219
- ree_type = get_ree_type(ree_object)
220
-
221
- label_details = Interface::CompletionItemLabelDetails.new(
222
- description: "#{ree_type}, from: :#{package_name}",
223
- detail: get_detail_string(signature)
224
- )
225
-
226
- Interface::CompletionItem.new(
227
- label: ree_object_name,
228
- label_details: label_details,
229
- filter_text: ree_object_name,
230
- text_edit: Interface::TextEdit.new(
231
- range: range_from_location(node.location),
232
- new_text: get_method_string(ree_object_name, signature)
233
- ),
234
- kind: Constant::CompletionItemKind::METHOD,
235
- insert_text_format: Constant::InsertTextFormat::SNIPPET,
236
- data: {
237
- owner_name: "Object",
238
- guessed_type: false,
239
- },
240
- additional_text_edits: MethodAdditionalTextEditsCreator.call(parsed_doc, ree_object_name, package_name)
241
- )
242
- end
243
- end
244
-
245
- def get_detail_string(signature)
246
- return '' unless signature
247
-
248
- "(#{get_parameters_string(signature)})"
249
- end
250
-
251
- def get_parameters_string(signature)
252
- return '' unless signature
253
-
254
- signature.parameters.map(&:decorated_name).join(', ')
255
- end
256
-
257
- def get_method_string(fn_name, signature)
258
- return fn_name unless signature
259
-
260
- "#{fn_name}(#{get_parameters_placeholder(signature)})"
261
- end
262
-
263
- def get_parameters_placeholder(signature)
264
- return '' unless signature
265
-
266
- signature.parameters.to_enum.with_index.map do |signature_param, index|
267
- case signature_param
268
- when RubyIndexer::Entry::KeywordParameter, RubyIndexer::Entry::OptionalKeywordParameter
269
- "#{signature_param.name}: ${#{index+1}:#{signature_param.name}}"
270
- else
271
- "${#{index+1}:#{signature_param.name}}"
272
- end
273
- end.join(', ')
78
+ @mapper.map_ree_objects(ree_objects, node, parsed_doc)
274
79
  end
275
80
  end
276
81
  end
@@ -0,0 +1,104 @@
1
+ class RubyLsp::Ree::CallObjectsParser
2
+ attr_reader :parsed_doc
3
+
4
+ class CallObject
5
+ attr_reader :name, :type, :receiver_name, :method_name
6
+
7
+ def initialize(name:, type:, receiver_name: nil)
8
+ @name = name
9
+ @type = type
10
+ @receiver_name = receiver_name
11
+ @method_name = nil
12
+ end
13
+
14
+ def set_method_name(method_name)
15
+ @method_name = method_name
16
+ end
17
+ end
18
+
19
+ def initialize(parsed_doc)
20
+ @parsed_doc = parsed_doc
21
+ end
22
+
23
+ def class_call_objects
24
+ call_objects = []
25
+ return unless parsed_doc.has_body?
26
+
27
+ call_objects += parse_body_call_objects(parsed_doc.class_node.body.body)
28
+
29
+ parsed_doc.parse_instance_methods
30
+
31
+ parsed_doc.doc_instance_methods.each do |doc_instance_method|
32
+ call_objects += method_call_objects(doc_instance_method)
33
+ end
34
+
35
+ call_objects
36
+ end
37
+
38
+ def method_call_objects(method_object)
39
+ method_body = method_object.method_body
40
+ return [] unless method_body
41
+
42
+ call_nodes = parse_body_call_objects(method_body)
43
+ call_expressions = [] # don't parse call expressions for now parse_body_call_expressions(method_body)
44
+
45
+ call_objects = call_nodes + call_expressions
46
+
47
+ call_objects.each{ |call_object| call_object.set_method_name(method_object.name) }
48
+ call_objects
49
+ end
50
+
51
+ private
52
+
53
+ def parse_body_call_objects(node_body)
54
+ call_objects = []
55
+
56
+ node_body.each do |node|
57
+ if node.is_a?(Prism::CallNode)
58
+ receiver = get_first_receiver(node)
59
+ receiver_name = receiver.respond_to?(:name) ? receiver.name : nil
60
+ call_objects << CallObject.new(name: node.name, type: :method_call, receiver_name: receiver_name)
61
+ elsif node.respond_to?(:statements)
62
+ call_objects += parse_body_call_objects(node.statements.body)
63
+ elsif node.respond_to?(:block) && node.block && node.block.is_a?(Prism::BlockNode)
64
+ call_objects += parse_body_call_objects(get_method_body(node.block))
65
+ elsif node.respond_to?(:value) && node.value
66
+ call_objects += parse_body_call_objects([node.value])
67
+ end
68
+ end
69
+
70
+ call_objects
71
+ end
72
+
73
+ def parse_body_call_expressions(node_body)
74
+ call_expressions = []
75
+
76
+ node_body.each do |node|
77
+ if node.respond_to?(:block) && node.block && node.block.is_a?(Prism::BlockArgumentNode) && node.block.expression.is_a?(Prism::SymbolNode)
78
+ call_expressions << CallObject.new(name: node.block.expression.unescaped.to_sym, type: :proc_to_sym)
79
+ end
80
+ end
81
+
82
+ call_expressions
83
+ end
84
+
85
+ def get_method_body(node)
86
+ return unless node.body
87
+
88
+ if node.body.is_a?(Prism::BeginNode)
89
+ node.body.statements.body
90
+ else
91
+ node.body.body
92
+ end
93
+ end
94
+
95
+ def get_first_receiver(node)
96
+ return nil unless node.receiver
97
+
98
+ if node.receiver.is_a?(Prism::CallNode) && node.receiver.receiver
99
+ return get_first_receiver(node.receiver)
100
+ end
101
+
102
+ node.receiver
103
+ end
104
+ end
@@ -0,0 +1,52 @@
1
+ class RubyLsp::Ree::LocalVariablesParser
2
+ attr_reader :parsed_doc, :method_object
3
+
4
+ class LocalVariable
5
+ attr_reader :name
6
+
7
+ def initialize(name:)
8
+ @name = name
9
+ end
10
+ end
11
+
12
+ def initialize(method_obj)
13
+ @method_object = method_obj
14
+ end
15
+
16
+ def method_local_variables
17
+ method_body = method_object.method_body
18
+ return [] unless method_body
19
+
20
+ parse_body_local_variables(method_body)
21
+ end
22
+
23
+ private
24
+
25
+ def parse_body_local_variables(node_body)
26
+ local_variables = []
27
+
28
+ node_body.each do |node|
29
+ if node.is_a?(Prism::LocalVariableWriteNode)
30
+ local_variables << LocalVariable.new(name: node.name)
31
+ elsif node.is_a?(Prism::MultiWriteNode)
32
+ local_variables += node.lefts.map{ |x| LocalVariable.new(name: x.name) }
33
+ elsif node.respond_to?(:statements)
34
+ local_variables += parse_body_local_variables(node.statements.body)
35
+ elsif node.respond_to?(:block) && node.block && node.block.is_a?(Prism::BlockNode)
36
+ local_variables += parse_body_local_variables(get_method_body(node.block))
37
+ end
38
+ end
39
+
40
+ local_variables
41
+ end
42
+
43
+ def get_method_body(node)
44
+ return unless node.body
45
+
46
+ if node.body.is_a?(Prism::BeginNode)
47
+ node.body.statements.body
48
+ else
49
+ node.body.body
50
+ end
51
+ end
52
+ end
@@ -2,6 +2,8 @@ require_relative 'parsed_base_document'
2
2
  require_relative 'parsed_link_node'
3
3
  require_relative 'parsed_method_node'
4
4
  require_relative "../ree_constants"
5
+ require_relative "body_parsers/call_objects_parser"
6
+
5
7
  require 'ostruct'
6
8
 
7
9
  class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
@@ -142,6 +144,7 @@ class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
142
144
  end
143
145
 
144
146
  def parse_instance_methods
147
+ return if @doc_instance_methods
145
148
  @doc_instance_methods = []
146
149
 
147
150
  current_contract_node = nil
@@ -202,6 +205,14 @@ class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
202
205
  .map(&:name)
203
206
  end
204
207
 
208
+ def parse_fn_calls
209
+ RubyLsp::Ree::CallObjectsParser.new(self).class_call_objects.select{ !_1.receiver_name }
210
+ end
211
+
212
+ def parse_bean_calls
213
+ RubyLsp::Ree::CallObjectsParser.new(self).class_call_objects.select{ _1.receiver_name }
214
+ end
215
+
205
216
  def class_name
206
217
  class_node.constant_path.name.to_s
207
218
  end
@@ -1,11 +1,28 @@
1
1
  require 'prism'
2
2
 
3
3
  class RubyLsp::Ree::ParsedLinkNode
4
- attr_reader :node, :document_package, :name, :imports
4
+ attr_reader :node, :document_package, :name, :import_items
5
5
 
6
6
  FROM_ARG_KEY = 'from'
7
7
  IMPORT_ARG_KEY = 'import'
8
8
 
9
+ class ImportItem
10
+ attr_reader :name, :original_name
11
+
12
+ def initialize(name:, original_name: nil)
13
+ @name = name
14
+ @original_name = original_name
15
+ end
16
+
17
+ def to_s
18
+ if @original_name
19
+ "#{@original_name}.as(#{@name})"
20
+ else
21
+ @name
22
+ end
23
+ end
24
+ end
25
+
9
26
  def initialize(node, document_package = nil)
10
27
  @node = node
11
28
  @document_package = document_package
@@ -72,7 +89,11 @@ class RubyLsp::Ree::ParsedLinkNode
72
89
  end
73
90
 
74
91
  def parse_imports
75
- @imports ||= get_imports
92
+ @import_items ||= get_import_items
93
+ end
94
+
95
+ def imports
96
+ @import_items.map(&:name)
76
97
  end
77
98
 
78
99
  def has_import_section?
@@ -116,7 +137,7 @@ class RubyLsp::Ree::ParsedLinkNode
116
137
  end
117
138
  end
118
139
 
119
- def get_imports
140
+ def get_import_items
120
141
  return [] if @node.arguments.arguments.size == 1
121
142
 
122
143
  if object_name_type?
@@ -136,10 +157,17 @@ class RubyLsp::Ree::ParsedLinkNode
136
157
  end
137
158
 
138
159
  def parse_object_link_multiple_imports(import_body)
139
- if import_body.is_a?(Prism::CallNode)
140
- parse_object_link_multiple_imports(import_body.receiver) + [import_body.arguments.arguments.first.name.to_s]
160
+ if import_body.is_a?(Prism::CallNode) && import_body.name == :&
161
+ parse_object_link_multiple_imports(import_body.receiver) + parse_object_link_multiple_imports(import_body.arguments.arguments.first)
162
+ elsif import_body.is_a?(Prism::CallNode) && import_body.name == :as
163
+ [
164
+ ImportItem.new(
165
+ name: import_body.arguments.arguments.first.name.to_s,
166
+ original_name: import_body.receiver.name.to_s
167
+ )
168
+ ]
141
169
  else
142
- [import_body.name.to_s]
170
+ [ ImportItem.new(name: import_body.name.to_s) ]
143
171
  end
144
172
  end
145
173
  end
@@ -1,4 +1,5 @@
1
1
  require 'prism'
2
+ require_relative "body_parsers/local_variables_parser"
2
3
 
3
4
  class RubyLsp::Ree::ParsedMethodNode
4
5
  attr_reader :method_node, :contract_node, :nested_local_methods
@@ -24,6 +25,10 @@ class RubyLsp::Ree::ParsedMethodNode
24
25
  @method_node.location.end_line - 1
25
26
  end
26
27
 
28
+ def method_body
29
+ get_method_body(@method_node)
30
+ end
31
+
27
32
  def raised_errors_nested
28
33
  return @raised_errors_nested if @raised_errors_nested
29
34
  raised = raised_errors
@@ -94,6 +99,10 @@ class RubyLsp::Ree::ParsedMethodNode
94
99
  def contract_in_parentheses?
95
100
  @contract_node.opening == '(' && @contract_node.closing == ')'
96
101
  end
102
+
103
+ def parse_local_variables
104
+ RubyLsp::Ree::LocalVariablesParser.new(self).method_local_variables
105
+ end
97
106
 
98
107
  def parse_nested_local_methods(local_methods)
99
108
  unless @method_node.body
@@ -111,6 +120,20 @@ class RubyLsp::Ree::ParsedMethodNode
111
120
  @nested_local_methods.each{ _1.parse_nested_local_methods(local_methods) }
112
121
  end
113
122
 
123
+ def parse_call_objects
124
+ method_body = get_method_body(@method_node)
125
+ return [] unless method_body
126
+
127
+ call_nodes = parse_body_call_objects(method_body)
128
+ call_expressions = parse_body_call_expressions(method_body)
129
+
130
+ call_node_names = call_nodes.map(&:name) + call_expressions
131
+
132
+ call_node_names
133
+ end
134
+
135
+ private
136
+
114
137
  def parse_body_call_objects(node_body)
115
138
  call_nodes = []
116
139
 
@@ -121,6 +144,8 @@ class RubyLsp::Ree::ParsedMethodNode
121
144
  call_nodes += parse_body_call_objects(node.statements.body)
122
145
  elsif node.respond_to?(:block) && node.block && node.block.is_a?(Prism::BlockNode)
123
146
  call_nodes += parse_body_call_objects(get_method_body(node.block))
147
+ elsif node.respond_to?(:value) && node.value
148
+ call_nodes += parse_body_call_objects([node.value])
124
149
  end
125
150
  end
126
151
 
@@ -140,6 +165,8 @@ class RubyLsp::Ree::ParsedMethodNode
140
165
  end
141
166
 
142
167
  def get_method_body(node)
168
+ return unless node.body
169
+
143
170
  if node.body.is_a?(Prism::BeginNode)
144
171
  node.body.statements.body
145
172
  else
@@ -3,6 +3,7 @@ require_relative 'formatters/missing_error_definitions_formatter'
3
3
  require_relative 'formatters/missing_error_contracts_formatter'
4
4
  require_relative 'formatters/missing_error_locales_formatter'
5
5
  require_relative 'formatters/unused_links_formatter'
6
+ require_relative 'formatters/missing_imports_formatter'
6
7
 
7
8
  module RubyLsp
8
9
  module Ree
@@ -23,11 +24,12 @@ module RubyLsp
23
24
  source = document.source
24
25
 
25
26
  formatters = [
26
- RubyLsp::Ree::SortLinksFormatter,
27
27
  RubyLsp::Ree::MissingErrorDefinitionsFormatter,
28
28
  RubyLsp::Ree::MissingErrorContractsFormatter,
29
29
  RubyLsp::Ree::MissingErrorLocalesFormatter,
30
30
  RubyLsp::Ree::UnusedLinksFormatter,
31
+ RubyLsp::Ree::MissingImportsFormatter,
32
+ RubyLsp::Ree::SortLinksFormatter,
31
33
  ].select do |formatter|
32
34
  formatter_name = formatter.name.split('::').last.to_sym
33
35
  @settings[formatter_name] != false
@@ -27,8 +27,8 @@ module RubyLsp
27
27
  set_empty_lines!(link_node.location.start_line-1, link_node.location.end_line-1)
28
28
  end
29
29
 
30
- def remove_link_import(link_node, link_import)
31
- imports_str = link_node.imports.reject{ _1 == link_import}.join(' & ')
30
+ def remove_link_imports(link_node, link_imports)
31
+ imports_str = link_node.import_items.reject{ link_imports.include?(_1.name) }.map(&:to_s).join(' & ')
32
32
 
33
33
  block_start_col = link_node.import_block_open_location.start_column
34
34
  block_line = link_node.import_block_open_location.start_line-1
@@ -63,6 +63,36 @@ module RubyLsp
63
63
  source_lines[i] = ''
64
64
  end
65
65
  end
66
+
67
+ def add_links(parsed_doc, ree_objects, current_package)
68
+ new_text = ''
69
+
70
+ ree_objects.each do |ree_object|
71
+ object_package = package_name_from_uri(ree_object.uri)
72
+
73
+ link_text = if current_package == object_package
74
+ "\s\slink :#{ree_object.name}"
75
+ else
76
+ "\s\slink :#{ree_object.name}, from: :#{object_package}"
77
+ end
78
+
79
+ if parsed_doc.links_container_node
80
+ link_text = "\s\s" + link_text
81
+ end
82
+
83
+ new_text += "\n" + link_text
84
+ end
85
+
86
+ new_text += "\n"
87
+
88
+ if parsed_doc.has_blank_links_container?
89
+ new_text = "\sdo#{new_text}\s\send\n"
90
+ end
91
+
92
+ line = parsed_doc.links_container_node.location.start_line - 1
93
+
94
+ source_lines[line] = source_lines[line].chomp + new_text
95
+ end
66
96
  end
67
97
  end
68
98
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyLsp
4
4
  module Ree
5
- VERSION = "0.1.14"
5
+ VERSION = "0.1.16"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp-ree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.14
4
+ version: 0.1.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruslan Gatiyatov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-04-15 00:00:00.000000000 Z
11
+ date: 2025-04-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A Ruby LSP addon that adds extra editor functionality for Ree applications
14
14
  email:
@@ -26,20 +26,24 @@ files:
26
26
  - README.md
27
27
  - Rakefile
28
28
  - lib/ruby_lsp/ruby_lsp_ree/addon.rb
29
+ - lib/ruby_lsp/ruby_lsp_ree/completion/completion_items_mapper.rb
30
+ - lib/ruby_lsp/ruby_lsp_ree/completion/const_additional_text_edits_creator.rb
31
+ - lib/ruby_lsp/ruby_lsp_ree/completion/method_additional_text_edits_creator.rb
29
32
  - lib/ruby_lsp/ruby_lsp_ree/formatters/base_formatter.rb
30
33
  - lib/ruby_lsp/ruby_lsp_ree/formatters/missing_error_contracts_formatter.rb
31
34
  - lib/ruby_lsp/ruby_lsp_ree/formatters/missing_error_definitions_formatter.rb
32
35
  - lib/ruby_lsp/ruby_lsp_ree/formatters/missing_error_locales_formatter.rb
36
+ - lib/ruby_lsp/ruby_lsp_ree/formatters/missing_imports_formatter.rb
33
37
  - lib/ruby_lsp/ruby_lsp_ree/formatters/sort_links_formatter.rb
34
38
  - lib/ruby_lsp/ruby_lsp_ree/formatters/unused_links_formatter.rb
35
39
  - lib/ruby_lsp/ruby_lsp_ree/handlers/completion_handler.rb
36
- - lib/ruby_lsp/ruby_lsp_ree/handlers/const_additional_text_edits_creator.rb
37
40
  - lib/ruby_lsp/ruby_lsp_ree/handlers/definition_handler.rb
38
41
  - lib/ruby_lsp/ruby_lsp_ree/handlers/hover_handler.rb
39
- - lib/ruby_lsp/ruby_lsp_ree/handlers/method_additional_text_edits_creator.rb
40
42
  - lib/ruby_lsp/ruby_lsp_ree/listeners/completion_listener.rb
41
43
  - lib/ruby_lsp/ruby_lsp_ree/listeners/definition_listener.rb
42
44
  - lib/ruby_lsp/ruby_lsp_ree/listeners/hover_listener.rb
45
+ - lib/ruby_lsp/ruby_lsp_ree/parsing/body_parsers/call_objects_parser.rb
46
+ - lib/ruby_lsp/ruby_lsp_ree/parsing/body_parsers/local_variables_parser.rb
43
47
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_base_document.rb
44
48
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_class_document.rb
45
49
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_document_builder.rb