ruby-lsp-mongoid 0.1.1 → 0.1.2

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: 25deaba20b973e9a4ceae841a71e0ae0d46b7ba6272fc5c48a9a56900d5240f3
4
- data.tar.gz: 1075b753283b79bdf2cb9a0e36fdc16659d7b79e7f419b7a039531b8d1b92ecf
3
+ metadata.gz: 459274f7c2be280ff2e284869fcd8dc7e424dcce61799d881a27f7bc250edf75
4
+ data.tar.gz: 07ee1f8583968c5b01bc395df23e3b598967e9ae389b10100d4681eb5c592f9d
5
5
  SHA512:
6
- metadata.gz: 7717dc958cb9f9c06fe1e097b8d8771ec0e52cfcf68ab502165ddc21e24dca5cf56cb07fff2c5d5790345fe56f08b479a843b2001b0ac21b7c89a9f0ef920e48
7
- data.tar.gz: da98dc048563431553f88902e5068ce03757dc9056f43c47388651f978a98909e1fbf5664f367c9e3d2fe81c8fbe5a024ea710e9606b95ca7804c57e49af1610
6
+ metadata.gz: c946f9ee440c08d5620f074272c1f34376624b4cfb7251bf26d4e7342a76792bd625d86b78eda2015da57eccdaf4b38be838e97e82a39ead6e8d73c8fe890750
7
+ data.tar.gz: 2343c3b4a06ecd64abf51015cac50ce6f45380148a796777860474e025e446526892a2f2a486235a0f5e38906258b9c2b6b07044d12500b38468ad2cfc8adcde
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.2] - 2025-12-15
4
+
5
+ ### Added
6
+
7
+ - Auto-index core instance methods when including `Mongoid::Document` or `ApplicationDocument`:
8
+ - ID accessors: `_id`, `_id=`, `id`, `id=` (always present in Mongoid documents)
9
+ - Persistence methods: `save`, `save!`, `update`, `update!`, `destroy`, `delete`, `upsert`, `reload`
10
+ - State methods: `new_record?`, `persisted?`, `valid?`, `changed?`
11
+ - Attribute methods: `attributes`, `attributes=`, `assign_attributes`, `read_attribute`, `write_attribute`, `changes`, `errors`
12
+ - Identity methods: `to_key`, `to_param`, `model_name`, `inspect`
13
+ - Auto-index core class methods when including `Mongoid::Document` or `ApplicationDocument`:
14
+ - Query methods: `all`, `where`, `find`, `find_by`, `find_by!`, `first`, `last`, `count`, `exists?`, `distinct`
15
+ - Creation methods: `create`, `create!`, `new`, `build`
16
+ - Modification methods: `update_all`, `delete_all`, `destroy_all`
17
+ - Database methods: `collection`, `database`
18
+ - Support for `ApplicationDocument` pattern (common Rails pattern where `ApplicationDocument` includes `Mongoid::Document`)
19
+
20
+ ### Changed
21
+
22
+ - ID accessors (`_id`, `id`) are now indexed when including `Mongoid::Document`/`ApplicationDocument` instead of at the first DSL call
23
+ - Simplified internal implementation by removing `ensure_id_field_indexed` mechanism
24
+
25
+ ## [0.1.1] - 2025-12-08
26
+
3
27
  ### Added
4
28
 
5
29
  - Auto-index `_id` and `id` accessor methods for all Mongoid models that use any DSL (field, associations, scope)
@@ -1,19 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../../ruby_lsp_mongoid/version"
4
+ require_relative "signature_resolver"
4
5
  require_relative "indexing_enhancement"
5
6
  require_relative "hover"
6
7
 
7
8
  module RubyLsp
8
9
  module Mongoid
9
10
  class Addon < ::RubyLsp::Addon
11
+ include SignatureResolver
12
+
10
13
  def activate(global_state, outgoing_queue)
11
14
  @global_state = global_state
12
15
  @outgoing_queue = outgoing_queue
13
16
  @outgoing_queue << Notification.window_log_message("Activating Ruby LSP Mongoid add-on v#{VERSION}")
