ruby-lsp-ree 0.1.5 → 0.1.7

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: 9c8f904d1cf98e1106296f608f265999c5c606fc83473d3e65a05395dd83ace5
4
- data.tar.gz: daba84c44a1bf697f80ec7589ab0a9a436af14aa5d9d8cc6cb12377623de5e8f
3
+ metadata.gz: 8d8bf3dfcc23d881b4bc1248851b63de1073b810186baeebe6d60894ea6c366a
4
+ data.tar.gz: fe3e91cb35202109f9884bfb523d473b7d54921ae839299d4201b782ea6ee0ee
5
5
  SHA512:
6
- metadata.gz: 318c26fe57dcc12ac7a2209c10abd65b5662034e00eb87fea1a2ea58107ccd955db3b1e22a40c80f0a92e1ff10fef0f27c5a1ed3964b7877daa15dd5c64c61d4
7
- data.tar.gz: 13c136bd68c1aa9f505a79b94d6f30b713e4e7f24b22a2157c5ccdadc1300d57e27fd7fb4bc5d75f55e7a441125dccf10d6923048948ec416616d7dd723936b1
6
+ metadata.gz: 9fc76ea86567ce249f96af8a5d50732c9f23b1dd8e0f0a307ce82d7e74234cef0e908d3cd0ad644b6f5617ab4c3f450a26a9dfe707907a46e0009a12b920b206
7
+ data.tar.gz: 507f4c378489292422e1560cfda1bfe6e3d4fc8cf8642f3f8786e0d75d389b0a9a4881b035da089ede1a8f90377f0016590441b740b5f207eae1c74b143b6f11
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [0.1.7] - 2025-03-14
2
+
3
+ - Go To Definition for ree errors into locales
4
+ - hover for ree error locales
5
+ - formatter: add raised ree errors to `throws` section
6
+
7
+ ## [0.1.6] - 2025-03-04
8
+
9
+ - file rename triggers class name change
10
+ - add documentation string to hover
11
+ - add hover on link section beans
12
+
1
13
  ## [0.1.5] - 2025-02-28
2
14
 
3
15
  - improved hover format
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-lsp-ree (0.1.5)
4
+ ruby-lsp-ree (0.1.7)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -6,6 +6,7 @@ require_relative "ree_indexing_enhancement"
6
6
  require_relative "utils/ree_lsp_utils"
7
7
  require_relative "ree_formatter"
8
8
  require_relative "ree_template_applicator"
9
+ require_relative "ree_rename_handler"
9
10
  require_relative "parsing/parsed_document_builder"
10
11
 
11
12
  module RubyLsp
@@ -46,8 +47,6 @@ module RubyLsp
46
47
  # Clients are not required to implement this capability
47
48
  return unless global_state.supports_watching_files
48
49
 
49
- return unless @template_applicator.template_dir_exists?
50
-
51
50
  message_queue << Request.new(
52
51
  id: "ruby-lsp-ree-file-create-watcher",
53
52
  method: "client/registerCapability",
@@ -60,7 +59,7 @@ module RubyLsp
60
59
  watchers: [
61
60
  Interface::FileSystemWatcher.new(
62
61
  glob_pattern: "**/*.rb",
63
- kind: Constant::WatchKind::CREATE,
62
+ kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE,
64
63
  ),
65
64
  ],
66
65
  ),
@@ -71,10 +70,16 @@ module RubyLsp
71
70
  end
72
71
 
73
72
  def workspace_did_change_watched_files(changes)
74
- $stderr.puts("workspace_did_change_watched_files #{changes.inspect}")
73
+ if changes.size == 1 && changes[0][:type] == Constant::FileChangeType::CREATED
74
+ $stderr.puts("file created #{changes[0][:uri]}")
75
+
76
+ return unless @template_applicator.template_dir_exists?
75
77
 
76
- changes.each do |change_item|
77
- @template_applicator.apply(change_item)
78
+ @template_applicator.apply(changes[0])
79
+ elsif changes.size == 2 && changes.any?{ _1[:type] == Constant::FileChangeType::CREATED } && changes.any?{ _1[:type] == Constant::FileChangeType::DELETED }
80
+ $stderr.puts("file renamed #{changes[0][:uri]} #{changes[1][:uri]}")
81
+
82
+ RubyLsp::Ree::ReeRenameHandler.call(changes)
78
83
  end
79
84
  end
80
85
  end
@@ -110,7 +110,7 @@ module RubyLsp
110
110
  def get_enum_values_completion_items(enum_obj, location)
111
111
  enum_node = RubyLsp::Ree::ParsedDocumentBuilder.build_from_uri(enum_obj.uri, :enum)
112
112
 
113
- class_name = enum_node.get_class_name
113
+ class_name = enum_node.full_class_name
114
114
 
