ruby-lsp-ree 0.1.13 → 0.1.15

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: 830eb15946648bcdb6d03779678f3a4ed0d1f25081bf948d9951496b050a51b0
4
- data.tar.gz: 6fb752c0d447d1e5a3e4d680b24ff8295555af253875d95498430951d872e919
3
+ metadata.gz: 7f3673076ca0aee98f0f92d6db25a2939ae21a58464a70e1a1e534bfef3458d0
4
+ data.tar.gz: 2191d354bbe75911cc230ca20665e5882e15eabee5de30801db1960990d42c3b
5
5
  SHA512:
6
- metadata.gz: 9766f1c674d1a3d695db8f181931c72d76993c46133d733e92f996c3d016d66bcc2b158f29a4e231b6dc39d5754b0586b9714496e13cb0d365169a96841d6626
7
- data.tar.gz: b2883d1193a0039850fc343e2baf6aea0e374b08fbd1dc8c04e44c76f9c16a3fb4a755269b33e260e7a38dc7d0ff2f8d9fcb90b24d83bf0d1b242a82560bfdd6
6
+ metadata.gz: ec2b1c6e15509e7e8f18758ef3a6912c14dccbdc6348d353331894fc53efbd072b043e49b1ee933c2b552e11404a4f322f8336aa29f267f3a842724d67fc8d7a
7
+ data.tar.gz: c584181adbf4ed1a02dc2545c1b65cf4ada307ade2d46aee661a97319adf43d67e6d21bb47f7b05d73bc4a2b913053ca990cae0cf1722587c350be942151e385
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [0.1.15] - 2025-04-18
2
+
3
+ - autocomplete - don't overwrite existing arguments
4
+ - unused links formatter - handle const aliases
5
+
6
+ ## [0.1.14] - 2025-04-15
7
+
8
+ - do not create error definition for defined class
9
+ - added formatter config settings
10
+ - formatter: remove unused imports
11
+ - fixed adding `throws` section to contract without parentheses
12
+
1
13
  ## [0.1.13] - 2025-04-04
2
14
 
3
15
  - Go To Definition for action and serializers in routes
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-lsp-ree (0.1.13)
4
+ ruby-lsp-ree (0.1.15)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -10,6 +10,8 @@ Ree addon for Ruby LSP
10
10
  If everything was installed successfully, you should see Ree in the list of Ruby LSP addons.
11
11
  (In VS Code click `{}` brackets in the bottom right corner)
12
12
 
13
+ ## Formatter
14
+
13
15
  To use ree_formatter, add the following line into your `settings.json` file (e.g. `.vscode/settings.json`)
14
16
  ```json
15
17
  "rubyLsp.formatter": "ree_formatter"
@@ -20,6 +22,18 @@ To use diagnostics, add the following line into your `settings.json` file (e.g.
20
22
  "rubyLsp.linters": ["ree_formatter"]
21
23
  ```
22
24
 
25
+ To switch off/on formatter features, use Ruby LSP addon settings:
26
+ ```json
27
+ "rubyLsp.addonSettings": {
28
+ "Ree Addon": {
29
+ "formatter": {
30
+ "MissingErrorLocalesFormatter": false
31
+ }
32
+ }
33
+ }
34
+ ```
35
+ available formatters: `SortLinksFormatter`, `MissingErrorDefinitionsFormatter`, `MissingErrorContractsFormatter`, `MissingErrorLocalesFormatter`, `UnusedLinksFormatter`
36
+
23
37
  ## Functions
24
38
 
25
39
  - autocomplete for Ree objects
