ruby-lsp-ree 0.1.12 → 0.1.14

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +16 -1
  5. data/lib/ruby_lsp/ruby_lsp_ree/addon.rb +8 -1
  6. data/lib/ruby_lsp/ruby_lsp_ree/formatters/base_formatter.rb +4 -3
  7. data/lib/ruby_lsp/ruby_lsp_ree/formatters/missing_error_contracts_formatter.rb +13 -4
  8. data/lib/ruby_lsp/ruby_lsp_ree/formatters/missing_error_definitions_formatter.rb +8 -3
  9. data/lib/ruby_lsp/ruby_lsp_ree/formatters/missing_error_locales_formatter.rb +13 -6
  10. data/lib/ruby_lsp/ruby_lsp_ree/formatters/unused_links_formatter.rb +56 -0
  11. data/lib/ruby_lsp/ruby_lsp_ree/handlers/completion_handler.rb +3 -2
  12. data/lib/ruby_lsp/ruby_lsp_ree/handlers/const_additional_text_edits_creator.rb +9 -2
  13. data/lib/ruby_lsp/ruby_lsp_ree/handlers/definition_handler.rb +39 -5
  14. data/lib/ruby_lsp/ruby_lsp_ree/handlers/hover_handler.rb +9 -4
  15. data/lib/ruby_lsp/ruby_lsp_ree/listeners/definition_listener.rb +3 -1
  16. data/lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_base_document.rb +12 -4
  17. data/lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_class_document.rb +33 -2
  18. data/lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_document_builder.rb +7 -0
  19. data/lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_link_node.rb +34 -2
  20. data/lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_method_node.rb +27 -3
  21. data/lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_route_document.rb +30 -0
  22. data/lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_rspec_document.rb +0 -4
  23. data/lib/ruby_lsp/ruby_lsp_ree/ree_constants.rb +2 -1
  24. data/lib/ruby_lsp/ruby_lsp_ree/ree_context.rb +6 -0
  25. data/lib/ruby_lsp/ruby_lsp_ree/ree_dsl_parser.rb +39 -0
  26. data/lib/ruby_lsp/ruby_lsp_ree/ree_formatter.rb +23 -11
  27. data/lib/ruby_lsp/ruby_lsp_ree/ree_indexing_enhancement.rb +18 -0
  28. data/lib/ruby_lsp/ruby_lsp_ree/ree_source_editor.rb +68 -0
  29. data/lib/ruby_lsp_ree/version.rb +1 -1
  30. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 655e8856a825a3b31fef950de21f56c6a4feb279693d2892000db1147db9b48e
4
- data.tar.gz: 85145dce503767be96a2f3c3f351be8a77f860713cceadb9e03757c0a97a65c1
3
+ metadata.gz: 72573c4b291dbdfd4ed45bc81625bfc2dbc97839cdee93214e3af5358dfb8572
4
+ data.tar.gz: 82849950d1fa5a0c7eb95f17b2417b97136df143bc871f2ce44e869e2a7c9721
5
5
  SHA512:
6
- metadata.gz: 4956cc2138f636e9bb2068287e456833215b12d56c8f8e4bccf42b9d6ac92db19b54c1fb04fecd4cd18a41355fdb86896f62cbbdb4b0377a0543af88017b8ddc
7
- data.tar.gz: 5f7a6ab9e5003f0c68351306a5df06005aad772fd3fb9dee262ef7e65f8071364f003f985d797f9bdd05ee4e04078092ef4561b63b1a8c0131452412870ebc2d
6
+ metadata.gz: 5aba139740ca71509e49885a4135cbc838fd1ce444b2525d15962126f558c09448caa1478a8df33669d223169e081901b87d8d12464ebd1555825735f8d95ec7
7
+ data.tar.gz: 3e9673f5830791ce62a5dfbcb4e74cec6e3cd774fdfe7e447d9090cb4ae2063d881395bf39086e8158b697492f4e88eb68f35c55cd2b742fcba28d516075e853
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## [0.1.14] - 2025-04-15
2
+
3
+ - do not create error definition for defined class
4
+ - added formatter config settings
5
+ - formatter: remove unused imports
6
+ - fixed adding `throws` section to contract without parentheses
7
+
8
+ ## [0.1.13] - 2025-04-04
9
+
10
+ - Go To Definition for action and serializers in routes
11
+ - support for `schema` objects
12
+ - better support for both ree locale conventions
13
+
1
14
  ## [0.1.11] - 2025-03-28