17
+
18
+ # Start background thread to update signatures after indexing completes
19
+ @signature_update_thread = Thread.new { wait_for_indexing_and_update_signatures }
14
20
  end
15
21
 
16
- def deactivate; end
22
+ def deactivate
23
+ @signature_update_thread&.kill
24
+ end
17
25
 
18
26
  def name
19
27
  "Ruby LSP Mongoid"
@@ -28,6 +36,91 @@ module RubyLsp
28
36
 
29
37
  Hover.new(response_builder, node_context, @global_state.index, dispatcher)
30
38
  end
39
+
40
+ private
41
+
42
+ def wait_for_indexing_and_update_signatures
43
+ return unless @global_state
44
+
45
+ index = @global_state.index
46
+
47
+ # Wait for initial indexing to complete
48
+ sleep(0.1) until index.instance_variable_get(:@initial_indexing_completed)
49
+
50
+ update_mongoid_signatures(index)
51
+ rescue StandardError => e
52
+ @outgoing_queue << Notification.window_log_message(
53
+ "Ruby LSP Mongoid: Error updating signatures: #{e.message}",
54
+ )
55
+ end
56
+
57
+ def update_mongoid_signatures(index)
58
+ updated_count = 0
59
+
60
+ # Update instance method signatures
61
+ CORE_INSTANCE_METHODS.each do |method_name|
62
+ mongoid_signatures = resolve_instance_method_signature(index, method_name)
63
+ next unless mongoid_signatures
64
+
65
+ updated_count += update_method_signatures_for_mongoid_models(index, method_name, mongoid_signatures)
66
+ end
67
+
68
+ # Update class method signatures
69
+ CORE_CLASS_METHODS.each do |method_name|
70
+ mongoid_signatures = resolve_class_method_signature(index, method_name)
71
+ next unless mongoid_signatures
72
+
73
+ updated_count += update_singleton_method_signatures_for_mongoid_models(
74
+ index,
75
+ method_name,
76
+ mongoid_signatures,
77
+ )
78
+ end
79
+
80
+ if updated_count > 0
81
+ @outgoing_queue << Notification.window_log_message(
82
+ "Ruby LSP Mongoid: Updated #{updated_count} method signatures from Mongoid modules",
83
+ )
84
+ end
85
+ end
86
+
87
+ def update_method_signatures_for_mongoid_models(index, method_name, new_signatures)
88
+ updated = 0
89
+
90
+ # Find all classes that have this method registered by our addon
91
+ # We look for methods that were registered with empty signatures
92
+ index.instance_variable_get(:@entries).each do |_name, entries|
93
+ entries.each do |entry|
94
+ next unless entry.is_a?(RubyIndexer::Entry::Method)
95
+ next unless entry.name == method_name
96
+ next unless entry.signatures.empty? || entry.signatures.first.parameters.empty?
97
+
98
+ # Update the signatures
99
+ entry.instance_variable_set(:@signatures, new_signatures)
100
+ updated += 1
101
+ end
102
+ end
103
+
104
+ updated
105
+ end
106
+
107
+ def update_singleton_method_signatures_for_mongoid_models(index, method_name, new_signatures)
108
+ updated = 0
109
+
110
+ index.instance_variable_get(:@entries).each do |_name, entries|
111
+ entries.each do |entry|
112
+ next unless entry.is_a?(RubyIndexer::Entry::Method)
113
+ next unless entry.name == method_name
114
+ next unless entry.owner.is_a?(RubyIndexer::Entry::SingletonClass)
115
+ next unless entry.signatures.empty? || entry.signatures.first.parameters.empty?
116
+
117
+ entry.instance_variable_set(:@signatures, new_signatures)
118
+ updated += 1
119
+ end
120
+ end
121
+
122
+ updated
123
+ end
31
124
  end
32
125
  end
33
126
  end
@@ -43,10 +43,21 @@ module RubyLsp
43
43
  return unless entries&.any?
44
44
 
45
45
  entry = entries.first