@@ -28,4 +42,5 @@ To use diagnostics, add the following line into your `settings.json` file (e.g.
28
42
  - sort links on document save or format (with ree_formatter enabled)
29
43
  - missing error locales detection
30
44
  - Go To Definition for Ree objects
31
- - hover information for Ree objects and error locales
45
+ - hover information for Ree objects and error locales
46
+ - Ree templates support
@@ -15,10 +15,17 @@ module RubyLsp
15
15
  class Addon < ::RubyLsp::Addon
16
16
  def activate(global_state, message_queue)
17
17
  @global_state = global_state
18
+ @settings = global_state.settings_for_addon(name) || {}
19
+
18
20
  @message_queue = message_queue
19
21
  @template_applicator = RubyLsp::Ree::ReeTemplateApplicator.new
22
+
23
+ global_state.register_formatter("ree_formatter", RubyLsp::Ree::ReeFormatter.new(
24
+ @message_queue,
25
+ @settings[:formatter],
26
+ @global_state.index
27
+ ))
20
28
 
21
- global_state.register_formatter("ree_formatter", RubyLsp::Ree::ReeFormatter.new(@message_queue))
22
29
  register_additional_file_watchers(global_state, message_queue)
23
30
  end
24
31
 
@@ -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
@@ -1,12 +1,16 @@
1
1
  module RubyLsp
2
2
  module Ree
3
3
  class BaseFormatter
4
- def self.call(source, uri, message_queue)
5
- new(message_queue).call(source, uri)
4
+ def self.call(source, uri, message_queue, index)
5
+ new(message_queue, index).call(source, uri)
6
+ rescue => e
7
+ $stderr.puts("error in #{self.class}: #{e.message} : #{e.backtrace.first}")
8
+ source
6
9
  end
7
10
 
8
- def initialize(message_queue)
11
+ def initialize(message_queue, index)
9
12
  @message_queue = message_queue
13
+ @index = index
10
14
  end
11
15
 
12
16
  def call(source, uri)
@@ -5,7 +5,7 @@ module RubyLsp
5
5
  class MissingErrorContractsFormatter < BaseFormatter
6
6
  def call(source, _uri)
7
7
  parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_source(source)
8
- return source if !parsed_doc || !parsed_doc.class_node
8
+ return source if !parsed_doc || !parsed_doc.has_root_class?
9
9
 
10
10
  parsed_doc.parse_error_definitions
11
11
  parsed_doc.parse_instance_methods
@@ -40,10 +40,19 @@ module RubyLsp
40
40
  source_lines[line] = source_lines[line][0..position] + ", #{missed_errors.join(', ')}\n"
41
41
  end
42
42
  else
43
- position = doc_instance_method.contract_node_end_position
44
- line = doc_instance_method.contract_node_end_line
43
+ if doc_instance_method.contract_in_parentheses?
44
+ position = doc_instance_method.contract_node_end_position
45
+ line = doc_instance_method.contract_node_end_line
45
46
 
46
- source_lines[line] = source_lines[line][0..position] + ".throws(#{missed_errors.join(', ')})\n"
47
+ source_lines[line] = source_lines[line][0..position] + ".throws(#{missed_errors.join(', ')})\n"
48
+ else
49
+ contract_end_position = doc_instance_method.contract_node_contract_end_position
50
+ start_line = doc_instance_method.contract_node_start_line
51
+ end_line = doc_instance_method.contract_node_end_line
52
+
53
+ source_lines[start_line] = source_lines[start_line][0..contract_end_position] + "(" + source_lines[start_line][contract_end_position+1..-1].lstrip
54
+ source_lines[end_line] = source_lines[end_line].chomp + ").throws(#{missed_errors.join(', ')})\n"
55
+ end
47
56
  end
48
57
 
49
58
  source_lines.join
@@ -7,13 +7,18 @@ module RubyLsp
7
7
 
8
8
  def call(source, _uri)
9
9
  parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_source(source)
10
- return source if !parsed_doc || !parsed_doc.class_node
10
+ return source if !parsed_doc || !parsed_doc.has_root_class?
11
11
 
12
12
  parsed_doc.parse_error_definitions
13
13
  parsed_doc.parse_instance_methods
14
14
  parsed_doc.parse_links
15
+ parsed_doc.parse_defined_classes
16
+ parsed_doc.parse_defined_consts
15
17
 
16
- existing_errors = parsed_doc.error_definition_names + parsed_doc.imported_constants
18
+ existing_error_classes = parsed_doc.error_definition_names +
19
+ parsed_doc.imported_constants +
20
+ parsed_doc.defined_classes +
21
+ parsed_doc.defined_consts
17
22
 
18
23
  missed_errors = []
19
24
  parsed_doc.doc_instance_methods.each do |doc_instance_method|
@@ -21,7 +26,7 @@ module RubyLsp
21
26
 
22
27
  raised_errors = doc_instance_method.raised_errors_nested
23
28
 
24
- missed_errors += raised_errors - existing_errors
29
+ missed_errors += raised_errors - existing_error_classes
25
30
  end
26
31
 
27
32
  missed_errors = missed_errors.uniq.reject{ Object.const_defined?(_1) }
@@ -10,6 +10,7 @@ module RubyLsp
10
10
 
11
11
  def call(source, uri)
12
12
  parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_source(source)
13
+ return source if !parsed_doc || !parsed_doc.has_root_class?
13
14
 
14
15
  locales_folder = package_locales_folder_path(get_uri_path(uri))
15
16
  return source if !locales_folder || !File.directory?(locales_folder)
@@ -0,0 +1,62 @@
1
+ require_relative 'base_formatter'
2
+ require_relative '../ree_source_editor'
3
+ require_relative '../ree_dsl_parser'
4
+
5
+ module RubyLsp
6
+ module Ree
7
+ class UnusedLinksFormatter < BaseFormatter
8
+ include RubyLsp::Ree::ReeLspUtils
9
+
10
+ attr_reader :editor, :dsl_parser
11
+
12
+ def call(source, _uri)
13
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_source(source)
14
+ return source if !parsed_doc
15
+
16
+ parsed_doc.parse_links
17
+
18
+ @editor = RubyLsp::Ree::ReeSourceEditor.new(source)
19
+ @dsl_parser = RubyLsp::Ree::ReeDslParser.new(parsed_doc, @index)
20
+
21
+ links_count = parsed_doc.link_nodes.size
22
+
23
+ removed_links = 0
24
+
25
+ parsed_doc.link_nodes.each do |link_node|
26
+ remove_imports = []
27
+
28
+ if link_node.has_import_section?
29
+ remove_imports = link_node.imports.reject{ |imp| import_is_used?(link_node, imp) }
30
+ editor.remove_link_imports(link_node, remove_imports)
31
+
32
+ if link_node.imports.size == remove_imports.size
33
+ editor.remove_link_import_arg(link_node)
34
+ end
35
+ end
36
+
37
+ next if link_is_used?(link_node, remove_imports)
38
+
39
+ editor.remove_link(link_node)
40
+ removed_links += 1
41
+ end
42
+
43
+ if removed_links == links_count
44
+ parsed_doc.parse_links_container_node
45
+ editor.remove_link_block(parsed_doc.links_container_node, parsed_doc.links_container_block_node)
46
+ end
47
+
48
+ editor.source
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
60
+ end
61
+ end
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
@@ -24,6 +24,10 @@ class RubyLsp::Ree::ParsedBaseDocument
24
24
  false
25
25
  end
26
26
 
27
+ def includes_ree_dsl?
28
+ false
29
+ end
30
+
27
31
  def includes_linked_object?(obj_name)
28
32
  @link_nodes.map(&:name).include?(obj_name)
29
33
  end
@@ -10,7 +10,8 @@ class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
10
10
 
11
11
  attr_reader :class_node, :class_includes,
12
12
  :values, :filters, :bean_methods, :links_container_block_node, :error_definitions,
13
- :error_definition_names, :doc_instance_methods, :links_container_node
13
+ :error_definition_names, :doc_instance_methods, :links_container_node,
14
+ :defined_classes, :defined_consts
14
15
 
15
16
  def initialize(ast, package_name = nil)
16
17
  super
@@ -37,6 +38,10 @@ class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
37
38
  @class_includes.any?{ node_name(_1) == ROUTES_DSL_MODULE }
38
39
  end
39
40
 
41
+ def includes_ree_dsl?
42
+ ree_dsls.size > 0
43
+ end
44
+
40
45
  def includes_linked_constant?(const_name)
41
46
  @link_nodes.map(&:imports).flatten.include?(const_name)
42
47
  end
@@ -106,7 +111,7 @@ class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
106
111
 
107
112
  @link_nodes = nodes.map do |link_node|
108
113
  link_node = RubyLsp::Ree::ParsedLinkNode.new(link_node, package_name)
109
- link_node.parse_imports
114
+ link_node.parse_imports # TODO move parse imports inside link_node constructor
110
115
  link_node
111
116
  end
112
117
  end
@@ -179,6 +184,24 @@ class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
179
184
  @error_definition_names = @error_definitions.map(&:name)
180
185
  end
181
186
 
187
+ def parse_defined_classes
188
+ @defined_classes = []
189
+ return unless has_body?
190
+
191
+ @defined_classes = class_node.body.body
192
+ .select{ _1.is_a?(Prism::ClassNode) }
193
+ .map(&:name)
194
+ end
195
+
196
+ def parse_defined_consts
197
+ @defined_consts = []
198
+ return unless has_body?
199
+
200
+ @defined_consts += class_node.body.body
201
+ .select{ _1.is_a?(Prism::ConstantWriteNode) }
202
+ .map(&:name)
203
+ end
204
+
182
205
  def class_name
183
206
  class_node.constant_path.name.to_s
184
207
  end
@@ -199,4 +222,8 @@ class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
199
222
  def imported_constants
200
223
  @link_nodes.map(&:imports).flatten.map(&:to_sym)
201
224
  end
225
+
226
+ def ree_dsls
227
+ @class_includes.select{ node_name(_1).downcase.match?(/ree/) && node_name(_1).downcase.match?(/dsl/)}
228
+ end
202
229
  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?
@@ -81,6 +102,27 @@ class RubyLsp::Ree::ParsedLinkNode
81
102
  !!import_arg
82
103
  end
83
104
 
105
+ def first_arg_location
106
+ @node.arguments.arguments.first.location
107
+ end
108
+
109
+ def import_block_open_location
110
+ if object_name_type?
111
+ import_arg.value.opening_loc
112
+ else
113
+ import_arg.opening_loc
114
+ end
115
+ end
116
+
117
+ def import_block_close_location
118
+ # TODO maybe use two classes for link types
119
+ if object_name_type?
120
+ import_arg.value.closing_loc
121
+ else
122
+ import_arg.closing_loc
123
+ end
124
+ end
125
+
84
126
  private
85
127
 
86
128
  def last_arg
@@ -95,14 +137,17 @@ class RubyLsp::Ree::ParsedLinkNode
95
137
  end
96
138
  end
97
139
 
98
- def get_imports
140
+ def get_import_items
99
141
  return [] if @node.arguments.arguments.size == 1
100
142
 
101
143
  if object_name_type?
102
144
  return [] unless import_arg
103
- [import_arg.value.body.body.first.name.to_s]
145
+ import_body = import_arg.value.body.body.first
146
+ parse_object_link_multiple_imports(import_body)
104
147
  elsif last_arg.is_a?(Prism::LambdaNode)
105
- [last_arg.body.body.first.name.to_s]
148
+ return [] unless last_arg.body
149
+ import_body = last_arg.body.body.first
150
+ parse_object_link_multiple_imports(import_body)
106
151
  else
107
152
  return []
108
153
  end
@@ -110,4 +155,19 @@ class RubyLsp::Ree::ParsedLinkNode
110
155
  $stderr.puts("can't parse imports: #{e.message}")
111
156
  return []
112
157
  end
158
+
159
+ def parse_object_link_multiple_imports(import_body)
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
+ ]
169
+ else
170
+ [ ImportItem.new(name: import_body.name.to_s) ]
171
+ end
172
+ end
113
173
  end