115
115
  label_details = Interface::CompletionItemLabelDetails.new(
116
116
  description: "from: #{class_name}",
@@ -2,17 +2,20 @@ require_relative "../utils/ree_lsp_utils"
2
2
  require_relative "../ree_object_finder"
3
3
  require_relative "../parsing/parsed_link_node"
4
4
  require_relative "../parsing/parsed_document_builder"
5
+ require_relative "../utils/ree_locale_utils"
5
6
 
6
7
  module RubyLsp
7
8
  module Ree
8
9
  class DefinitionHandler
9
10
  include Requests::Support::Common
10
11
  include RubyLsp::Ree::ReeLspUtils
12
+ include RubyLsp::Ree::ReeLocaleUtils
11
13
 
12
14
  def initialize(index, uri, node_context)
13
15
  @index = index
14
16
  @uri = uri
15
17
  @node_context = node_context
18
+ @root_node = @node_context.instance_variable_get(:@nesting_nodes).first
16
19
  @finder = ReeObjectFinder.new(@index)
17
20
  end
18
21
 
@@ -71,7 +74,15 @@ module RubyLsp
71
74
  message = node.message
72
75
  result = []
73
76
 
74
- definition_item = ReeObjectFinder.new(@index).find_object(message)
77
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_ast(@root_node, @uri)
78
+ link_node = parsed_doc.find_link_node(message)
79
+
80
+ definition_item = if link_node
81
+ @finder.find_object_for_package(message, link_node.link_package_name)
82
+ else
83
+ @finder.find_object(message)
84
+ end
85
+
75
86
  return [] unless definition_item
76
87
 
77
88
  definition_uri = definition_item.uri.to_s
@@ -127,6 +138,60 @@ module RubyLsp
127
138
  ),
128
139
  )
129
140
  end
141
+
142
+ def get_error_locales_definition_items(node)
143
+ locales_folder = package_locales_folder_path(@uri.path)
144
+
145
+ return [] unless File.directory?(locales_folder)
146
+
147
+ result = []
148
+ key_path = node.unescaped
149
+
150
+ Dir.glob(File.join(locales_folder, '**/*.yml')).each do |locale_file|
151
+ line = find_locale_key_line(locale_file, key_path)
152
+
153
+ result << Interface::Location.new(
154
+ uri: locale_file,
155
+ range: Interface::Range.new(
156
+ start: Interface::Position.new(line: line, character: 0),
157
+ end: Interface::Position.new(line: line, character: 0),
158
+ ),
159
+ )
160
+ end
161
+
162
+ result
163
+ end
164
+
165
+ def get_error_code_definition_items(node)
166
+ locales_folder = package_locales_folder_path(@uri.path)
167
+
168
+ return [] unless File.directory?(locales_folder)
169
+
170
+ result = []
171
+
172
+ key_path = if @node_context.parent.arguments.arguments.size > 1
173
+ @node_context.parent.arguments.arguments[1].unescaped
174
+ else
175
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_ast(@root_node, @uri)
176
+
177
+ mod = underscore(parsed_doc.module_name)
178
+ "#{mod}.errors.#{node.unescaped}"
179
+ end
180
+
181
+ Dir.glob(File.join(locales_folder, '**/*.yml')).each do |locale_file|
182
+ line = find_locale_key_line(locale_file, key_path)
183
+
184
+ result << Interface::Location.new(
185
+ uri: locale_file,
186
+ range: Interface::Range.new(
187
+ start: Interface::Position.new(line: line, character: 0),
188
+ end: Interface::Position.new(line: line, character: 0),
189
+ ),
190
+ )
191
+ end
192
+
193
+ result
194
+ end
130
195
  end
131
196
  end
132
197
  end
@@ -2,17 +2,20 @@ require_relative "../ree_object_finder"
2
2
  require_relative "../parsing/parsed_document_builder"
3
3
  require_relative "../parsing/parsed_link_node"
4
4
  require_relative "../utils/ree_lsp_utils"
5
+ require_relative "../utils/ree_locale_utils"
5
6
 
6
7
  module RubyLsp
7
8
  module Ree
8
9
  class HoverHandler
9
10
  include Requests::Support::Common
10
11
  include RubyLsp::Ree::ReeLspUtils
12
+ include RubyLsp::Ree::ReeLocaleUtils
11
13
 
12
14
  def initialize(index, node_context)
13
15
  @index = index
14
16
  @node_context = node_context
15
17
  @finder = ReeObjectFinder.new(@index)
18
+ @root_node = @node_context.instance_variable_get(:@nesting_nodes).first
16
19
  end
17
20
 
18
21
  def get_ree_object_hover_items(node)
@@ -20,17 +23,34 @@ module RubyLsp
20
23
 