46
+
47
+ # Build hover content with signature and options
48
+ content_parts = []
49
+
50
+ # Add method signature
51
+ signature = format_signature(name.to_s, entry)
52
+ content_parts << signature if signature
53
+
54
+ # Add field options from comments
46
55
  comments = entry.comments
47
- return if comments.nil? || comments.empty?
56
+ content_parts << comments if comments && !comments.empty?
48
57
 
49
- @response_builder.push(comments, category: :documentation)
58
+ return if content_parts.empty?
59
+
60
+ @response_builder.push(content_parts.join("\n\n"), category: :documentation)
50
61
  end
51
62
 
52
63
  def handle_association(node)
@@ -128,6 +139,37 @@ module RubyLsp
128
139
 
129
140
  @response_builder.push(content, category: :documentation)
130
141
  end
142
+
143
+ def format_signature(method_name, entry)
144
+ return nil unless entry.respond_to?(:signatures) && entry.signatures.any?
145
+
146
+ sig = entry.signatures.first
147
+ return nil if sig.parameters.empty?
148
+
149
+ params = sig.parameters.map { |param| format_parameter(param) }.join(", ")
150
+ "```ruby\ndef #{method_name}(#{params})\n```"
151
+ end
152
+
153
+ def format_parameter(param)
154
+ case param
155
+ when RubyIndexer::Entry::RequiredParameter
156
+ param.name.to_s
157
+ when RubyIndexer::Entry::OptionalParameter
158
+ "#{param.name} = nil"
159
+ when RubyIndexer::Entry::KeywordParameter
160
+ "#{param.name}:"
161
+ when RubyIndexer::Entry::OptionalKeywordParameter
162
+ "#{param.name}: nil"
163
+ when RubyIndexer::Entry::RestParameter
164
+ "*#{param.name}"
165
+ when RubyIndexer::Entry::KeywordRestParameter
166
+ "**#{param.name}"
167
+ when RubyIndexer::Entry::BlockParameter
168
+ "&#{param.name}"
169
+ else
170
+ param.name.to_s
171
+ end
172
+ end
131
173
  end
132
174
  end
133
175
  end
@@ -5,7 +5,6 @@ module RubyLsp
5
5
  class IndexingEnhancement < RubyIndexer::Enhancement
6
6
  def initialize(listener)
7
7
  super
8
- @id_indexed_owners = Set.new
9
8
  end
10
9
 
11
10
  def on_call_node_enter(call_node)
@@ -13,6 +12,8 @@ module RubyLsp
13
12
  return unless owner
14
13
 
15
14
  case call_node.name
15
+ when :include
16
+ handle_include(call_node)
16
17
  when :field
17
18
  handle_field(call_node)
18
19
  when :embeds_many, :embedded_in
@@ -30,6 +31,58 @@ module RubyLsp
30
31
 
31
32
  private
32
33
 
34
+ # Core instance methods automatically added by Mongoid::Document
35
+ CORE_INSTANCE_METHODS = %w[
36
+ save save! update update! destroy delete upsert reload
37
+ new_record? persisted? valid? changed?
38
+ attributes attributes= assign_attributes read_attribute write_attribute changes errors
39
+ to_key to_param model_name inspect
40
+ ].freeze
41
+
42
+ # Class methods automatically added by Mongoid::Document
43
+ CORE_CLASS_METHODS = %w[
44
+ all where find find_by find_by! first last count exists? distinct
45
+ create create! new build
46
+ update_all delete_all destroy_all
47
+ collection database
48
+ ].freeze
49
+
50
+ def handle_include(call_node)
51
+ arguments = call_node.arguments&.arguments
52
+ return unless arguments
53
+
54
+ # Check if including Mongoid::Document or ApplicationDocument
55
+ first_arg = arguments.first
56
+ module_name = case first_arg
57
+ when Prism::ConstantReadNode
58
+ first_arg.name.to_s
59
+ when Prism::ConstantPathNode
60
+ first_arg.full_name
61
+ end
62
+
63
+ # Support both Mongoid::Document and ApplicationDocument (common Rails pattern)
64
+ return unless module_name == "Mongoid::Document" || module_name == "ApplicationDocument"
65
+
66
+ owner = @listener.current_owner
67
+ return unless owner
68
+
69
+ loc = call_node.location
70
+
71
+ # Add _id and id accessor methods (always present in Mongoid documents)
72
+ add_accessor_methods("_id", loc)
73
+ add_accessor_methods("id", loc)
74
+
75
+ # Add core instance methods
76
+ CORE_INSTANCE_METHODS.each do |method_name|
77
+ add_core_method(method_name, loc)
78
+ end
79
+
80
+ # Add core class methods
81
+ CORE_CLASS_METHODS.each do |method_name|
82
+ add_singleton_method(method_name, loc, owner)
83
+ end
84
+ end
85
+
33
86
  def handle_field(call_node)