@@ -82,6 +82,18 @@ class RubyLsp::Ree::ParsedMethodNode
82
82
  def contract_node_end_line
83
83
  @contract_node.location.end_line - 1
84
84
  end
85
+
86
+ def contract_node_start_line
87
+ @contract_node.location.start_line - 1
88
+ end
89
+
90
+ def contract_node_contract_end_position
91
+ @contract_node.message_loc.end_column - 1
92
+ end
93
+
94
+ def contract_in_parentheses?
95
+ @contract_node.opening == '(' && @contract_node.closing == ')'
96
+ end
85
97
 
86
98
  def parse_nested_local_methods(local_methods)
87
99
  unless @method_node.body
@@ -91,9 +103,9 @@ class RubyLsp::Ree::ParsedMethodNode
91
103
 
92
104
  method_body = get_method_body(@method_node)
93
105
 
94
- local_method_names = local_methods.map(&:name)
95
106
  call_nodes = parse_body_call_objects(method_body)
96
- call_node_names = call_nodes.map(&:name)
107
+ call_expressions = parse_body_call_expressions(method_body)
108
+ call_node_names = call_nodes.map(&:name) + call_expressions
97
109
 
98
110
  @nested_local_methods = local_methods.select{ call_node_names.include?(_1.name) }
99
111
  @nested_local_methods.each{ _1.parse_nested_local_methods(local_methods) }