2
15
 
3
16
  - fixed formatting errors for methods with `rescue`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-lsp-ree (0.1.12)
4
+ ruby-lsp-ree (0.1.14)
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
 
@@ -1,12 +1,13 @@
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
6
  end
7
7
 
8
- def initialize(message_queue)
8
+ def initialize(message_queue, index)
9
9
  @message_queue = message_queue
10
+ @index = index
10
11
  end
11
12
 
12
13
  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,28 +10,35 @@ 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)
16
17
 
18
+ file_name = File.basename(uri.to_s, '.rb')
19
+
17
20
  result = []
18
21
  key_paths = []
19
22
  parsed_doc.parse_error_definitions
20
23
  parsed_doc.error_definitions.each do |error_definition|
21
- key_path = if error_definition.value.arguments.arguments.size > 1
22
- error_definition.value.arguments.arguments[1].unescaped
24
+ key_path_entries = if error_definition.value.arguments.arguments.size > 1
25
+ [error_definition.value.arguments.arguments[1].unescaped]
23
26
  else
24
27
  mod = underscore(parsed_doc.module_name)
25
- "#{mod}.errors.#{error_definition.value.arguments.arguments[0].unescaped}"
28
+ [
29
+ "#{mod}.errors.#{error_definition.value.arguments.arguments[0].unescaped}",
30
+ "#{mod}.errors.#{file_name}.#{error_definition.value.arguments.arguments[0].unescaped}"
31
+ ]
26
32
  end
27
33
 
28
- key_paths << key_path
34
+ key_paths << key_path_entries
29
35
  end
30
36
 
31
37
  Dir.glob(File.join(locales_folder, '**/*.yml')).each do |locale_file|
32
- key_paths.each do |key_path|
33
- value = find_locale_value(locale_file, key_path)
38
+ key_paths.each do |key_path_entries|
39
+ value = key_path_entries.map{ find_locale_value(locale_file, _1) }.compact.first
34
40
  unless value
41
+ key_path = key_path_entries.last
35
42
  loc_key = File.basename(locale_file, '.yml')
36
43
 
37
44
  add_locale_placeholder(locale_file, key_path)
@@ -0,0 +1,56 @@
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
+ def call(source, _uri)
11
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_source(source)
12
+ return source if !parsed_doc
13
+
14
+ parsed_doc.parse_links
15
+
16
+ editor = RubyLsp::Ree::ReeSourceEditor.new(source)
17
+ dsl_parser = RubyLsp::Ree::ReeDslParser.new(parsed_doc, @index)
18
+
19
+ links_count = parsed_doc.link_nodes.size
20
+
21
+ removed_links = 0
22
+
23
+ parsed_doc.link_nodes.each do |link_node|
24
+ removed_imports = 0
25
+
26
+ 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
34
+
35
+ if link_node.imports.size == removed_imports
36
+ editor.remove_link_import_arg(link_node)
37
+ end
38
+ end
39
+
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)
42
+
43
+ editor.remove_link(link_node)
44
+ removed_links += 1
45
+ end
46
+
47
+ if removed_links == links_count
48
+ parsed_doc.parse_links_container_node
49
+ editor.remove_link_block(parsed_doc.links_container_node, parsed_doc.links_container_block_node)
50
+ end
51
+
52
+ editor.source
53
+ end
54
+ end
55
+ end
56
+ end
@@ -160,13 +160,14 @@ module RubyLsp
160
160
  class_name = full_class_name.split('::').last
161
161
  package_name = package_name_from_uri(entry.uri)
162
162
  file_name = File.basename(entry.uri.to_s)
163
+ entry_comment = entry.comments && entry.comments.size > 0 ? " (#{entry.comments})" : ''
163
164
 
164
165
  matched_import = parsed_doc.find_import_for_package(class_name, package_name)
165
166
 
166
167
  if matched_import