34
87
  name = extract_name(call_node)
35
88
  return unless name
@@ -37,8 +90,6 @@ module RubyLsp
37
90
  loc = call_node.location
38
91
  comment = build_field_options_comment(call_node)
39
92
 
40
- ensure_id_field_indexed(loc)
41
-
42
93
  add_accessor_methods(name, loc, comments: comment)
43
94
 
44
95
  # Handle as: option for field alias
@@ -51,7 +102,6 @@ module RubyLsp
51
102
  return unless name
52
103
 
53
104
  loc = call_node.location
54
- ensure_id_field_indexed(loc)
55
105
 
56
106
  add_accessor_methods(name, loc)
57
107
  end
@@ -61,7 +111,6 @@ module RubyLsp
61
111
  return unless name
62
112
 
63
113
  loc = call_node.location
64
- ensure_id_field_indexed(loc)
65
114
 
66
115
  add_accessor_methods(name, loc)
67
116
 
@@ -74,7 +123,6 @@ module RubyLsp
74
123
  return unless name
75
124
 
76
125
  loc = call_node.location
77
- ensure_id_field_indexed(loc)
78
126
 
79
127
  add_accessor_methods(name, loc)
80
128
  add_builder_methods(name, loc)
@@ -88,9 +136,17 @@ module RubyLsp
88
136
  return unless owner
89
137
 
90
138
  loc = call_node.location
91
- ensure_id_field_indexed(loc)
92
139
 
93
- add_singleton_method(name.to_s, loc, owner)
140
+ # Extract lambda parameters if present
141
+ lambda_node = extract_lambda_node(call_node)
142
+ signatures = if lambda_node
143
+ params = extract_lambda_parameters(lambda_node)
144
+ [RubyIndexer::Entry::Signature.new(params)]
145
+ else
146
+ [RubyIndexer::Entry::Signature.new([])]
147
+ end
148
+
149
+ add_singleton_method_with_signatures(name.to_s, loc, owner, signatures)
94
150
  end
95
151
 
96
152
  def extract_name(call_node)
@@ -175,21 +231,34 @@ module RubyLsp
175
231
  @listener.add_method("#{name}=", location, writer_signatures, comments: comments)
176
232
  end
177
233
 
234
+ def add_core_method(name, location)
235
+ signatures = [RubyIndexer::Entry::Signature.new([])]
236
+ @listener.add_method(name.to_s, location, signatures)
237
+ end
238
+
178
239
  def add_builder_methods(name, location)
179
- builder_signatures = [RubyIndexer::Entry::Signature.new([])]
240
+ builder_signatures = [
241
+ RubyIndexer::Entry::Signature.new([
242
+ RubyIndexer::Entry::OptionalParameter.new(name: :attributes),
243
+ ]),
244
+ ]
180
245
  @listener.add_method("build_#{name}", location, builder_signatures)
181
246
  @listener.add_method("create_#{name}", location, builder_signatures)
182
247
  @listener.add_method("create_#{name}!", location, builder_signatures)
183
248
  end
184
249
 
185
250
  def add_singleton_method(name, node_location, owner)
251
+ signatures = [RubyIndexer::Entry::Signature.new([])]
252
+ add_singleton_method_with_signatures(name, node_location, owner, signatures)
253
+ end
254
+
255
+ def add_singleton_method_with_signatures(name, node_location, owner, signatures)
186
256
  index = @listener.instance_variable_get(:@index)