@@ -107,7 +119,7 @@ class RubyLsp::Ree::ParsedMethodNode
107
119
  call_nodes << node
108
120
  elsif node.respond_to?(:statements)
109
121
  call_nodes += parse_body_call_objects(node.statements.body)
110
- elsif node.respond_to?(:block) && node.block
122
+ elsif node.respond_to?(:block) && node.block && node.block.is_a?(Prism::BlockNode)
111
123
  call_nodes += parse_body_call_objects(get_method_body(node.block))
112
124
  end
113
125
  end
@@ -115,6 +127,18 @@ class RubyLsp::Ree::ParsedMethodNode
115
127
  call_nodes
116
128
  end
117
129
 
130
+ def parse_body_call_expressions(node_body)
131
+ call_expressions = []
132
+
133
+ node_body.each do |node|
134
+ if node.respond_to?(:block) && node.block && node.block.is_a?(Prism::BlockArgumentNode) && node.block.expression.is_a?(Prism::SymbolNode)
135
+ call_expressions << node.block.expression.unescaped.to_sym
136
+ end
137
+ end
138
+
139
+ call_expressions
140
+ end
141
+
118
142
  def get_method_body(node)
119
143
  if node.body.is_a?(Prism::BeginNode)
120
144
  node.body.statements.body