167
168
  label_details = Interface::CompletionItemLabelDetails.new(
168
169
  description: "imported from: :#{package_name}",
169
- detail: ""
170
+ detail: entry_comment
170
171
  )
171
172
 
172
173
  imported_consts << Interface::CompletionItem.new(
@@ -183,7 +184,7 @@ module RubyLsp
183
184
  else
184
185
  label_details = Interface::CompletionItemLabelDetails.new(
185
186
  description: "from: :#{package_name}",
186
- detail: " #{file_name}"
187
+ detail: entry_comment + " #{file_name}"
187
188
  )
188
189
 
189
190
  not_imported_consts << Interface::CompletionItem.new(
@@ -46,7 +46,11 @@ module RubyLsp
46
46
  else
47
47
  # add new link
48
48
 
49
- link_text = "\s\slink #{const_link[:link_name]}, import: -> { #{@const_name} }"
49
+ link_text = if const_link[:link_type] == :file_path
50
+ "\s\slink #{const_link[:link_name]}, -> { #{@const_name} }"
51
+ else
52
+ "\s\slink #{const_link[:link_name]}, import: -> { #{@const_name} }"
53
+ end
50
54
 
51
55
  if @parsed_doc.links_container_node
52
56
  link_text = "\s\s" + link_text
@@ -84,10 +88,12 @@ module RubyLsp
84
88
 
85
89
  ree_obj_name = nil
86
90
  link_name = nil
91
+ link_type = nil
87
92
 
88
93
  if is_ree_object?(@entry.uri)
89
94
  ree_obj_name = File.basename(entry_uri, ".*")
90
95
  link_name = ":#{ree_obj_name}"
96
+ link_type = :ree_object
91
97
 
92
98
  if @package_name != @parsed_doc.package_name
93
99
  link_name += ", from: :#{@package_name}"
@@ -95,9 +101,10 @@ module RubyLsp
95
101
  else
96
102
  ree_obj_name = path_from_package_folder(entry_uri)
97
103
  link_name = "\"#{ree_obj_name}\""
104
+ link_type = :file_path
98
105
  end
99
106
 
100
- return { link_name: link_name, object_name: ree_obj_name }
107
+ return { link_name: link_name, object_name: ree_obj_name, link_type: link_type }
101
108
  end
102
109
  end
103
110
  end
@@ -102,7 +102,6 @@ module RubyLsp
102
102
  def get_linked_object_definition_items(node)
103
103
  result = []
104
104
  parent_node = @node_context.parent
105
- return [] unless parent_node.name == :link
106
105
 
107
106
  link_node = RubyLsp::Ree::ParsedLinkNode.new(parent_node, package_name_from_uri(@uri))
108
107
  package_name = link_node.link_package_name
@@ -124,6 +123,33 @@ module RubyLsp
124
123
  result
125
124
  end
126
125
 
126
+ def get_routes_definition_items(node)
127
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_ast(@root_node, @uri, :route)
128
+
129
+ return [] unless parsed_doc.includes_routes_dsl?
130
+
131
+ parent_node = @node_context.parent
132
+
133
+ package_name = if parsed_doc.has_route_option?(:from)
134
+ parsed_doc.route_option_value(:from)
135
+ else
136
+ package_name_from_uri(@uri)
137
+ end
138
+
139
+ ree_object = @finder.find_object_for_package(node.unescaped, package_name)
140
+ return [] unless ree_object
141
+
142
+ [
143
+ Interface::Location.new(
144
+ uri: ree_object.uri.to_s,
145
+ range: Interface::Range.new(
146
+ start: Interface::Position.new(line: 0, character: 0),
147
+ end: Interface::Position.new(line: 0, character: 0),
148
+ ),
149
+ )
150
+ ]
151
+ end
152
+
127
153
  def get_linked_filepath_definition_items(node)
128
154
  result = []
129
155
  local_path = find_local_file_path(node.unescaped)
@@ -168,17 +194,25 @@ module RubyLsp
168
194
 
169
195
  result = []
170
196
 
171
- key_path = if @node_context.parent.arguments.arguments.size > 1
172
- @node_context.parent.arguments.arguments[1].unescaped
197
+ key_path_entries = if @node_context.parent.arguments.arguments.size > 1
198
+ [@node_context.parent.arguments.arguments[1].unescaped]
173
199
  else
174
200
  parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_ast(@root_node, @uri)
201
+ file_name = File.basename(@uri.path, '.rb')
175
202
 
176
203
  mod = underscore(parsed_doc.module_name)
177
- "#{mod}.errors.#{node.unescaped}"
204
+ [
205
+ "#{mod}.errors.#{node.unescaped}",
206
+ "#{mod}.errors.#{file_name}.#{node.unescaped}"
207
+ ]
178
208
  end
179
209
 
180
210
  Dir.glob(File.join(locales_folder, '**/*.yml')).each do |locale_file|
181
- location = find_locale_key_location(locale_file, key_path)
211
+ values = key_path_entries.map{ [_1, find_locale_value(locale_file, _1)] }
212
+ key_path = values.select{ |val| !val[1].nil? }.last
213
+ next unless key_path
214
+
215
+ location = find_locale_key_location(locale_file, key_path[0])
182
216
 
183
217
  result << Interface::Location.new(
184
218
  uri: locale_file,
@@ -109,17 +109,22 @@ module RubyLsp
109
109
 
110
110
  result = []
111
111
 
112
- key_path = if @node_context.parent.arguments.arguments.size > 1
113
- @node_context.parent.arguments.arguments[1].unescaped
112
+ key_path_entries = if @node_context.parent.arguments.arguments.size > 1
113
+ [@node_context.parent.arguments.arguments[1].unescaped]
114
114
  else
115
+ file_name = File.basename(uri.path, '.rb')
116
+
115
117
  mod = underscore(parsed_doc.module_name)
116
- "#{mod}.errors.#{node.unescaped}"
118
+ [
119
+ "#{mod}.errors.#{node.unescaped}",
120
+ "#{mod}.errors.#{file_name}.#{node.unescaped}"
121
+ ]
117
122
  end
118
123
 
119
124
  documentation = ''
120
125
 
121
126
  Dir.glob(File.join(locales_folder, '**/*.yml')).each do |locale_file|
122
- value = find_locale_value(locale_file, key_path)
127
+ value = key_path_entries.map{ find_locale_value(locale_file, _1) }.compact.first
123
128
 
124
129
  loc_key = File.basename(locale_file, '.yml')
125
130
 
@@ -44,8 +44,10 @@ module RubyLsp
44
44
  def on_symbol_node_enter(node)
45
45
  definition_items = if @ree_context.is_error_definition?
46
46
  @handler.get_error_code_definition_items(node)
47
- else
47
+ elsif @ree_context.is_link_object?
48
48
  @handler.get_linked_object_definition_items(node)
49
+ else
50
+ @handler.get_routes_definition_items(node)
49
51
  end
50
52
 
51
53
  put_items_into_response(definition_items)
@@ -16,6 +16,18 @@ class RubyLsp::Ree::ParsedBaseDocument
16
16
  false
17
17
  end
18
18
 
19
+ def includes_link_dsl?
20
+ false
21
+ end
22
+
23
+ def includes_routes_dsl?
24
+ false
25
+ end
26
+
27
+ def includes_ree_dsl?
28
+ false
29
+ end
30
+
19
31
  def includes_linked_object?(obj_name)
20
32
  @link_nodes.map(&:name).include?(obj_name)
21
33
  end
@@ -42,10 +54,6 @@ class RubyLsp::Ree::ParsedBaseDocument
42
54
  raise "abstract method"
43
55
  end
44
56
 
45
- def includes_link_dsl?
46
- raise "abstract method"
47
- end
48
-
49
57
  def parse_links
50
58
  raise "abstract method"
51
59
  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
@@ -33,6 +34,14 @@ class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
33
34
  @class_includes.any?{ node_name(_1) == LINK_DSL_MODULE }
34
35
  end
35
36
 
37
+ def includes_routes_dsl?
38
+ @class_includes.any?{ node_name(_1) == ROUTES_DSL_MODULE }
39
+ end
40
+
41
+ def includes_ree_dsl?
42
+ ree_dsls.size > 0
43
+ end
44
+
36
45
  def includes_linked_constant?(const_name)
37
46
  @link_nodes.map(&:imports).flatten.include?(const_name)
38
47
  end
@@ -102,7 +111,7 @@ class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
102
111
 
103
112
  @link_nodes = nodes.map do |link_node|
104
113
  link_node = RubyLsp::Ree::ParsedLinkNode.new(link_node, package_name)
105
- link_node.parse_imports
114
+ link_node.parse_imports # TODO move parse imports inside link_node constructor
106
115
  link_node
107
116
  end
108
117
  end
@@ -175,6 +184,24 @@ class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
175
184
  @error_definition_names = @error_definitions.map(&:name)
176
185
  end
177
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
+
178
205
  def class_name
179
206
  class_node.constant_path.name.to_s
180
207
  end
@@ -195,4 +222,8 @@ class RubyLsp::Ree::ParsedClassDocument < RubyLsp::Ree::ParsedBaseDocument
195
222
  def imported_constants
196
223
  @link_nodes.map(&:imports).flatten.map(&:to_sym)
197
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
198
229
  end
@@ -1,6 +1,7 @@
1
1
  require 'prism'
2
2
  require_relative 'parsed_class_document'
3
3
  require_relative 'parsed_rspec_document'
4
+ require_relative 'parsed_route_document'
4
5
 
5
6
  class RubyLsp::Ree::ParsedDocumentBuilder
6
7
  extend RubyLsp::Ree::ReeLspUtils
@@ -37,6 +38,8 @@ class RubyLsp::Ree::ParsedDocumentBuilder
37
38
  build_dao_document(ast)
38
39
  when :bean
39
40
  build_bean_document(ast)
41
+ when :route
42
+ build_route_document(ast)
40
43
  else
41
44
  build_detected_document_type(ast, package_name)
42
45
  end
@@ -104,6 +107,10 @@ class RubyLsp::Ree::ParsedDocumentBuilder
104
107
  document
105
108
  end
106
109
 
110
+ def self.build_route_document(ast)
111
+ RubyLsp::Ree::ParsedRouteDocument.new(ast)
112
+ end
113
+
107
114
  def self.is_ruby_file?(uri)
108
115
  File.extname(uri.to_s) == '.rb'
109
116
  end
@@ -81,6 +81,27 @@ class RubyLsp::Ree::ParsedLinkNode
81
81
  !!import_arg
82
82
  end
83
83
 
84
+ def first_arg_location
85
+ @node.arguments.arguments.first.location
86
+ end
87
+
88
+ def import_block_open_location
89
+ if object_name_type?
90
+ import_arg.value.opening_loc
91
+ else
92
+ import_arg.opening_loc
93
+ end
94
+ end
95
+
96
+ def import_block_close_location
97
+ # TODO maybe use two classes for link types
98
+ if object_name_type?
99
+ import_arg.value.closing_loc
100
+ else
101
+ import_arg.closing_loc
102
+ end
103
+ end
104
+
84
105
  private
85
106
 
86
107
  def last_arg
@@ -100,9 +121,12 @@ class RubyLsp::Ree::ParsedLinkNode
100
121
 
101
122
  if object_name_type?
102
123
  return [] unless import_arg
103
- [import_arg.value.body.body.first.name.to_s]
124
+ import_body = import_arg.value.body.body.first
125
+ parse_object_link_multiple_imports(import_body)
104
126
  elsif last_arg.is_a?(Prism::LambdaNode)
105
- [last_arg.body.body.first.name.to_s]
127
+ return [] unless last_arg.body
128
+ import_body = last_arg.body.body.first
129
+ parse_object_link_multiple_imports(import_body)
106
130
  else
107
131
  return []
108
132
  end
@@ -110,4 +134,12 @@ class RubyLsp::Ree::ParsedLinkNode
110
134
  $stderr.puts("can't parse imports: #{e.message}")
111
135
  return []
112
136
  end
137
+
138
+ 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]
141
+ else
142
+ [import_body.name.to_s]
143
+ end
144
+ end
113
145
  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,30 @@
1
+ class RubyLsp::Ree::ParsedRouteDocument < RubyLsp::Ree::ParsedClassDocument
2
+ include RubyLsp::Ree::ReeLspUtils
3
+
4
+ def initialize(ast, package_name = nil)
5
+ super
6
+ parse_class_includes
7
+ parse_route_options
8
+ end
9
+
10
+ def parse_route_options
11
+ @route_options ||= {}
12
+ return unless @class_node
13
+
14
+ @routes_node = @class_node.body.body.detect{ node_name(_1) == :routes }
15
+ return if !@routes_node || !@routes_node.block
16
+
17
+ @route_opts_node = @routes_node.block.body.body.detect{ node_name(_1) == :opts }
18
+ @route_opts_node.value.elements.each do |assoc_node|
19
+ @route_options[assoc_node.key.unescaped] = assoc_node.value.unescaped
20
+ end
21
+ end
22
+
23
+ def has_route_option?(option_name)
24
+ @route_options.has_key?(option_name.to_s)
25
+ end
26
+
27
+ def route_option_value(option_name)
28
+ @route_options[option_name.to_s]
29
+ end
30
+ end
@@ -31,10 +31,6 @@ class RubyLsp::Ree::ParsedRspecDocument < RubyLsp::Ree::ParsedBaseDocument
31
31
  false
32
32
  end
33
33
 
34
- def includes_link_dsl?
35
- false
36
- end
37
-
38
34
  def parse_describe_node
39
35
  @describe_node ||= @ast.statements.body.detect{ |node| node.is_a?(Prism::CallNode) && node.name == :describe }
40
36
  end
@@ -2,7 +2,8 @@ module RubyLsp
2
2
  module Ree
3
3
  module ReeConstants
4
4
  LINK_DSL_MODULE = 'Ree::LinkDSL'
5
-
5
+ ROUTES_DSL_MODULE = 'ReeRoutes::DSL'
6
+
6
7
  LINKS_CONTAINER_TYPES = [
7
8
  :fn,
8
9
  :action,
@@ -15,6 +15,12 @@ module RubyLsp
15
15
 
16
16
  ERROR_DEFINITION_NAMES.include?(@node_context.parent.name)
17
17
  end
18
+
19
+ def is_link_object?
20
+ return false if !@node_context || !@node_context.parent || !@node_context.parent.is_a?(Prism::CallNode)
21
+
22
+ @node_context.parent.name == :link
23
+ end
18
24
  end
19
25
  end
20
26
  end
@@ -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
@@ -47,29 +54,34 @@ module RubyLsp
47
54
 
48
55
  result = []
49
56
  error_keys = []
57
+ file_name = File.basename(uri.to_s, '.rb')
50
58
 
51
59
  parsed_doc.parse_error_definitions
52
60
  parsed_doc.error_definitions.each do |error_definition|
53
- key_path = if error_definition.value.arguments.arguments.size > 1
54
- error_definition.value.arguments.arguments[1].unescaped
61
+ key_path_entries = if error_definition.value.arguments.arguments.size > 1
62
+ [error_definition.value.arguments.arguments[1].unescaped]
55
63
  else
56
64
  mod = underscore(parsed_doc.module_name)
57
- "#{mod}.errors.#{error_definition.value.arguments.arguments[0].unescaped}"
65
+ [
66
+ "#{mod}.errors.#{error_definition.value.arguments.arguments[0].unescaped}",
67
+ "#{mod}.errors.#{file_name}.#{error_definition.value.arguments.arguments[0].unescaped}"
68
+ ]
58
69
  end
59
70
 
60
- error_keys << [key_path, error_definition]
71
+ error_keys << [key_path_entries, error_definition]
61
72
  end
62
73
 
63
74
  Dir.glob(File.join(locales_folder, '**/*.yml')).each do |locale_file|
64
75
  error_keys.each do |error_key|
65
- key_path = error_key[0]
66
- value = find_locale_value(locale_file, key_path)
76
+ key_path_entries = error_key[0]
77
+ value = key_path_entries.map{ find_locale_value(locale_file, _1) }.compact.first
78
+
67
79
  if !value || value == MISSING_LOCALE_PLACEHOLDER
68
80
  loc_key = File.basename(locale_file, '.yml')
69
81
  error_definition = error_key[1]
70
82
 
71
83
  result << RubyLsp::Interface::Diagnostic.new(
72
- message: "Missing locale #{loc_key}: #{key_path}",
84
+ message: "Missing locale #{loc_key}: #{key_path_entries[0]} or #{key_path_entries[1]} ",
73
85
  source: "Ree formatter",
74
86
  severity: RubyLsp::Constant::DiagnosticSeverity::ERROR,
75
87
  range: RubyLsp::Interface::Range.new(
@@ -7,9 +7,14 @@ module RubyLsp
7
7
  include RubyLsp::Ree::ReeLspUtils
8
8
 
9
9
  REE_INDEXED_OBJECTS = [:fn, :enum, :action, :dao, :bean, :mapper, :aggregate, :async_bean]
10
+ SCHEMA_NODE_NAME = :schema
10
11
 
11
12
  def on_call_node_enter(node)
12
13
  return unless @listener.current_owner
14
+
15
+ if node.name == SCHEMA_NODE_NAME
16
+ return index_ree_schema(node)
17
+ end
13
18
 
14
19
  return unless REE_INDEXED_OBJECTS.include?(node.name)
15
20
  return unless node.arguments
@@ -37,6 +42,19 @@ module RubyLsp
37
42
 
38
43
  private
39
44
 
45
+ def index_ree_schema(node)
46
+ return if node.receiver
47
+ return if !node.arguments || !node.block
48
+
49
+ @listener.add_class(
50
+ node.arguments.arguments.first.name.to_s,
51
+ node.location,
52
+ node.location,
53
+ parent_class_name: nil,
54
+ comments: "ree_schema"
55
+ )
56
+ end
57
+
40
58
  def parse_signatures(fn_name, ast)
41
59
  class_node = ast.statements.body.detect{ |node| node.is_a?(Prism::ClassNode) }
42
60
  return [] unless class_node
@@ -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_import(link_node, link_import)
31
+ imports_str = link_node.imports.reject{ _1 == link_import}.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.12"
5
+ VERSION = "0.1.14"
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.12
4
+ version: 0.1.14
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-03-28 00:00:00.000000000 Z
11
+ date: 2025-04-15 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:
@@ -31,6 +31,7 @@ files:
31
31
  - lib/ruby_lsp/ruby_lsp_ree/formatters/missing_error_definitions_formatter.rb
32
32
  - lib/ruby_lsp/ruby_lsp_ree/formatters/missing_error_locales_formatter.rb
33
33
  - lib/ruby_lsp/ruby_lsp_ree/formatters/sort_links_formatter.rb
34
+ - lib/ruby_lsp/ruby_lsp_ree/formatters/unused_links_formatter.rb
34
35
  - lib/ruby_lsp/ruby_lsp_ree/handlers/completion_handler.rb
35
36
  - lib/ruby_lsp/ruby_lsp_ree/handlers/const_additional_text_edits_creator.rb
36
37
  - lib/ruby_lsp/ruby_lsp_ree/handlers/definition_handler.rb
@@ -44,13 +45,16 @@ files:
44
45
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_document_builder.rb
45
46
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_link_node.rb
46
47
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_method_node.rb
48
+ - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_route_document.rb
47
49
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_rspec_document.rb
48
50
  - lib/ruby_lsp/ruby_lsp_ree/ree_constants.rb
49
51
  - lib/ruby_lsp/ruby_lsp_ree/ree_context.rb
52
+ - lib/ruby_lsp/ruby_lsp_ree/ree_dsl_parser.rb
50
53
  - lib/ruby_lsp/ruby_lsp_ree/ree_formatter.rb
51
54
  - lib/ruby_lsp/ruby_lsp_ree/ree_indexing_enhancement.rb
52
55
  - lib/ruby_lsp/ruby_lsp_ree/ree_object_finder.rb
53
56
  - lib/ruby_lsp/ruby_lsp_ree/ree_rename_handler.rb
57
+ - lib/ruby_lsp/ruby_lsp_ree/ree_source_editor.rb
54
58
  - lib/ruby_lsp/ruby_lsp_ree/ree_template_applicator.rb
55
59
  - lib/ruby_lsp/ruby_lsp_ree/utils/ree_locale_utils.rb
56
60
  - lib/ruby_lsp/ruby_lsp_ree/utils/ree_lsp_utils.rb