21
24
  return [] unless ree_object
22
25
 
23
- documentation = <<~DOC
26
+ documentation = get_object_documentation(ree_object)
27
+
28
+ [documentation]
29
+ end
30
+
31
+ def get_linked_object_hover_items(node)
32
+ parent_node = @node_context.parent
33
+ return [] unless parent_node.name == :link
34
+
35
+ ree_object = @finder.find_object(node.unescaped)
36
+
37
+ return [] unless ree_object
38
+
39
+ documentation = get_object_documentation(ree_object)
40
+
41
+ [documentation]
42
+ end
43
+
44
+ def get_object_documentation(ree_object)
45
+ <<~DOC
24
46
  \`\`\`ruby
25
- #{node.name.to_s}#{get_detail_string(ree_object)}
47
+ #{ree_object.name}#{get_detail_string(ree_object)}
26
48
  \`\`\`
27
49
  ---
28
- ree type: :#{@finder.object_type(ree_object)} package: #{package_name_from_uri(ree_object.uri)}
50
+ #{@finder.object_documentation(ree_object)}
29
51
 
30
52
  [#{path_from_package_folder(ree_object.uri)}](#{ree_object.uri})
31
53
  DOC
32
-
33
- [documentation]
34
54
  end
35
55
 
36
56
  def get_detail_string(ree_object)
@@ -44,6 +64,67 @@ module RubyLsp
44
64
 
45
65
  signature.parameters.map(&:decorated_name).join(', ')
46
66
  end
67
+
68
+ def get_error_locales_hover_items(node)
69
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_ast(@root_node, nil)
70
+ uri = get_uri_from_object(parsed_doc)
71
+
72
+ locales_folder = package_locales_folder_path(uri.path)
73
+ return [] unless File.directory?(locales_folder)
74
+
75
+ result = []
76
+ key_path = node.unescaped
77
+
78
+ documentation = ''
79
+
80
+ Dir.glob(File.join(locales_folder, '**/*.yml')).each do |locale_file|
81
+ value = find_locale_value(locale_file, key_path)
82
+
83
+ if value
84
+ loc_key = File.basename(locale_file, '.yml')
85
+ documentation += "#{loc_key}: #{value}\n\n"
86
+ end
87
+ end
88
+
89
+ [documentation]
90
+ end
91
+
92
+ def get_error_code_hover_items(node)
93
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_ast(@root_node, nil)
94
+ uri = get_uri_from_object(parsed_doc)
95
+
96
+ locales_folder = package_locales_folder_path(uri.path)
97
+ return [] unless File.directory?(locales_folder)
98
+
99
+ result = []
100
+
101
+ key_path = if @node_context.parent.arguments.arguments.size > 1
102
+ @node_context.parent.arguments.arguments[1].unescaped
103
+ else
104
+ mod = underscore(parsed_doc.module_name)
105
+ "#{mod}.errors.#{node.unescaped}"
106
+ end
107
+
108
+ documentation = ''
109
+
110
+ Dir.glob(File.join(locales_folder, '**/*.yml')).each do |locale_file|
111
+ value = find_locale_value(locale_file, key_path)
112
+
113
+ if value
114
+ loc_key = File.basename(locale_file, '.yml')
115
+ documentation += "#{loc_key}: #{value}\n\n"
116
+ end
117
+ end
118
+
119
+ [documentation]
120
+ end
121
+
122
+ def get_uri_from_object(parsed_doc)
123
+ obj = parsed_doc.links_container_node_name
124
+
125
+ ree_obj = @finder.find_object(obj)
126
+ ree_obj.uri
127
+ end
47
128
  end
48
129
  end
49
130
  end
@@ -1,4 +1,5 @@
1
1
  require_relative "../handlers/definition_handler"
2
+ require_relative "../ree_context"
2
3
 
3
4
  module RubyLsp
4
5
  module Ree
@@ -8,6 +9,7 @@ module RubyLsp
8
9
  def initialize(response_builder, node_context, index, dispatcher, uri)
9
10
  @response_builder = response_builder
10
11
  @handler = RubyLsp::Ree::DefinitionHandler.new(index, uri, node_context)
12
+ @ree_context = RubyLsp::Ree::ReeContext.new(node_context)
11
13
 
12
14
  dispatcher.register(
13
15
  self,
@@ -40,14 +42,24 @@ module RubyLsp
40
42
  end
41
43
 
42
44
  def on_symbol_node_enter(node)
43
- definition_items = @handler.get_linked_object_definition_items(node)
45
+ definition_items = if @ree_context.is_error_definition?
46
+ @handler.get_error_code_definition_items(node)
47
+ else
48
+ @handler.get_linked_object_definition_items(node)
49
+ end
50
+
44
51
  put_items_into_response(definition_items)
45
52
  rescue => e
46
53
  $stderr.puts("error in definition listener(on_symbol_node_enter): #{e.message} : #{e.backtrace.first}")
47
54
  end
48
55
 
49
56
  def on_string_node_enter(node)
50
- definition_items = @handler.get_linked_filepath_definition_items(node)
57
+ definition_items = if @ree_context.is_error_definition?
58
+ @handler.get_error_locales_definition_items(node)
59
+ else
60
+ @handler.get_linked_filepath_definition_items(node)
61
+ end
62
+
51
63
  put_items_into_response(definition_items)
52
64
  rescue => e
53
65
  $stderr.puts("error in definition listener(on_string_node_enter): #{e.message} : #{e.backtrace.first}")
@@ -8,9 +8,15 @@ module RubyLsp
8
8
  def initialize(response_builder, node_context, index, dispatcher)
9
9
  @response_builder = response_builder
10
10
  @handler = RubyLsp::Ree::HoverHandler.new(index, node_context)
11
+ @ree_context = RubyLsp::Ree::ReeContext.new(node_context)
11
12
 
12
- dispatcher.register(self, :on_call_node_enter)
13
- dispatcher.register(self, :on_constant_read_node_enter)
13
+ dispatcher.register(
14
+ self,
15
+ :on_call_node_enter,
16
+ :on_symbol_node_enter,
17
+ :on_string_node_enter,
18
+ :on_constant_read_node_enter
19
+ )
14
20
  end
15
21
 
16
22
  def on_constant_read_node_enter(node)
@@ -32,6 +38,28 @@ module RubyLsp
32
38
  $stderr.puts("error in hover listener(on_call_node_enter): #{e.message} : #{e.backtrace.first}")
33
39
  end
34
40
 
41
+ def on_string_node_enter(node)
42
+ return unless @ree_context.is_error_definition?
43
+
44
+ hover_items = @handler.get_error_locales_hover_items(node)
45
+
46
+ put_items_into_response(hover_items)
47
+ rescue => e
48
+ $stderr.puts("error in hover listener(on_string_node_enter): #{e.message} : #{e.backtrace.first}")
49
+ end
50
+
51
+ def on_symbol_node_enter(node)
52
+ hover_items = if @ree_context.is_error_definition?
53
+ @handler.get_error_code_hover_items(node)
54
+ else
55
+ @handler.get_linked_object_hover_items(node)
56
+ end
57
+
58
+ put_items_into_response(hover_items)
59
+ rescue => e
60
+ $stderr.puts("error in hover listener(on_symbol_node_enter): #{e.message} : #{e.backtrace.first}")
61
+ end
62
+
35
63
  def put_items_into_response(items)
36
64
  items.each do |item|
37
65
  @response_builder.push(item, category: :documentation)
@@ -1,4 +1,5 @@
1
1
  require_relative 'parsed_link_node'
2
+ require_relative 'parsed_method_node'
2
3
  require 'ostruct'
3
4
 
4
5
  class RubyLsp::Ree::ParsedDocument
@@ -6,9 +7,26 @@ class RubyLsp::Ree::ParsedDocument
6
7
 
7
8
  LINK_DSL_MODULE = 'Ree::LinkDSL'
8
9
 
10
+ ERROR_DEFINITION_NAMES = [
11
+ :auth_error,
12
+ :build_error,
13
+ :conflict_error,
14
+ :invalid_param_error,
15
+ :not_found_error,
16
+ :payment_required_error,
17
+ :permission_error,
18
+ :validation_error
19
+ ]
20
+
21
+ CONTRACT_CALL_NODE_NAMES = [
22
+ :contract,
23
+ :throws
24
+ ]
25
+
9
26
  attr_reader :ast, :package_name, :class_node, :fn_node, :class_includes,
10
27
  :link_nodes, :values, :action_node, :dao_node, :filters,
11
- :bean_node, :bean_methods, :mapper_node, :links_container_block_node, :aggregate_node
28
+ :bean_node, :bean_methods, :mapper_node, :links_container_block_node, :aggregate_node,
29
+ :error_definitions, :doc_instance_methods
12
30
 
13
31
  def initialize(ast)
14
32
  @ast = ast
@@ -161,6 +179,25 @@ class RubyLsp::Ree::ParsedDocument
161
179
  .map{ OpenStruct.new(name: node_name(_1).to_s, signatures: parse_signatures_from_params(_1.parameters)) }
162
180
  end
163
181
 
182
+ def parse_instance_methods
183
+ @doc_instance_methods = []
184
+
185
+ current_contract_node = nil
186
+ class_node.body.body.each do |node|
187
+ if node.is_a?(Prism::CallNode) && CONTRACT_CALL_NODE_NAMES.include?(node_name(node))
188
+ current_contract_node = node
189
+ else
190
+ if node.is_a?(Prism::DefNode)
191
+ @doc_instance_methods << RubyLsp::Ree::ParsedMethodNode.new(node, current_contract_node)
192
+ end
193
+
194
+ current_contract_node = nil
195
+ end
196
+ end
197
+
198
+ @doc_instance_methods
199
+ end
200
+
164
201
  def parse_filter_signature(filter_node)
165
202
  return [] unless filter_node
166
203
 
@@ -175,11 +212,31 @@ class RubyLsp::Ree::ParsedDocument
175
212
  [RubyIndexer::Entry::Signature.new(signature_params)]
176
213
  end
177
214
 
178
- def get_class_name
215
+ def parse_error_definitions
216
+ return unless class_node
217
+
218
+ @error_definitions = class_node.body.body
219
+ .select{ _1.is_a?(Prism::ConstantWriteNode) }
220
+ .select{ ERROR_DEFINITION_NAMES.include?(node_name(_1.value)) }
221
+ end
222
+
223
+ def class_name
224
+ class_node.constant_path.name.to_s
225
+ end
226
+
227
+ def module_name
228
+ class_node.constant_path&.parent&.name.to_s
229
+ end
230
+
231
+ def full_class_name
179
232
  name_parts = [class_node.constant_path&.parent&.name, class_node.constant_path.name]
180
233
  name_parts.compact.map(&:to_s).join('::')
181
234
  end
182
235
 
236
+ def links_container_node_name
237
+ links_container_node.arguments.arguments.first.unescaped
238
+ end
239
+
183
240
  def node_name(node)
184
241
  return nil unless node.respond_to?(:name)
185
242
 
@@ -38,11 +38,11 @@ class RubyLsp::Ree::ParsedDocumentBuilder
38
38
  when :bean
39
39
  build_bean_document(ast)
40
40
  else
41
- build_detected_doument_type(ast)
41
+ build_detected_document_type(ast)
42
42
  end
43
43
  end
44
44
 
45
- def self.build_detected_doument_type(ast)
45
+ def self.build_detected_document_type(ast)
46
46
  if has_root_class?(ast)
47
47
  build_regular_document(ast)
48
48
  elsif has_root_rspec_call?(ast)
@@ -53,7 +53,7 @@ class RubyLsp::Ree::ParsedDocumentBuilder
53
53
  end
54
54
 
55
55
  def self.has_root_class?(ast)
56
- ast.statements.body.detect{ |node| node.is_a?(Prism::ClassNode) }
56
+ !!ast.statements.body.detect{ |node| node.is_a?(Prism::ClassNode) }
57
57
  end
58
58
 
59
59
  def self.has_root_rspec_call?(ast)
@@ -0,0 +1,109 @@
1
+ require 'prism'
2
+
3
+ class RubyLsp::Ree::ParsedMethodNode
4
+ attr_reader :method_node, :contract_node
5
+
6
+ def initialize(method_node, contract_node)
7
+ @method_node = method_node
8
+ @contract_node = contract_node
9
+ end
10
+
11
+ def name
12
+ @method_node.name
13
+ end
14
+
15
+ def has_contract?
16
+ !!@contract_node
17
+ end
18
+
19
+ def start_line
20
+ @method_node.location.start_line - 1
21
+ end
22
+
23
+ def end_line
24
+ @method_node.location.end_line - 1
25
+ end
26
+
27
+ def raised_errors_nested(source, error_definitions)
28
+ return [] if error_definitions.size == 0
29
+
30
+ raised = raised_errors(source, error_definitions)
31
+
32
+ not_detected_errors = error_definitions.select{ !raised.include?(_1.name.to_s) }
33
+ @nested_local_methods.each do |nested_method|
34
+ raised += nested_method.raised_errors_nested(source, not_detected_errors)
35
+ not_detected_errors = error_definitions.select{ !raised.include?(_1.name.to_s) }
36
+ end
37
+
38
+ raised
39
+ end
40
+
41
+ def raised_errors(source, error_definitions)
42
+ raised = []
43
+ error_names = error_definitions.map(&:name).map(&:to_s)
44
+
45
+ source.lines[start_line+1 .. end_line-1].each do |line|
46
+ error_names.each do |error_name|
47
+ regex = /\braise #{Regexp.escape(error_name)}\b/
48
+
49
+ if line.match?(regex)
50
+ raised << error_name
51
+ end
52
+ end
53
+ end
54
+
55
+ raised.uniq
56
+ end
57
+
58
+ def throws_errors
59
+ return [] unless has_contract?
60
+ return [] unless has_throw_section?
61
+
62
+ @contract_node.arguments.arguments.map{ _1.name.to_s }
63
+ end
64
+
65
+ def has_throw_section?
66
+ @contract_node && @contract_node.name == :throws
67
+ end
68
+
69
+ def throw_arguments_end_position
70
+ @contract_node.arguments.arguments.last.location.end_column - 1
71
+ end
72
+
73
+ def throw_arguments_end_line
74
+ @contract_node.arguments.arguments.last.location.end_line - 1
75
+ end
76
+
77
+ def contract_node_end_position
78
+ @contract_node.location.end_column - 1
79
+ end
80
+
81
+ def contract_node_end_line
82
+ @contract_node.location.end_line - 1
83
+ end
84
+
85
+ def parse_nested_local_methods(local_methods)
86
+ local_method_names = local_methods.map(&:name)
87
+ call_nodes = parse_body_call_objects(@method_node.body.body)
88
+ call_node_names = call_nodes.map(&:name)
89
+
90
+ @nested_local_methods = local_methods.select{ call_node_names.include?(_1.name) }
91
+ @nested_local_methods.each{ _1.parse_nested_local_methods(local_methods) }
92
+ end
93
+
94
+ def parse_body_call_objects(node_body)
95
+ call_nodes = []
96
+
97
+ node_body.each do |node|
98
+ if node.is_a?(Prism::CallNode) && !node.receiver
99
+ call_nodes << node
100
+ elsif node.respond_to?(:statements)
101
+ call_nodes += parse_body_call_objects(node.statements.body)
102
+ elsif node.respond_to?(:block) && node.block
103
+ call_nodes += parse_body_call_objects(node.block.body.body)
104
+ end
105
+ end
106
+
107
+ call_nodes
108
+ end
109
+ end
@@ -0,0 +1,28 @@
1
+ require 'prism'
2
+
3
+ module RubyLsp
4
+ module Ree
5
+ class ReeContext
6
+ ERROR_DEFINITION_NAMES = [
7
+ :auth_error,
8
+ :build_error,
9
+ :conflict_error,
10
+ :invalid_param_error,
11
+ :not_found_error,
12
+ :payment_required_error,
13
+ :permission_error,
14
+ :validation_error
15
+ ]
16
+
17
+ def initialize(node_context)
18
+ @node_context = node_context
19
+ end
20
+
21
+ def is_error_definition?
22
+ return false if !@node_context || !@node_context.parent || !@node_context.parent.is_a?(Prism::CallNode)
23
+
24
+ ERROR_DEFINITION_NAMES.include?(@node_context.parent.name)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -9,7 +9,9 @@ module RubyLsp
9
9
 
10
10
  def run_formatting(uri, document)
11
11
  source = document.source
12
- sort_links(source)
12
+
13
+ sorted_source = sort_links(source)
14
+ add_missing_error_contracts(sorted_source)
13
15
  rescue => e
14
16
  $stderr.puts("error in ree_formatter: #{e.message} : #{e.backtrace.first}")
15
17
  end
@@ -57,6 +59,47 @@ module RubyLsp
57
59
  source_lines[link_line - 1] = sorted_lines[index]
58
60
  end
59
61
 
62
+ source_lines.join()
63
+ end
64
+
65
+ def add_missing_error_contracts(source)
66
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_source(source)
67
+ return source if !parsed_doc || !parsed_doc.class_node
68
+
69
+ parsed_doc.parse_error_definitions
70
+ parsed_doc.parse_instance_methods
71
+
72
+ parsed_doc.doc_instance_methods.select(&:has_contract?).each do |doc_instance_method|
73
+ doc_instance_method.parse_nested_local_methods(parsed_doc.doc_instance_methods)
74
+
75
+ raised_errors = doc_instance_method.raised_errors_nested(source, parsed_doc.error_definitions)
76
+ throws_errors = doc_instance_method.throws_errors
77
+
78
+ missed_errors = raised_errors - throws_errors
79
+ source = add_missed_errors(source, doc_instance_method, missed_errors)
80
+ end
81
+
82
+ source
83
+ end
84
+
85
+ def add_missed_errors(source, doc_instance_method, missed_errors)
86
+ return source if missed_errors.size == 0
87
+
88
+ source_lines = source.lines
89
+
90
+ if doc_instance_method.has_throw_section?
91
+ position = doc_instance_method.throw_arguments_end_position
92
+ line = doc_instance_method.throw_arguments_end_line
93
+
94
+ source_lines[line] = source_lines[line][0..position] + ", #{missed_errors.join(', ')})\n"
95
+ else
96
+ position = doc_instance_method.contract_node_end_position
97
+ line = doc_instance_method.contract_node_end_line
98
+
99
+ source_lines[line] = source_lines[line][0..position] + ".throws(#{missed_errors.join(', ')})\n"
100
+ end
101
+
102
+
60
103
  source_lines.join()
61
104
  end
62
105
  end
@@ -18,9 +18,14 @@ module RubyLsp
18
18
  obj_name = node.arguments.child_nodes.first.unescaped
19
19
  return unless current_filename == obj_name
20
20
 
21
+ source = @listener.instance_variable_get(:@source_lines).join
22
+ ast = Prism.parse(source).value # TODO use doc builder
23
+
21
24
  location = node.location
22
- signatures = parse_signatures(obj_name)
23
- comments = "ree_object\ntype: :#{node.name}"
25
+ signatures = parse_signatures(obj_name, ast)
26
+ documentation = parse_documentation(obj_name, ast)
27
+
28
+ comments = "ree_object\ntype: :#{node.name}\n#{documentation}"
24
29
 
25
30
  @listener.add_method(
26
31
  obj_name,
@@ -32,10 +37,7 @@ module RubyLsp
32
37
 
33
38
  private
34
39
 
35
- def parse_signatures(fn_name)
36
- source = @listener.instance_variable_get(:@source_lines).join
37
- ast = Prism.parse(source).value
38
-
40
+ def parse_signatures(fn_name, ast)
39
41
  class_node = ast.statements.body.detect{ |node| node.is_a?(Prism::ClassNode) }
40
42
  return [] unless class_node
41
43
 
@@ -47,6 +49,28 @@ module RubyLsp
47
49
  [RubyIndexer::Entry::Signature.new(signature_params)]
48
50
  end
49
51
 
52
+ def parse_documentation(fn_name, ast)
53
+ class_node = ast.statements.body.detect{ |node| node.is_a?(Prism::ClassNode) }
54
+ return '' unless class_node
55
+
56
+ doc_node = class_node.body.body.detect{ |node| node.respond_to?(:name) && node.name == :doc }
57
+ return '' unless doc_node
58
+
59
+ str_node = doc_node.arguments.arguments.first
60
+
61
+ case str_node
62
+ when Prism::StringNode
63
+ str_node.unescaped
64
+ when Prism::InterpolatedStringNode
65
+ str_node.parts.map(&:unescaped).join
66
+ else
67
+ ''
68
+ end
69
+ rescue => e
70
+ $stderr.puts("error parsing documentation for #{fn_name}: #{e.message} : #{e.backtrace.first}")
71
+ return ''
72
+ end
73
+
50
74
  def current_filename
51
75
  uri = @listener.instance_variable_get(:@uri)
52
76
  File.basename(uri.path, '.rb')
@@ -1,6 +1,10 @@
1
+ require_relative "utils/ree_lsp_utils"
2
+
1
3
  module RubyLsp
2
4
  module Ree
3
5
  class ReeObjectFinder
6
+ include RubyLsp::Ree::ReeLspUtils
7
+
4
8
  MAX_LIMIT = 1000
5
9
 
6
10
  REE_OBJECT_STRING = 'ree_object'
@@ -42,6 +46,13 @@ module RubyLsp
42
46
  objects_by_name.detect{ _1.comments.to_s.lines.first&.chomp == REE_OBJECT_STRING }
43
47
  end
44
48
 
49
+ def find_object_for_package(name, package_name)
50
+ objects_by_name = @index[name]
51
+ return unless objects_by_name
52
+
53
+ objects_by_name.detect{ _1.comments.to_s.lines.first&.chomp == REE_OBJECT_STRING && package_name_from_uri(_1.uri) == package_name }
54
+ end
55
+
45
56
  def find_objects_by_types(name, types)
46
57
  objects_by_name = @index[name]
47
58
  return [] unless objects_by_name
@@ -76,6 +87,10 @@ module RubyLsp
76
87
 
77
88
  type_str.split(' ').last[1..-1].to_sym
78
89
  end
90
+
91
+ def object_documentation(ree_object)
92
+ ree_object.comments.lines[2..-1].join("\n").chomp
93
+ end
79
94
  end
80
95
  end
81
96
  end
@@ -0,0 +1,39 @@
1
+ module RubyLsp
2
+ module Ree
3
+ class ReeRenameHandler
4
+ include RubyLsp::Ree::ReeLspUtils
5
+
6
+ def self.call(changes)
7
+ old_uri = URI.parse(changes.detect{ _1[:type] == Constant::FileChangeType::DELETED }[:uri])
8
+ new_uri = URI.parse(changes.detect{ _1[:type] == Constant::FileChangeType::CREATED }[:uri])
9
+
10
+ old_file_name = File.basename(old_uri, '.rb')
11
+ new_file_name = File.basename(new_uri, '.rb')
12
+
13
+ return if old_file_name == new_file_name
14
+
15
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_uri(new_uri)
16
+ return if !parsed_doc || !parsed_doc.class_node
17
+
18
+ old_class_name = old_file_name.split('_').collect(&:capitalize).join
19
+ new_class_name = new_file_name.split('_').collect(&:capitalize).join
20
+
21
+ return unless parsed_doc.class_name == old_class_name
22
+
23
+ file_content_lines = File.read(new_uri.path).lines
24
+
25
+ class_line = parsed_doc.class_node.location.start_line - 1
26
+
27
+ file_content_lines[class_line].gsub!(/\b#{old_class_name}\b/, new_class_name)
28
+
29
+ if parsed_doc.links_container_node
30
+ links_container_node_line = parsed_doc.links_container_node.location.start_line - 1
31
+
32
+ file_content_lines[links_container_node_line].gsub!(/\b#{old_file_name}\b/, new_file_name)
33
+ end
34
+
35
+ File.write(new_uri.path, file_content_lines.join)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ require 'yaml'
2
+
3
+ module RubyLsp
4
+ module Ree
5
+ module ReeLocaleUtils
6
+ def package_locales_folder_path(uri)
7
+ uri_parts = uri.to_s.chomp(File.extname(uri.to_s)).split('/')
8
+
9
+ package_folder_index = uri_parts.index('package')
10
+ return unless package_folder_index
11
+
12
+ path_parts = uri_parts.take(package_folder_index+2) + ['locales']
13
+ path_parts.join('/')
14
+ end
15
+
16
+ def find_locale_value(file_path, key_path)
17
+ loc_yaml = YAML.load_file(file_path)
18
+ loc_key = File.basename(file_path, '.yml')
19
+ key_parts = [loc_key] + key_path.split('.')
20
+
21
+ loc_yaml.dig(*key_parts)
22
+ end
23
+
24
+ def find_locale_key_line(file_path, key_path)
25
+ loc_key = File.basename(file_path, '.yml')
26
+
27
+ key_parts = [loc_key] + key_path.split('.')
28
+
29
+ current_key_index = 0
30
+ current_key = key_parts[current_key_index]
31
+ regex = /^\s*#{Regexp.escape(current_key)}:/
32
+
33
+ File.open(file_path, 'r:UTF-8').each_with_index do |line, line_index|
34
+ if line.match?(regex)
35
+ current_key_index += 1
36
+ current_key = key_parts[current_key_index]
37
+ return line_index unless current_key
38
+
39
+ regex = /^\s*#{Regexp.escape(current_key)}:/
40
+ end
41
+ end
42
+
43
+ 0
44
+ end
45
+ end
46
+ end
47
+ end
@@ -7,7 +7,7 @@ module RubyLsp
7
7
  file_name = file_path + ".rb"
8
8
  Dir[File.join('**', file_name)].first
9
9
  end
10
-
10
+
11
11
  def package_name_from_uri(uri)
12
12
  uri_parts = uri.to_s.split('/')
13
13
 
@@ -170,6 +170,17 @@ module RubyLsp
170
170
  :"(#{names_with_commas})"
171
171
  end
172
172
  end
173
+
174
+ # copied from ree string_utils
175
+ def underscore(camel_cased_word)
176
+ return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
177
+ word = camel_cased_word.to_s.gsub("::".freeze, "/".freeze)
178
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
179
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
180
+ word.tr!("-".freeze, "_".freeze)
181
+ word.downcase!
182
+ word
183
+ end
173
184
  end
174
185
  end
175
186
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyLsp
4
4
  module Ree
5
- VERSION = "0.1.5"
5
+ VERSION = "0.1.7"
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.5
4
+ version: 0.1.7
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-02-28 00:00:00.000000000 Z
11
+ date: 2025-03-20 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:
@@ -35,11 +35,15 @@ files:
35
35
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_document.rb
36
36
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_document_builder.rb
37
37
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_link_node.rb
38
+ - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_method_node.rb
38
39
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_rspec_document.rb
40
+ - lib/ruby_lsp/ruby_lsp_ree/ree_context.rb
39
41
  - lib/ruby_lsp/ruby_lsp_ree/ree_formatter.rb
40
42
  - lib/ruby_lsp/ruby_lsp_ree/ree_indexing_enhancement.rb
41
43
  - lib/ruby_lsp/ruby_lsp_ree/ree_object_finder.rb
44
+ - lib/ruby_lsp/ruby_lsp_ree/ree_rename_handler.rb
42
45
  - lib/ruby_lsp/ruby_lsp_ree/ree_template_applicator.rb
46
+ - lib/ruby_lsp/ruby_lsp_ree/utils/ree_locale_utils.rb
43
47
  - lib/ruby_lsp/ruby_lsp_ree/utils/ree_lsp_utils.rb
44
48
  - lib/ruby_lsp_ree.rb
45
49
  - lib/ruby_lsp_ree/version.rb