@@ -0,0 +1,39 @@
1
+ module RubyLsp
2
+ module Ree
3
+ class ReeDslParser
4
+ include RubyLsp::Ree::ReeLspUtils
5
+
6
+ attr_reader :parsed_doc
7
+
8
+ def initialize(parsed_doc, index)
9
+ @parsed_doc = parsed_doc
10
+ @index = index
11
+ end
12
+
13
+ def contains_object_usage?(obj_name)
14
+ return false unless @index
15
+ return false unless parsed_doc.includes_ree_dsl?
16
+
17
+ parsed_doc.ree_dsls.any? do |ree_dsl|
18
+ ree_dsl_contains_object_usage?(ree_dsl.name, obj_name)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def ree_dsl_contains_object_usage?(dsl_name, obj_name)
25
+ dsl_objects = @index[dsl_name]
26
+ return unless dsl_objects
27
+
28
+ uris = dsl_objects.map(&:uri)
29
+
30
+ uris.any?{ file_contains_object_usage?(_1, obj_name) }
31
+ end
32
+
33
+ def file_contains_object_usage?(file_uri, obj_name)
34
+ file_content = File.read(file_uri.path.to_s)
35
+ file_content.match?(/\W#{obj_name}\W/)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -2,6 +2,7 @@ require_relative 'formatters/sort_links_formatter'
2
2
  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
+ require_relative 'formatters/unused_links_formatter'
5
6
 
6
7
  module RubyLsp
7
8
  module Ree
@@ -12,8 +13,10 @@ module RubyLsp
12
13
 
13
14
  MISSING_LOCALE_PLACEHOLDER = '_MISSING_LOCALE_'
14
15
 
15
- def initialize(message_queue)
16
+ def initialize(message_queue, settings, index = nil)
16
17
  @message_queue = message_queue
18
+ @settings = settings || {}
19
+ @index = index
17
20
  end
18
21
 
19
22
  def run_formatting(uri, document)
@@ -23,10 +26,14 @@ module RubyLsp
23
26
  RubyLsp::Ree::SortLinksFormatter,
24
27
  RubyLsp::Ree::MissingErrorDefinitionsFormatter,
25
28
  RubyLsp::Ree::MissingErrorContractsFormatter,
26
- RubyLsp::Ree::MissingErrorLocalesFormatter
27
- ]
29
+ RubyLsp::Ree::MissingErrorLocalesFormatter,
30
+ RubyLsp::Ree::UnusedLinksFormatter,
31
+ ].select do |formatter|
32
+ formatter_name = formatter.name.split('::').last.to_sym
33
+ @settings[formatter_name] != false
34
+ end
28
35
 
29
- formatters.reduce(source){ |s, formatter| formatter.call(s, uri, @message_queue) }
36
+ formatters.reduce(source){ |s, formatter| formatter.call(s, uri, @message_queue, @index) }
30
37
  rescue => e
31
38
  $stderr.puts("error in ree_formatter: #{e.message} : #{e.backtrace.first}")
32
39
  end