187
257
  code_units_cache = @listener.instance_variable_get(:@code_units_cache)
188
258
  uri = @listener.instance_variable_get(:@uri)
189
259
 
190
260
  location = RubyIndexer::Location.from_prism_location(node_location, code_units_cache)
191
261
  singleton = index.existing_or_new_singleton_class(owner.name)
192
- signatures = [RubyIndexer::Entry::Signature.new([])]
193
262
 
194
263
  index.add(RubyIndexer::Entry::Method.new(
195
264
  name,
@@ -203,6 +272,69 @@ module RubyLsp
203
272
  ))
204
273
  end
205
274
 
275
+ def extract_lambda_node(call_node)
276
+ arguments = call_node.arguments&.arguments
277
+ return unless arguments
278
+
279
+ arguments.find { |arg| arg.is_a?(Prism::LambdaNode) }
280
+ end
281
+
282
+ def extract_lambda_parameters(lambda_node)
283
+ return [] unless lambda_node.is_a?(Prism::LambdaNode)
284
+
285
+ params_node = lambda_node.parameters
286
+ return [] unless params_node
287
+
288
+ # Lambda parameters can be either BlockParametersNode or NumberedParametersNode
289
+ case params_node
290
+ when Prism::BlockParametersNode
291
+ extract_parameters_from_block_params(params_node)
292
+ else
293
+ []
294
+ end
295
+ end
296
+
297
+ def extract_parameters_from_block_params(block_params_node)
298
+ params = []
299
+
300
+ # BlockParametersNode has a `parameters` method that returns ParametersNode
301
+ inner_params = block_params_node.parameters
302
+ return params unless inner_params
303
+
304
+ # Required parameters
305
+ inner_params.requireds&.each do |param|
306
+ next unless param.respond_to?(:name)
307
+
308
+ params << RubyIndexer::Entry::RequiredParameter.new(name: param.name)
309
+ end
310
+
311
+ # Optional parameters
312
+ inner_params.optionals&.each do |param|
313
+ next unless param.respond_to?(:name)
314
+
315
+ params << RubyIndexer::Entry::OptionalParameter.new(name: param.name)
316
+ end
317
+
318
+ # Rest parameter
319
+ if inner_params.rest && inner_params.rest.respond_to?(:name)
320
+ name = inner_params.rest.name || :args
321
+ params << RubyIndexer::Entry::RestParameter.new(name: name)
322
+ end
323
+
324
+ # Keyword parameters
325
+ inner_params.keywords&.each do |param|
326
+ next unless param.respond_to?(:name)
327
+
328
+ if param.respond_to?(:value) && param.value
329
+ params << RubyIndexer::Entry::OptionalKeywordParameter.new(name: param.name)
330
+ else
331
+ params << RubyIndexer::Entry::KeywordParameter.new(name: param.name)
332
+ end
333
+ end
334
+
335
+ params
336
+ end
337
+
206
338
  def singularize(name)
207
339
  name_str = name.to_s
208
340
  if name_str.end_with?("ies")
@@ -213,18 +345,6 @@ module RubyLsp
213
345
  name_str
214
346
  end
215
347
  end
216
-
217
- def ensure_id_field_indexed(location)
218
- owner = @listener.current_owner
219
- return unless owner
220
- return if @id_indexed_owners.include?(owner.name)
221
-
222
- @id_indexed_owners.add(owner.name)
223
-
224
- # Add _id and id (alias) accessor methods
225
- add_accessor_methods("_id", location)
226
- add_accessor_methods("id", location)
227
- end
228
348
  end
229
349
  end
