type-guessr 0.0.1 → 0.0.3
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 +4 -4
- data/README.md +41 -0
- data/exe/type-guessr +30 -0
- data/lib/ruby_lsp/type_guessr/addon.rb +20 -45
- data/lib/ruby_lsp/type_guessr/code_index_adapter.rb +352 -0
- data/lib/ruby_lsp/type_guessr/constants.rb +39 -0
- data/lib/ruby_lsp/type_guessr/{graph_builder.rb → debug_graph_builder.rb} +27 -22
- data/lib/ruby_lsp/type_guessr/debug_server.rb +20 -17
- data/lib/ruby_lsp/type_guessr/dsl/activerecord_adapter.rb +404 -0
- data/lib/ruby_lsp/type_guessr/dsl/ar_schema_watcher.rb +96 -0
- data/lib/ruby_lsp/type_guessr/dsl/ar_type_mapper.rb +51 -0
- data/lib/ruby_lsp/type_guessr/dsl.rb +3 -0
- data/lib/ruby_lsp/type_guessr/dsl_type_registrar.rb +60 -0
- data/lib/ruby_lsp/type_guessr/hover.rb +129 -261
- data/lib/ruby_lsp/type_guessr/rails_server_addon.rb +83 -0
- data/lib/ruby_lsp/type_guessr/runtime_adapter.rb +613 -277
- data/lib/ruby_lsp/type_guessr/type_inferrer.rb +8 -105
- data/lib/type-guessr.rb +3 -11
- data/lib/type_guessr/core/cache/gem_dependency_resolver.rb +113 -0
- data/lib/type_guessr/core/cache/gem_signature_cache.rb +98 -0
- data/lib/type_guessr/core/cache/gem_signature_extractor.rb +87 -0
- data/lib/type_guessr/core/cache.rb +5 -0
- data/lib/{ruby_lsp/type_guessr → type_guessr/core}/config.rb +19 -34
- data/lib/type_guessr/core/converter/call_converter.rb +161 -0
- data/lib/type_guessr/core/converter/container_mutation_converter.rb +241 -0
- data/lib/type_guessr/core/converter/context.rb +144 -0
- data/lib/type_guessr/core/converter/control_flow_converter.rb +425 -0
- data/lib/type_guessr/core/converter/definition_converter.rb +246 -0
- data/lib/type_guessr/core/converter/literal_converter.rb +217 -0
- data/lib/type_guessr/core/converter/prism_converter.rb +154 -1613
- data/lib/type_guessr/core/converter/rbs_converter.rb +35 -14
- data/lib/type_guessr/core/converter/registration.rb +100 -0
- data/lib/type_guessr/core/converter/variable_converter.rb +225 -0
- data/lib/type_guessr/core/converter.rb +4 -0
- data/lib/type_guessr/core/index/location_index.rb +32 -0
- data/lib/type_guessr/core/index.rb +3 -0
- data/lib/type_guessr/core/inference/resolver.rb +516 -349
- data/lib/type_guessr/core/inference.rb +4 -0
- data/lib/type_guessr/core/ir/nodes.rb +362 -103
- data/lib/type_guessr/core/ir.rb +3 -0
- data/lib/type_guessr/core/logger.rb +6 -13
- data/lib/type_guessr/core/node_context_helper.rb +126 -0
- data/lib/type_guessr/core/node_key_generator.rb +31 -0
- data/lib/type_guessr/core/registry/class_variable_registry.rb +63 -0
- data/lib/type_guessr/core/registry/instance_variable_registry.rb +84 -0
- data/lib/type_guessr/core/registry/method_registry.rb +65 -38
- data/lib/type_guessr/core/registry/signature_registry.rb +543 -0
- data/lib/type_guessr/core/registry.rb +6 -0
- data/lib/type_guessr/core/signature_builder.rb +39 -0
- data/lib/type_guessr/core/type_serializer.rb +96 -0
- data/lib/type_guessr/core/type_simplifier.rb +15 -12
- data/lib/type_guessr/core/types.rb +250 -32
- data/lib/type_guessr/core.rb +29 -0
- data/lib/type_guessr/mcp/file_watcher.rb +87 -0
- data/lib/type_guessr/mcp/server.rb +463 -0
- data/lib/type_guessr/mcp/standalone_runtime.rb +213 -0
- data/lib/type_guessr/version.rb +1 -1
- metadata +57 -8
- data/lib/type_guessr/core/rbs_provider.rb +0 -304
- data/lib/type_guessr/core/registry/variable_registry.rb +0 -87
- data/lib/type_guessr/core/signature_provider.rb +0 -101
|
@@ -6,34 +6,8 @@ module RubyLsp
|
|
|
6
6
|
class Hover
|
|
7
7
|
# Core layer shortcuts
|
|
8
8
|
Types = ::TypeGuessr::Core::Types
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# Define all node types that should trigger hover content
|
|
12
|
-
HOVER_NODE_TYPES = %i[
|
|
13
|
-
local_variable_read
|
|
14
|
-
local_variable_write
|
|
15
|
-
local_variable_target
|
|
16
|
-
instance_variable_read
|
|
17
|
-
instance_variable_write
|
|
18
|
-
instance_variable_target
|
|
19
|
-
class_variable_read
|
|
20
|
-
class_variable_write
|
|
21
|
-
class_variable_target
|
|
22
|
-
global_variable_read
|
|
23
|
-
global_variable_write
|
|
24
|
-
global_variable_target
|
|
25
|
-
required_parameter
|
|
26
|
-
optional_parameter
|
|
27
|
-
rest_parameter
|
|
28
|
-
required_keyword_parameter
|
|
29
|
-
optional_keyword_parameter
|
|
30
|
-
keyword_rest_parameter
|
|
31
|
-
block_parameter
|
|
32
|
-
forwarding_parameter
|
|
33
|
-
call
|
|
34
|
-
def
|
|
35
|
-
self
|
|
36
|
-
].freeze
|
|
9
|
+
NodeContextHelper = ::TypeGuessr::Core::NodeContextHelper
|
|
10
|
+
private_constant :Types, :NodeContextHelper
|
|
37
11
|
|
|
38
12
|
def initialize(runtime_adapter, response_builder, node_context, dispatcher, global_state)
|
|
39
13
|
@runtime_adapter = runtime_adapter
|
|
@@ -45,18 +19,16 @@ module RubyLsp
|
|
|
45
19
|
end
|
|
46
20
|
|
|
47
21
|
# Dynamically define handler methods for each node type
|
|
48
|
-
|
|
22
|
+
Constants::HOVER_NODE_MAPPING.each_key do |node_type|
|
|
49
23
|
define_method(:"on_#{node_type}_node_enter") do |node|
|
|
50
24
|
add_hover_content(node)
|
|
51
25
|
end
|
|
52
26
|
end
|
|
53
27
|
|
|
54
|
-
private
|
|
55
|
-
|
|
56
|
-
def register_listeners(dispatcher)
|
|
28
|
+
private def register_listeners(dispatcher)
|
|
57
29
|
dispatcher.register(
|
|
58
30
|
self,
|
|
59
|
-
*
|
|
31
|
+
*Constants::HOVER_NODE_MAPPING.keys.map { |type| :"on_#{type}_node_enter" }
|
|
60
32
|
)
|
|
61
33
|
end
|
|
62
34
|
|
|
@@ -64,12 +36,12 @@ module RubyLsp
|
|
|
64
36
|
IR = ::TypeGuessr::Core::IR
|
|
65
37
|
private_constant :IR
|
|
66
38
|
|
|
67
|
-
def add_hover_content(node)
|
|
39
|
+
private def add_hover_content(node)
|
|
68
40
|
# Generate node_key from scope and Prism node
|
|
69
41
|
# DefNode is indexed with parent scope (not including the method itself)
|
|
70
42
|
exclude_method = node.is_a?(Prism::DefNode)
|
|
71
|
-
scope_id = generate_scope_id(exclude_method: exclude_method)
|
|
72
|
-
node_hash = generate_node_hash(node)
|
|
43
|
+
scope_id = NodeContextHelper.generate_scope_id(@node_context, exclude_method: exclude_method)
|
|
44
|
+
node_hash = NodeContextHelper.generate_node_hash(node, @node_context)
|
|
73
45
|
return unless node_hash
|
|
74
46
|
|
|
75
47
|
node_key = "#{scope_id}:#{node_hash}"
|
|
@@ -105,21 +77,19 @@ module RubyLsp
|
|
|
105
77
|
warn "[TypeGuessr] Error in add_hover_content: #{e.message}\n#{e.backtrace.first(5).join("\n")}"
|
|
106
78
|
end
|
|
107
79
|
|
|
108
|
-
def add_def_node_hover(def_node)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return_result = @runtime_adapter.infer_type(def_node)
|
|
112
|
-
return_type_str = return_result.type.to_s
|
|
113
|
-
|
|
114
|
-
signature = "(#{params_str}) -> #{return_type_str}"
|
|
115
|
-
content = "**Guessed Signature:** `#{signature}`"
|
|
80
|
+
private def add_def_node_hover(def_node)
|
|
81
|
+
method_sig = @runtime_adapter.build_method_signature(def_node)
|
|
82
|
+
content = "**Guessed Signature:** `#{method_sig}`"
|
|
116
83
|
|
|
117
|
-
|
|
84
|
+
if debug_enabled?
|
|
85
|
+
return_result = @runtime_adapter.infer_type(def_node)
|
|
86
|
+
content += build_debug_info(return_result)
|
|
87
|
+
end
|
|
118
88
|
|
|
119
89
|
@response_builder.push(content, category: :documentation)
|
|
120
90
|
end
|
|
121
91
|
|
|
122
|
-
def add_call_node_hover(call_node)
|
|
92
|
+
private def add_call_node_hover(call_node)
|
|
123
93
|
# Special case: Handle .new calls to show constructor signature
|
|
124
94
|
# Support both ClassName.new (ConstantNode) and self.new (SelfNode) in singleton methods
|
|
125
95
|
if call_node.method == :new &&
|
|
@@ -128,6 +98,15 @@ module RubyLsp
|
|
|
128
98
|
return
|
|
129
99
|
end
|
|
130
100
|
|
|
101
|
+
# Handle implicit self calls (receiver is nil)
|
|
102
|
+
unless call_node.receiver
|
|
103
|
+
def_node = lookup_def_node_for_implicit_self(call_node)
|
|
104
|
+
if def_node
|
|
105
|
+
add_def_node_hover(def_node)
|
|
106
|
+
return
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
131
110
|
# Get receiver type to look up method signature
|
|
132
111
|
if call_node.receiver
|
|
133
112
|
# For ConstantNode receiver (e.g., File.exist?, RBS::Environment.from_loader),
|
|
@@ -142,34 +121,55 @@ module RubyLsp
|
|
|
142
121
|
class_name = extract_class_name(receiver_type)
|
|
143
122
|
|
|
144
123
|
if class_name
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
content = "**Guessed Signature:** `#{sig_strs.first}`"
|
|
161
|
-
|
|
162
|
-
if debug_enabled?
|
|
163
|
-
content += "\n\n**[TypeGuessr Debug]**"
|
|
164
|
-
content += "\n\n**Receiver:** `#{receiver_type}`"
|
|
165
|
-
if sig_strs.size > 1
|
|
166
|
-
content += "\n\n**Overloads:**\n"
|
|
167
|
-
sig_strs.each { |s| content += "- `#{s}`\n" }
|
|
124
|
+
method_name_str = call_node.method.to_s
|
|
125
|
+
skip_stdlib_rbs = if receiver_type.is_a?(Types::SingletonType)
|
|
126
|
+
@runtime_adapter.skip_stdlib_rbs_class_method?(class_name, method_name_str)
|
|
127
|
+
else
|
|
128
|
+
@runtime_adapter.skip_stdlib_rbs_method?(class_name, method_name_str)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Methods marked skip_stdlib_rbs skip DefNode + RBS → go straight to fallback (resolver)
|
|
132
|
+
unless skip_stdlib_rbs
|
|
133
|
+
# Try to find DefNode first (for project methods)
|
|
134
|
+
unless receiver_type.is_a?(Types::SingletonType)
|
|
135
|
+
def_node = @runtime_adapter.lookup_method(class_name, method_name_str)
|
|
136
|
+
if def_node
|
|
137
|
+
add_def_node_hover(def_node)
|
|
138
|
+
return
|
|
168
139
|
end
|
|
169
140
|
end
|
|
170
141
|
|
|
171
|
-
|
|
172
|
-
|
|
142
|
+
# Fall back to RBS signature lookup (for stdlib/gems)
|
|
143
|
+
rbs_result = if receiver_type.is_a?(Types::SingletonType)
|
|
144
|
+
@runtime_adapter.get_rbs_class_method_signatures(
|
|
145
|
+
class_name, method_name_str
|
|
146
|
+
)
|
|
147
|
+
else
|
|
148
|
+
@runtime_adapter.get_rbs_method_signatures(
|
|
149
|
+
class_name, method_name_str
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
signatures = rbs_result[:signatures]
|
|
154
|
+
owner = rbs_result[:owner]
|
|
155
|
+
|
|
156
|
+
if signatures.any?
|
|
157
|
+
sig_strs = signatures.map { |sig| sig.method_type.to_s }
|
|
158
|
+
content = "**Guessed Signature:** `#{sig_strs.first}`"
|
|
159
|
+
|
|
160
|
+
if debug_enabled?
|
|
161
|
+
content += "\n\n**[TypeGuessr Debug]**"
|
|
162
|
+
content += "\n\n**Receiver:** `#{receiver_type}`"
|
|
163
|
+
content += "\n\n**Defined in:** `#{owner}`" if owner != class_name
|
|
164
|
+
if sig_strs.size > 1
|
|
165
|
+
content += "\n\n**Overloads:**\n"
|
|
166
|
+
sig_strs.each { |s| content += "- `#{s}`\n" }
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
@response_builder.push(content, category: :documentation)
|
|
171
|
+
return
|
|
172
|
+
end
|
|
173
173
|
end
|
|
174
174
|
end
|
|
175
175
|
end
|
|
@@ -187,7 +187,7 @@ module RubyLsp
|
|
|
187
187
|
end
|
|
188
188
|
|
|
189
189
|
# Build parameter signature for a method call using RubyIndexer
|
|
190
|
-
def build_call_signature_params(call_node)
|
|
190
|
+
private def build_call_signature_params(call_node)
|
|
191
191
|
method_entry = lookup_method_entry_for_call(call_node)
|
|
192
192
|
|
|
193
193
|
if method_entry&.signatures&.any?
|
|
@@ -200,7 +200,7 @@ module RubyLsp
|
|
|
200
200
|
end
|
|
201
201
|
|
|
202
202
|
# Look up method entry from RubyIndexer based on call node
|
|
203
|
-
def lookup_method_entry_for_call(call_node)
|
|
203
|
+
private def lookup_method_entry_for_call(call_node)
|
|
204
204
|
return nil unless @global_state&.index
|
|
205
205
|
return nil unless call_node.receiver
|
|
206
206
|
|
|
@@ -215,7 +215,7 @@ module RubyLsp
|
|
|
215
215
|
end
|
|
216
216
|
|
|
217
217
|
# Format parameters from RubyIndexer method entry with inferred argument types
|
|
218
|
-
def format_params_from_entry(method_entry, args)
|
|
218
|
+
private def format_params_from_entry(method_entry, args)
|
|
219
219
|
params = method_entry.signatures.first.parameters
|
|
220
220
|
return "" if params.nil? || params.empty?
|
|
221
221
|
|
|
@@ -230,7 +230,7 @@ module RubyLsp
|
|
|
230
230
|
end
|
|
231
231
|
|
|
232
232
|
# Format a single parameter based on its type
|
|
233
|
-
def format_single_param(param, arg_type)
|
|
233
|
+
private def format_single_param(param, arg_type)
|
|
234
234
|
param_name = param.name.to_s
|
|
235
235
|
|
|
236
236
|
case param
|
|
@@ -254,7 +254,7 @@ module RubyLsp
|
|
|
254
254
|
end
|
|
255
255
|
|
|
256
256
|
# Format arguments when no method entry is available
|
|
257
|
-
def format_params_from_args(args)
|
|
257
|
+
private def format_params_from_args(args)
|
|
258
258
|
args.each_with_index.map do |arg, i|
|
|
259
259
|
arg_type = @runtime_adapter.infer_type(arg).type.to_s
|
|
260
260
|
"#{arg_type} arg#{i + 1}"
|
|
@@ -262,12 +262,12 @@ module RubyLsp
|
|
|
262
262
|
end
|
|
263
263
|
|
|
264
264
|
# Look up class method entry from RubyIndexer
|
|
265
|
-
def lookup_class_method_entry(class_name, method_name)
|
|
265
|
+
private def lookup_class_method_entry(class_name, method_name)
|
|
266
266
|
return nil unless @global_state&.index
|
|
267
267
|
|
|
268
268
|
# Query singleton class for the method
|
|
269
269
|
# Ruby LSP uses unqualified name for singleton class (e.g., "RBS::Environment::<Class:Environment>")
|
|
270
|
-
unqualified_name =
|
|
270
|
+
unqualified_name = ::TypeGuessr::Core::IR.extract_last_name(class_name)
|
|
271
271
|
singleton_name = "#{class_name}::<Class:#{unqualified_name}>"
|
|
272
272
|
entries = @global_state.index.resolve_method(method_name, singleton_name)
|
|
273
273
|
return nil if entries.nil? || entries.empty?
|
|
@@ -278,7 +278,7 @@ module RubyLsp
|
|
|
278
278
|
end
|
|
279
279
|
|
|
280
280
|
# Look up instance method entry from RubyIndexer
|
|
281
|
-
def lookup_instance_method_entry(class_name, method_name)
|
|
281
|
+
private def lookup_instance_method_entry(class_name, method_name)
|
|
282
282
|
return nil unless @global_state&.index
|
|
283
283
|
|
|
284
284
|
entries = @global_state.index.resolve_method(method_name, class_name)
|
|
@@ -289,13 +289,29 @@ module RubyLsp
|
|
|
289
289
|
nil
|
|
290
290
|
end
|
|
291
291
|
|
|
292
|
-
|
|
292
|
+
# Look up DefNode for implicit self calls (receiver is nil)
|
|
293
|
+
# Searches in current class scope and falls back to top-level
|
|
294
|
+
private def lookup_def_node_for_implicit_self(call_node)
|
|
295
|
+
method_name = call_node.method.to_s
|
|
296
|
+
|
|
297
|
+
# Get current class scope from node_context
|
|
298
|
+
class_name = @node_context.nesting.map { |n| n.is_a?(String) ? n : n.name.to_s }.join("::")
|
|
299
|
+
|
|
300
|
+
# Try current class scope first
|
|
301
|
+
def_node = @runtime_adapter.lookup_method(class_name, method_name) if class_name && !class_name.empty?
|
|
302
|
+
return def_node if def_node
|
|
303
|
+
|
|
304
|
+
# Fall back to top-level (empty class name)
|
|
305
|
+
@runtime_adapter.lookup_method("", method_name)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
private def extract_class_name(type)
|
|
293
309
|
case type
|
|
294
310
|
when Types::ClassInstance
|
|
295
311
|
type.name
|
|
296
312
|
when Types::SingletonType
|
|
297
313
|
type.name
|
|
298
|
-
when Types::ArrayType
|
|
314
|
+
when Types::ArrayType, Types::TupleType
|
|
299
315
|
"Array"
|
|
300
316
|
when Types::HashType, Types::HashShape
|
|
301
317
|
"Hash"
|
|
@@ -303,99 +319,51 @@ module RubyLsp
|
|
|
303
319
|
end
|
|
304
320
|
|
|
305
321
|
# Handle .new calls to show constructor signature
|
|
306
|
-
def add_new_call_hover(call_node)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
content = if init_info
|
|
318
|
-
case init_info[:source]
|
|
319
|
-
when :project
|
|
320
|
-
params_str = format_params(init_info[:params])
|
|
321
|
-
"**Guessed Signature:** `(#{params_str}) -> #{class_name}`"
|
|
322
|
-
when :rbs
|
|
323
|
-
sig_str = init_info[:signature].method_type.to_s
|
|
324
|
-
sig_str = sig_str.sub(/-> .+$/, "-> #{class_name}")
|
|
325
|
-
"**Guessed Signature:** `#{sig_str}`"
|
|
326
|
-
end
|
|
327
|
-
else
|
|
328
|
-
"**Guessed Signature:** `() -> #{class_name}`"
|
|
322
|
+
private def add_new_call_hover(call_node)
|
|
323
|
+
class_name = resolve_receiver_class_name(call_node.receiver)
|
|
324
|
+
result = @runtime_adapter.build_constructor_signature(class_name)
|
|
325
|
+
|
|
326
|
+
content = case result[:source]
|
|
327
|
+
when :project, :default
|
|
328
|
+
"**Guessed Signature:** `#{result[:signature]}`"
|
|
329
|
+
when :rbs
|
|
330
|
+
sig_str = result[:rbs_signature].method_type.to_s
|
|
331
|
+
sig_str = sig_str.sub(/-> .+$/, "-> #{class_name}")
|
|
332
|
+
"**Guessed Signature:** `#{sig_str}`"
|
|
329
333
|
end
|
|
330
334
|
|
|
331
|
-
content += build_debug_info_for_new(
|
|
335
|
+
content += build_debug_info_for_new(result[:source], class_name) if debug_enabled?
|
|
332
336
|
|
|
333
337
|
@response_builder.push(content, category: :documentation)
|
|
334
338
|
end
|
|
335
339
|
|
|
336
|
-
#
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
class_name, "initialize"
|
|
345
|
-
)
|
|
346
|
-
return { signature: signatures.first, source: :rbs } if signatures.any?
|
|
340
|
+
# Resolve receiver to class name (handles aliases via type inference)
|
|
341
|
+
# Uses CodeIndexAdapter to resolve short constant names to fully qualified names
|
|
342
|
+
private def resolve_receiver_class_name(receiver)
|
|
343
|
+
receiver_result = @runtime_adapter.infer_type(receiver)
|
|
344
|
+
short_name = case receiver_result.type
|
|
345
|
+
when Types::SingletonType then receiver_result.type.name
|
|
346
|
+
else receiver.name
|
|
347
|
+
end
|
|
347
348
|
|
|
348
|
-
|
|
349
|
+
# Use CodeIndexAdapter to resolve full qualified name with nesting context
|
|
350
|
+
nesting = @node_context.nesting.map { |n| n.is_a?(String) ? n : n.name.to_s }
|
|
351
|
+
@runtime_adapter.resolve_constant_name(short_name, nesting) || short_name
|
|
349
352
|
end
|
|
350
353
|
|
|
351
354
|
# Build debug info for .new calls
|
|
352
|
-
def build_debug_info_for_new(
|
|
355
|
+
private def build_debug_info_for_new(source, class_name)
|
|
353
356
|
info = "\n\n**[TypeGuessr Debug]**"
|
|
354
357
|
info += "\n\n**Class:** `#{class_name}`"
|
|
355
|
-
info += "\n\n**Source:** #{
|
|
358
|
+
info += "\n\n**Source:** #{source}"
|
|
356
359
|
info
|
|
357
360
|
end
|
|
358
361
|
|
|
359
|
-
def
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
params.map do |param|
|
|
363
|
-
param_type = infer_param_type(param)
|
|
364
|
-
type_str = param_type.to_s
|
|
365
|
-
|
|
366
|
-
case param.kind
|
|
367
|
-
when :required
|
|
368
|
-
"#{type_str} #{param.name}"
|
|
369
|
-
when :optional
|
|
370
|
-
"?#{type_str} #{param.name}"
|
|
371
|
-
when :rest
|
|
372
|
-
"*#{type_str} #{param.name}"
|
|
373
|
-
when :keyword_required
|
|
374
|
-
"#{param.name}: #{type_str}"
|
|
375
|
-
when :keyword_optional
|
|
376
|
-
"#{param.name}: ?#{type_str}"
|
|
377
|
-
when :keyword_rest
|
|
378
|
-
"**#{type_str} #{param.name}"
|
|
379
|
-
when :block
|
|
380
|
-
"&#{type_str} #{param.name}"
|
|
381
|
-
when :forwarding
|
|
382
|
-
"..."
|
|
383
|
-
else
|
|
384
|
-
"#{type_str} #{param.name}"
|
|
385
|
-
end
|
|
386
|
-
end.join(", ")
|
|
387
|
-
end
|
|
388
|
-
|
|
389
|
-
def infer_param_type(param)
|
|
390
|
-
result = @runtime_adapter.infer_type(param)
|
|
391
|
-
result.type
|
|
362
|
+
private def debug_enabled?
|
|
363
|
+
::TypeGuessr::Core::Config.debug?
|
|
392
364
|
end
|
|
393
365
|
|
|
394
|
-
def
|
|
395
|
-
Config.debug?
|
|
396
|
-
end
|
|
397
|
-
|
|
398
|
-
def build_debug_info(result, ir_node = nil)
|
|
366
|
+
private def build_debug_info(result, ir_node = nil)
|
|
399
367
|
info = "\n\n**[TypeGuessr Debug]**"
|
|
400
368
|
info += "\n\n**Reason:** #{result.reason}"
|
|
401
369
|
info += "\n\n**Source:** #{result.source}"
|
|
@@ -406,7 +374,7 @@ module RubyLsp
|
|
|
406
374
|
info
|
|
407
375
|
end
|
|
408
376
|
|
|
409
|
-
def extract_called_methods(ir_node)
|
|
377
|
+
private def extract_called_methods(ir_node)
|
|
410
378
|
case ir_node
|
|
411
379
|
when IR::LocalWriteNode, IR::LocalReadNode,
|
|
412
380
|
IR::InstanceVariableWriteNode, IR::InstanceVariableReadNode,
|
|
@@ -421,108 +389,8 @@ module RubyLsp
|
|
|
421
389
|
end
|
|
422
390
|
end
|
|
423
391
|
|
|
424
|
-
# Generate scope_id from node_context
|
|
425
|
-
# Format: "ClassName#method_name" or "ClassName" or "#method_name" or ""
|
|
426
|
-
# @param exclude_method [Boolean] Whether to exclude method from scope (for DefNode)
|
|
427
|
-
def generate_scope_id(exclude_method: false)
|
|
428
|
-
class_path = @node_context.nesting.map do |n|
|
|
429
|
-
n.is_a?(String) ? n : n.name.to_s
|
|
430
|
-
end.join("::")
|
|
431
|
-
|
|
432
|
-
method_name = exclude_method ? nil : @node_context.surrounding_method
|
|
433
|
-
|
|
434
|
-
if method_name
|
|
435
|
-
"#{class_path}##{method_name}"
|
|
436
|
-
else
|
|
437
|
-
class_path
|
|
438
|
-
end
|
|
439
|
-
end
|
|
440
|
-
|
|
441
|
-
# Generate node_hash from Prism node to match IR node_hash format
|
|
442
|
-
def generate_node_hash(node)
|
|
443
|
-
line = node.location.start_line
|
|
444
|
-
case node
|
|
445
|
-
when Prism::LocalVariableWriteNode, Prism::LocalVariableTargetNode
|
|
446
|
-
"local_write:#{node.name}:#{line}"
|
|
447
|
-
when Prism::LocalVariableReadNode
|
|
448
|
-
"local_read:#{node.name}:#{line}"
|
|
449
|
-
when Prism::InstanceVariableWriteNode, Prism::InstanceVariableTargetNode
|
|
450
|
-
"ivar_write:#{node.name}:#{line}"
|
|
451
|
-
when Prism::InstanceVariableReadNode
|
|
452
|
-
"ivar_read:#{node.name}:#{line}"
|
|
453
|
-
when Prism::ClassVariableWriteNode, Prism::ClassVariableTargetNode
|
|
454
|
-
"cvar_write:#{node.name}:#{line}"
|
|
455
|
-
when Prism::ClassVariableReadNode
|
|
456
|
-
"cvar_read:#{node.name}:#{line}"
|
|
457
|
-
when Prism::GlobalVariableWriteNode, Prism::GlobalVariableTargetNode
|
|
458
|
-
"global_write:#{node.name}:#{line}"
|
|
459
|
-
when Prism::GlobalVariableReadNode
|
|
460
|
-
"global_read:#{node.name}:#{line}"
|
|
461
|
-
when Prism::RequiredParameterNode, Prism::OptionalParameterNode, Prism::RestParameterNode,
|
|
462
|
-
Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode,
|
|
463
|
-
Prism::KeywordRestParameterNode, Prism::BlockParameterNode
|
|
464
|
-
# Check if this is a block parameter (parent is BlockParametersNode)
|
|
465
|
-
if block_parameter?(node)
|
|
466
|
-
index = block_parameter_index(node)
|
|
467
|
-
"bparam:#{index}:#{line}"
|
|
468
|
-
else
|
|
469
|
-
"param:#{node.name}:#{line}"
|
|
470
|
-
end
|
|
471
|
-
when Prism::ForwardingParameterNode
|
|
472
|
-
"param:...:#{line}"
|
|
473
|
-
when Prism::CallNode
|
|
474
|
-
# Use message_loc for accurate line number
|
|
475
|
-
call_line = node.message_loc&.start_line || line
|
|
476
|
-
"call:#{node.name}:#{call_line}"
|
|
477
|
-
when Prism::DefNode
|
|
478
|
-
# Use name_loc for accurate line number
|
|
479
|
-
def_line = node.name_loc&.start_line || line
|
|
480
|
-
"def:#{node.name}:#{def_line}"
|
|
481
|
-
when Prism::SelfNode
|
|
482
|
-
class_path = @node_context.nesting.map do |n|
|
|
483
|
-
n.is_a?(String) ? n : n.name.to_s
|
|
484
|
-
end.join("::")
|
|
485
|
-
"self:#{class_path}:#{line}"
|
|
486
|
-
end
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
# Check if a parameter node is inside a block (not a method definition)
|
|
490
|
-
def block_parameter?(node)
|
|
491
|
-
call_node = @node_context.call_node
|
|
492
|
-
return false unless call_node&.block
|
|
493
|
-
|
|
494
|
-
# Check if this parameter is in the block's parameters
|
|
495
|
-
block_params = call_node.block.parameters&.parameters
|
|
496
|
-
return false unless block_params
|
|
497
|
-
|
|
498
|
-
all_params = collect_block_params(block_params)
|
|
499
|
-
all_params.include?(node)
|
|
500
|
-
end
|
|
501
|
-
|
|
502
|
-
# Get the index of a block parameter
|
|
503
|
-
def block_parameter_index(node)
|
|
504
|
-
call_node = @node_context.call_node
|
|
505
|
-
return 0 unless call_node&.block
|
|
506
|
-
|
|
507
|
-
block_params = call_node.block.parameters&.parameters
|
|
508
|
-
return 0 unless block_params
|
|
509
|
-
|
|
510
|
-
all_params = collect_block_params(block_params)
|
|
511
|
-
all_params.index(node) || 0
|
|
512
|
-
end
|
|
513
|
-
|
|
514
|
-
# Collect all positional parameters from a ParametersNode
|
|
515
|
-
def collect_block_params(params_node)
|
|
516
|
-
all_params = []
|
|
517
|
-
all_params.concat(params_node.requireds || [])
|
|
518
|
-
all_params.concat(params_node.optionals || [])
|
|
519
|
-
all_params << params_node.rest if params_node.rest
|
|
520
|
-
all_params.concat(params_node.posts || [])
|
|
521
|
-
all_params
|
|
522
|
-
end
|
|
523
|
-
|
|
524
392
|
# Format type with definition link if available
|
|
525
|
-
def format_type_with_link(type)
|
|
393
|
+
private def format_type_with_link(type)
|
|
526
394
|
formatted = type.to_s
|
|
527
395
|
|
|
528
396
|
# Only link ClassInstance types
|
|
@@ -539,7 +407,7 @@ module RubyLsp
|
|
|
539
407
|
end
|
|
540
408
|
|
|
541
409
|
# Find entry for a type name in RubyIndexer
|
|
542
|
-
def find_type_entry(type_name)
|
|
410
|
+
private def find_type_entry(type_name)
|
|
543
411
|
return nil unless @global_state&.index
|
|
544
412
|
|
|
545
413
|
entries = @global_state.index.resolve(type_name, [])
|
|
@@ -550,7 +418,7 @@ module RubyLsp
|
|
|
550
418
|
end
|
|
551
419
|
|
|
552
420
|
# Build a location link from an entry
|
|
553
|
-
def build_location_link(entry)
|
|
421
|
+
private def build_location_link(entry)
|
|
554
422
|
uri = entry.uri
|
|
555
423
|
return nil if uri.nil?
|
|
556
424
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This file is loaded in the Rails runner process (not the LSP server)
|
|
4
|
+
# via RunnerClient.register_server_addon.
|
|
5
|
+
# RubyLsp::Rails::ServerAddon is already defined when this file is required.
|
|
6
|
+
|
|
7
|
+
module RubyLsp
|
|
8
|
+
module TypeGuessr
|
|
9
|
+
# ServerAddon that runs inside the Rails runner process.
|
|
10
|
+
# Queries ActiveRecord runtime for model metadata (columns, enums, associations, scopes).
|
|
11
|
+
class RailsServerAddon < ::RubyLsp::Rails::ServerAddon
|
|
12
|
+
def name
|
|
13
|
+
"TypeGuessr"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def execute(request, params)
|
|
17
|
+
case request
|
|
18
|
+
when "model_metadata"
|
|
19
|
+
handle_model_metadata(params)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private def handle_model_metadata(params)
|
|
24
|
+
class_name = params[:name]
|
|
25
|
+
klass = class_name.constantize
|
|
26
|
+
|
|
27
|
+
return send_error_response("#{class_name} is not an ActiveRecord model") unless active_record_model?(klass)
|
|
28
|
+
|
|
29
|
+
send_result({
|
|
30
|
+
columns: extract_columns(klass),
|
|
31
|
+
enums: extract_enums(klass),
|
|
32
|
+
associations: extract_associations(klass),
|
|
33
|
+
scopes: extract_scopes(klass)
|
|
34
|
+
})
|
|
35
|
+
rescue NameError
|
|
36
|
+
send_error_response("#{class_name} is not a valid class")
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
send_error_response("#{e.class}: #{e.message}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private def active_record_model?(klass)
|
|
42
|
+
klass < ::ActiveRecord::Base
|
|
43
|
+
rescue StandardError
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private def extract_columns(klass)
|
|
48
|
+
klass.columns.map do |col|
|
|
49
|
+
[col.name, col.type.to_s, col.null]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private def extract_enums(klass)
|
|
54
|
+
return {} unless klass.respond_to?(:defined_enums)
|
|
55
|
+
|
|
56
|
+
klass.defined_enums
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private def extract_associations(klass)
|
|
60
|
+
klass.reflect_on_all_associations.map do |assoc|
|
|
61
|
+
{
|
|
62
|
+
name: assoc.name.to_s,
|
|
63
|
+
macro: assoc.macro.to_s,
|
|
64
|
+
class_name: assoc.class_name
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
rescue StandardError
|
|
68
|
+
[]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private def extract_scopes(klass)
|
|
72
|
+
enum_values = klass.respond_to?(:defined_enums) ? klass.defined_enums.values.flat_map(&:keys) : []
|
|
73
|
+
exclude = Set.new(enum_values + enum_values.map { |v| "not_#{v}" })
|
|
74
|
+
|
|
75
|
+
klass.singleton_methods(false)
|
|
76
|
+
.map(&:to_s)
|
|
77
|
+
.reject { |m| exclude.include?(m) || m.start_with?("find_by_", "create_", "build_") }
|
|
78
|
+
rescue StandardError
|
|
79
|
+
[]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|