@@ -0,0 +1,68 @@
1
+ module RubyLsp
2
+ module Ree
3
+ class ReeSourceEditor
4
+ include RubyLsp::Ree::ReeLspUtils
5
+
6
+ attr_reader :source_lines
7
+
8
+ def initialize(source)
9
+ @source_lines = source.lines
10
+ end
11
+
12
+ def source
13
+ @source_lines.join
14
+ end
15
+
16
+ def contains_link_usage?(link_node)
17
+ source_lines_except_link = source_lines[0...(link_node.location.start_line-1)] + source_lines[(link_node.location.end_line)..-1]
18
+ source_lines_except_link.any?{ |source_line| source_line.match?(/\W#{link_node.name}\W/)}
19
+ end
20
+
21
+ def contains_link_import_usage?(link_node, link_import)
22
+ source_lines_except_link = source_lines[0...(link_node.location.start_line-1)] + source_lines[(link_node.location.end_line)..-1]
23
+ source_lines_except_link.any?{ |source_line| source_line.match?(/\W#{link_import}\W/)}
24
+ end
25
+
26
+ def remove_link(link_node)
27
+ set_empty_lines!(link_node.location.start_line-1, link_node.location.end_line-1)
28
+ end
29
+
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
+
33
+ block_start_col = link_node.import_block_open_location.start_column
34
+ block_line = link_node.import_block_open_location.start_line-1
35
+ block_end_line = link_node.import_block_close_location.end_line-1
36
+
37
+ source_lines[block_line] = source_lines[block_line][0..block_start_col] + " #{imports_str} }\n"
38
+ set_empty_lines!(block_line+1, block_end_line)
39
+ end
40
+
41
+ def remove_link_import_arg(link_node)
42
+ link_line = link_node.location.start_line - 1
43
+ link_end_line = link_node.location.end_line - 1
44
+ link_name_end = link_node.first_arg_location.end_column - 1
45
+
46
+ source_lines[link_line] = source_lines[link_line][0..link_name_end] + "\n"
47
+ set_empty_lines!(link_line+1, link_end_line)
48
+ end
49
+
50
+ def remove_link_block(links_container_node, links_container_block_node)
51
+ return source_lines unless links_container_block_node
52
+
53
+ link_container_start_line = links_container_node.location.start_line-1
54
+ link_container_end_line = links_container_node.location.end_line-1
55
+ block_start = links_container_block_node.location.start_column-1
56
+
57
+ source_lines[link_container_start_line] = source_lines[link_container_start_line][0..block_start] + "\n"
58
+ set_empty_lines!(link_container_start_line+1, link_container_end_line)
59
+ end
60
+
61
+ def set_empty_lines!(start_line, end_line)
62
+ (start_line .. end_line).each do |i|
63
+ source_lines[i] = ''
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyLsp
4
4
  module Ree
5
- VERSION = "0.1.13"
5
+ VERSION = "0.1.15"
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.13
4
+ version: 0.1.15
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-04 00:00:00.000000000 Z
11
+ date: 2025-04-18 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,16 +26,18 @@ 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
33
36
  - lib/ruby_lsp/ruby_lsp_ree/formatters/sort_links_formatter.rb
37
+ - lib/ruby_lsp/ruby_lsp_ree/formatters/unused_links_formatter.rb
34
38
  - lib/ruby_lsp/ruby_lsp_ree/handlers/completion_handler.rb
35
- - lib/ruby_lsp/ruby_lsp_ree/handlers/const_additional_text_edits_creator.rb
36
39
  - lib/ruby_lsp/ruby_lsp_ree/handlers/definition_handler.rb
37
40
  - lib/ruby_lsp/ruby_lsp_ree/handlers/hover_handler.rb
38
- - lib/ruby_lsp/ruby_lsp_ree/handlers/method_additional_text_edits_creator.rb
39
41
  - lib/ruby_lsp/ruby_lsp_ree/listeners/completion_listener.rb
40
42
  - lib/ruby_lsp/ruby_lsp_ree/listeners/definition_listener.rb
41
43
  - lib/ruby_lsp/ruby_lsp_ree/listeners/hover_listener.rb
@@ -48,10 +50,12 @@ files:
48
50
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_rspec_document.rb
49
51
  - lib/ruby_lsp/ruby_lsp_ree/ree_constants.rb
50
52
  - lib/ruby_lsp/ruby_lsp_ree/ree_context.rb
53
+ - lib/ruby_lsp/ruby_lsp_ree/ree_dsl_parser.rb
51
54
  - lib/ruby_lsp/ruby_lsp_ree/ree_formatter.rb
52
55
  - lib/ruby_lsp/ruby_lsp_ree/ree_indexing_enhancement.rb
53
56
  - lib/ruby_lsp/ruby_lsp_ree/ree_object_finder.rb
54
57
  - lib/ruby_lsp/ruby_lsp_ree/ree_rename_handler.rb
58
+ - lib/ruby_lsp/ruby_lsp_ree/ree_source_editor.rb
55
59
  - lib/ruby_lsp/ruby_lsp_ree/ree_template_applicator.rb
56
60
  - lib/ruby_lsp/ruby_lsp_ree/utils/ree_locale_utils.rb
57
61
  - lib/ruby_lsp/ruby_lsp_ree/utils/ree_lsp_utils.rb