230
350
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLsp
4
+ module Mongoid
5
+ # Resolves method signatures from Mongoid modules in the Ruby LSP index.
6
+ # Used to update method signatures after initial indexing is complete.
7
+ module SignatureResolver
8
+ # Mongoid modules that provide instance methods
9
+ INSTANCE_METHOD_SOURCES = [
10
+ "Mongoid::Persistable::Savable",
11
+ "Mongoid::Persistable::Updatable",
12
+ "Mongoid::Persistable::Deletable",
13
+ "Mongoid::Persistable::Destroyable",
14
+ "Mongoid::Persistable::Upsertable",
15
+ "Mongoid::Attributes",
16
+ "Mongoid::Reloadable",
17
+ "Mongoid::Stateful",
18
+ "Mongoid::Changeable",
19
+ "Mongoid::Inspectable",
20
+ ].freeze
21
+
22
+ # Mongoid modules that provide class methods
23
+ CLASS_METHOD_SOURCES = [
24
+ "Mongoid::Findable",
25
+ "Mongoid::Criteria",
26
+ "Mongoid::Persistable::Creatable::ClassMethods",
27
+ "Mongoid::Clients::Sessions::ClassMethods",
28
+ ].freeze
29
+
30
+ # Core instance methods to look up signatures for
31
+ CORE_INSTANCE_METHODS = %w[
32
+ save save! update update! destroy delete upsert reload
33
+ new_record? persisted? valid? changed?
34
+ attributes attributes= assign_attributes read_attribute write_attribute
35
+ changes errors to_key to_param model_name inspect
36
+ ].freeze
37
+
38
+ # Core class methods to look up signatures for
39
+ CORE_CLASS_METHODS = %w[
40
+ all where find find_by find_by! first last count exists? distinct
41
+ create create! new build update_all delete_all destroy_all
42
+ collection database
43
+ ].freeze
44
+
45
+ # Resolve instance method signature from Mongoid modules
46
+ # @param index [RubyIndexer::Index] Ruby LSP index
47
+ # @param method_name [String] Method name to look up
48
+ # @return [Array<RubyIndexer::Entry::Signature>, nil] Signatures or nil if not found
49
+ def resolve_instance_method_signature(index, method_name)
50
+ INSTANCE_METHOD_SOURCES.each do |module_name|
51
+ entries = index.resolve_method(method_name, module_name)
52
+ next unless entries&.any?
53
+
54
+ entry = entries.first
55
+ return entry.signatures if entry.respond_to?(:signatures) && entry.signatures.any?
56
+ end
57
+
58
+ nil
59
+ end
60
+
61
+ # Resolve class method signature from Mongoid modules
62
+ # @param index [RubyIndexer::Index] Ruby LSP index
63
+ # @param method_name [String] Method name to look up
64
+ # @return [Array<RubyIndexer::Entry::Signature>, nil] Signatures or nil if not found
65
+ def resolve_class_method_signature(index, method_name)
66
+ CLASS_METHOD_SOURCES.each do |module_name|
67
+ entries = index.resolve_method(method_name, module_name)
68
+ next unless entries&.any?
69
+
70
+ entry = entries.first
71
+ return entry.signatures if entry.respond_to?(:signatures) && entry.signatures.any?
72
+ end
73
+
74
+ nil
75
+ end
76
+ end
77
+ end
78
+ end
@@ -4,4 +4,5 @@ require "ruby_lsp/addon"
4
4
  require "ruby_lsp/internal"
5
5
 
6
6
  require_relative "../ruby_lsp_mongoid/version"
7
+ require_relative "ruby_lsp_mongoid/signature_resolver"
7
8
  require_relative "ruby_lsp_mongoid/addon"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyLsp
4
4
  module Mongoid
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp-mongoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shia
@@ -41,6 +41,7 @@ files:
41
41
  - lib/ruby_lsp/ruby_lsp_mongoid/addon.rb
42
42
  - lib/ruby_lsp/ruby_lsp_mongoid/hover.rb
43
43
  - lib/ruby_lsp/ruby_lsp_mongoid/indexing_enhancement.rb
44
+ - lib/ruby_lsp/ruby_lsp_mongoid/signature_resolver.rb
44
45
  - lib/ruby_lsp_mongoid/version.rb
45
46
  homepage: https://github.com/riseshia/ruby-lsp-mongoid
46
47
  licenses: