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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -0
  3. data/exe/type-guessr +30 -0
  4. data/lib/ruby_lsp/type_guessr/addon.rb +20 -45
  5. data/lib/ruby_lsp/type_guessr/code_index_adapter.rb +352 -0
  6. data/lib/ruby_lsp/type_guessr/constants.rb +39 -0
  7. data/lib/ruby_lsp/type_guessr/{graph_builder.rb → debug_graph_builder.rb} +27 -22
  8. data/lib/ruby_lsp/type_guessr/debug_server.rb +20 -17
  9. data/lib/ruby_lsp/type_guessr/dsl/activerecord_adapter.rb +404 -0
  10. data/lib/ruby_lsp/type_guessr/dsl/ar_schema_watcher.rb +96 -0
  11. data/lib/ruby_lsp/type_guessr/dsl/ar_type_mapper.rb +51 -0
  12. data/lib/ruby_lsp/type_guessr/dsl.rb +3 -0
  13. data/lib/ruby_lsp/type_guessr/dsl_type_registrar.rb +60 -0
  14. data/lib/ruby_lsp/type_guessr/hover.rb +129 -261
  15. data/lib/ruby_lsp/type_guessr/rails_server_addon.rb +83 -0
  16. data/lib/ruby_lsp/type_guessr/runtime_adapter.rb +613 -277
  17. data/lib/ruby_lsp/type_guessr/type_inferrer.rb +8 -105
  18. data/lib/type-guessr.rb +3 -11
  19. data/lib/type_guessr/core/cache/gem_dependency_resolver.rb +113 -0
  20. data/lib/type_guessr/core/cache/gem_signature_cache.rb +98 -0
  21. data/lib/type_guessr/core/cache/gem_signature_extractor.rb +87 -0
  22. data/lib/type_guessr/core/cache.rb +5 -0
  23. data/lib/{ruby_lsp/type_guessr → type_guessr/core}/config.rb +19 -34
  24. data/lib/type_guessr/core/converter/call_converter.rb +161 -0
  25. data/lib/type_guessr/core/converter/container_mutation_converter.rb +241 -0
  26. data/lib/type_guessr/core/converter/context.rb +144 -0
  27. data/lib/type_guessr/core/converter/control_flow_converter.rb +425 -0
  28. data/lib/type_guessr/core/converter/definition_converter.rb +246 -0
  29. data/lib/type_guessr/core/converter/literal_converter.rb +217 -0
  30. data/lib/type_guessr/core/converter/prism_converter.rb +154 -1613
  31. data/lib/type_guessr/core/converter/rbs_converter.rb +35 -14
  32. data/lib/type_guessr/core/converter/registration.rb +100 -0
  33. data/lib/type_guessr/core/converter/variable_converter.rb +225 -0
  34. data/lib/type_guessr/core/converter.rb +4 -0
  35. data/lib/type_guessr/core/index/location_index.rb +32 -0
  36. data/lib/type_guessr/core/index.rb +3 -0
  37. data/lib/type_guessr/core/inference/resolver.rb +516 -349
  38. data/lib/type_guessr/core/inference.rb +4 -0
  39. data/lib/type_guessr/core/ir/nodes.rb +362 -103
  40. data/lib/type_guessr/core/ir.rb +3 -0
  41. data/lib/type_guessr/core/logger.rb +6 -13
  42. data/lib/type_guessr/core/node_context_helper.rb +126 -0
  43. data/lib/type_guessr/core/node_key_generator.rb +31 -0
  44. data/lib/type_guessr/core/registry/class_variable_registry.rb +63 -0
  45. data/lib/type_guessr/core/registry/instance_variable_registry.rb +84 -0
  46. data/lib/type_guessr/core/registry/method_registry.rb +65 -38
  47. data/lib/type_guessr/core/registry/signature_registry.rb +543 -0
  48. data/lib/type_guessr/core/registry.rb +6 -0
  49. data/lib/type_guessr/core/signature_builder.rb +39 -0
  50. data/lib/type_guessr/core/type_serializer.rb +96 -0
  51. data/lib/type_guessr/core/type_simplifier.rb +15 -12
  52. data/lib/type_guessr/core/types.rb +250 -32
  53. data/lib/type_guessr/core.rb +29 -0
  54. data/lib/type_guessr/mcp/file_watcher.rb +87 -0
  55. data/lib/type_guessr/mcp/server.rb +463 -0
  56. data/lib/type_guessr/mcp/standalone_runtime.rb +213 -0
  57. data/lib/type_guessr/version.rb +1 -1
  58. metadata +57 -8
  59. data/lib/type_guessr/core/rbs_provider.rb +0 -304
  60. data/lib/type_guessr/core/registry/variable_registry.rb +0 -87
  61. data/lib/type_guessr/core/signature_provider.rb +0 -101
@@ -4,7 +4,7 @@ module RubyLsp
4
4
  module TypeGuessr
5
5
  # Builds graph data from IR nodes for visualization
6
6
  # Uses body_nodes structure with value/receiver/args edges
7
- class GraphBuilder
7
+ class DebugGraphBuilder
8
8
  def initialize(runtime_adapter)
9
9
  @runtime_adapter = runtime_adapter
10
10
  end
@@ -41,9 +41,7 @@ module RubyLsp
41
41
  # Limit to MAX_NODES to prevent infinite/huge graphs
42
42
  MAX_NODES = 200
43
43
 
44
- private
45
-
46
- def build_def_node_graph(def_node, def_key)
44
+ private def build_def_node_graph(def_node, def_key)
47
45
  method_scope = @scope_id.empty? ? "##{def_node.name}" : "#{@scope_id}##{def_node.name}"
48
46
 
49
47
  # 1. Add DefNode
@@ -72,7 +70,7 @@ module RubyLsp
72
70
  @nodes[return_key] = {
73
71
  key: return_key,
74
72
  type: "Return",
75
- line: def_node.loc&.line,
73
+ offset: def_node.loc,
76
74
  inferred_type: infer_type_str(def_node),
77
75
  details: { virtual: true }
78
76
  }
@@ -84,7 +82,7 @@ module RubyLsp
84
82
  add_edge(return_key, def_key)
85
83
  end
86
84
 
87
- def process_body_node(node, scope_id, return_sources)
85
+ private def process_body_node(node, scope_id, return_sources)
88
86
  return unless node
89
87
  return if @nodes.size >= MAX_NODES
90
88
 
@@ -137,6 +135,11 @@ module RubyLsp
137
135
  branch_key = process_body_node(branch, scope_id, return_sources)
138
136
  add_edge(branch_key, node_key) if branch_key
139
137
  end
138
+ when ::TypeGuessr::Core::IR::OrNode
139
+ [node.lhs, node.rhs].each do |child|
140
+ child_key = process_body_node(child, scope_id, return_sources)
141
+ add_edge(child_key, node_key) if child_key
142
+ end
140
143
  when ::TypeGuessr::Core::IR::LiteralNode
141
144
  # Process internal values (for Hash/Array literals with expressions) # -- values is an Array attribute, not Hash#values
142
145
  node.values&.each do |value_node|
@@ -149,7 +152,7 @@ module RubyLsp
149
152
  node_key
150
153
  end
151
154
 
152
- def add_node(node, scope_id = @scope_id)
155
+ private def add_node(node, scope_id = @scope_id)
153
156
  node_key = node.node_key(scope_id)
154
157
  return node_key if @nodes.key?(node_key)
155
158
 
@@ -157,20 +160,20 @@ module RubyLsp
157
160
  node_key
158
161
  end
159
162
 
160
- def add_edge(from_key, to_key)
163
+ private def add_edge(from_key, to_key)
161
164
  return unless from_key && to_key
162
165
 
163
166
  edge = { from: from_key, to: to_key }
164
167
  @edges << edge unless @edges.include?(edge)
165
168
  end
166
169
 
167
- def infer_type_str(node)
170
+ private def infer_type_str(node)
168
171
  result = @runtime_adapter.infer_type(node)
169
172
  result.type.to_s
170
173
  end
171
174
 
172
175
  # BFS traversal for non-DefNode (fallback)
173
- def traverse_dependencies(node, node_key)
176
+ private def traverse_dependencies(node, node_key)
174
177
  visited = Set.new
175
178
  queue = [[node, node_key]]
176
179
 
@@ -193,12 +196,12 @@ module RubyLsp
193
196
 
194
197
  # Extract scope_id from a node key
195
198
  # Key format: {scope_id}:{type}:{name}:{line}
196
- def extract_scope_id(node_key)
199
+ private def extract_scope_id(node_key)
197
200
  # For root key like "Class:def:name:line", scope is "Class"
198
201
  # For "Class#method:type:name:line", scope is "Class#method"
199
202
  # Find the last occurrence of known type prefixes
200
- type_prefixes = %w[def: param: call: lit: local_write: local_read: ivar_write: ivar_read: cvar_write: cvar_read: merge: const: bparam: return: class:
201
- self:]
203
+ type_prefixes = %w[def: param: call: lit: local_write: local_read: ivar_write: ivar_read:
204
+ cvar_write: cvar_read: merge: or: const: bparam: return: class: self:]
202
205
  last_type_pos = nil
203
206
 
204
207
  type_prefixes.each do |prefix|
@@ -226,27 +229,27 @@ module RubyLsp
226
229
  end
227
230
 
228
231
  # Serialize a node to hash format
229
- def serialize_node(node, node_key)
230
- warn("[GraphBuilder] serialize_node: #{node_key}") if Config.debug?
232
+ private def serialize_node(node, node_key)
233
+ warn("[DebugGraphBuilder] serialize_node: #{node_key}") if ::TypeGuessr::Core::Config.debug?
231
234
  result = @runtime_adapter.infer_type(node)
232
- warn("[GraphBuilder] infer_type done for: #{node_key}") if Config.debug?
235
+ warn("[DebugGraphBuilder] infer_type done for: #{node_key}") if ::TypeGuessr::Core::Config.debug?
233
236
 
234
237
  {
235
238
  key: node_key,
236
239
  type: node_type_name(node),
237
- line: node.loc&.line,
240
+ offset: node.loc,
238
241
  inferred_type: result.type.to_s,
239
242
  details: extract_details(node, node_key)
240
243
  }
241
244
  end
242
245
 
243
246
  # Get the short type name for a node
244
- def node_type_name(node)
245
- node.class.name.split("::").last
247
+ private def node_type_name(node)
248
+ ::TypeGuessr::Core::IR.extract_last_name(node.class.name)
246
249
  end
247
250
 
248
251
  # Extract details based on node type
249
- def extract_details(node, node_key)
252
+ private def extract_details(node, node_key)
250
253
  case node
251
254
  when ::TypeGuessr::Core::IR::DefNode
252
255
  # Build full method signature with param types
@@ -288,6 +291,8 @@ module RubyLsp
288
291
  { literal_type: node.type.to_s }
289
292
  when ::TypeGuessr::Core::IR::MergeNode
290
293
  { branches_count: node.branches.size }
294
+ when ::TypeGuessr::Core::IR::OrNode
295
+ { kind: "or" }
291
296
  when ::TypeGuessr::Core::IR::ConstantNode
292
297
  { name: node.name.to_s }
293
298
  when ::TypeGuessr::Core::IR::BlockParamSlot
@@ -302,7 +307,7 @@ module RubyLsp
302
307
  end
303
308
 
304
309
  # Format receiver node for display
305
- def format_receiver(receiver)
310
+ private def format_receiver(receiver)
306
311
  return nil unless receiver
307
312
 
308
313
  case receiver
@@ -321,7 +326,7 @@ module RubyLsp
321
326
 
322
327
  # Infer node key for a dependency
323
328
  # Uses parent node info to determine correct scope for children
324
- def infer_dependency_key(dep_node, parent_key, parent_node = nil)
329
+ private def infer_dependency_key(dep_node, parent_key, parent_node = nil)
325
330
  # Extract scope_id from parent key
326
331
  # Key format: {scope_id}:{type}:{name}:{line}
327
332
  # scope_id can contain :: for namespaces and # for methods
@@ -3,7 +3,7 @@
3
3
  require "socket"
4
4
  require "json"
5
5
  require "cgi"
6
- require_relative "graph_builder"
6
+ require_relative "debug_graph_builder"
7
7
 
8
8
  module RubyLsp
9
9
  module TypeGuessr
@@ -16,7 +16,7 @@ module RubyLsp
16
16
  def initialize(global_state, runtime_adapter, port: DEFAULT_PORT)
17
17
  @global_state = global_state
18
18
  @runtime_adapter = runtime_adapter
19
- @graph_builder = GraphBuilder.new(runtime_adapter)
19
+ @graph_builder = DebugGraphBuilder.new(runtime_adapter)
20
20
  @port = port
21
21
  @server = nil
22
22
  @thread = nil
@@ -38,9 +38,7 @@ module RubyLsp
38
38
  @server = nil
39
39
  end
40
40
 
41
- private
42
-
43
- def run_server
41
+ private def run_server
44
42
  @server = TCPServer.new("127.0.0.1", @port)
45
43
  warn("[TypeGuessr DebugServer] Listening on 127.0.0.1:#{@port}")
46
44
 
@@ -63,7 +61,7 @@ module RubyLsp
63
61
  warn("[TypeGuessr DebugServer] #{e.backtrace&.first(5)&.join("\n")}")
64
62
  end
65
63
 
66
- def handle_request(client)
64
+ private def handle_request(client)
67
65
  request_line = client.gets
68
66
  return client.close if request_line.nil?
69
67
 
@@ -85,7 +83,7 @@ module RubyLsp
85
83
  client.close
86
84
  end
87
85
 
88
- def parse_query_string(query_string)
86
+ private def parse_query_string(query_string)
89
87
  return {} unless query_string
90
88
 
91
89
  query_string.split("&").to_h do |pair|
@@ -94,7 +92,7 @@ module RubyLsp
94
92
  end
95
93
  end
96
94
 
97
- def route_request(path, params)
95
+ private def route_request(path, params)
98
96
  case path
99
97
  when "/"
100
98
  index_page
@@ -111,7 +109,7 @@ module RubyLsp
111
109
  end
112
110
  end
113
111
 
114
- def send_response(client, response)
112
+ private def send_response(client, response)
115
113
  client.print "HTTP/1.1 #{response[:status]}\r\n"
116
114
  client.print "Content-Type: #{response[:content_type]}\r\n"
117
115
  client.print "Content-Length: #{response[:body].bytesize}\r\n"
@@ -122,14 +120,14 @@ module RubyLsp
122
120
 
123
121
  # API Endpoints
124
122
 
125
- def search_api(query)
123
+ private def search_api(query)
126
124
  return json_response({ query: query, results: [] }) if query.empty?
127
125
 
128
126
  results = @runtime_adapter.search_project_methods(query)
129
127
  json_response({ query: query, results: results })
130
128
  end
131
129
 
132
- def keys_api(query)
130
+ private def keys_api(query)
133
131
  all_keys = @runtime_adapter.instance_variable_get(:@location_index)
134
132
  .instance_variable_get(:@key_index).keys
135
133
  keys = if query.empty?
@@ -140,7 +138,7 @@ module RubyLsp
140
138
  json_response({ query: query, total: all_keys.size, keys: keys })
141
139
  end
142
140
 
143
- def graph_api(node_key)
141
+ private def graph_api(node_key)
144
142
  return json_error("node_key parameter required", 400) if node_key.empty?
145
143
 
146
144
  begin
@@ -164,7 +162,7 @@ module RubyLsp
164
162
  end
165
163
  end
166
164
 
167
- def json_response(data)
165
+ private def json_response(data)
168
166
  {
169
167
  status: "200 OK",
170
168
  content_type: "application/json; charset=utf-8",
@@ -172,7 +170,7 @@ module RubyLsp
172
170
  }
173
171
  end
174
172
 
175
- def json_error(message, status_code)
173
+ private def json_error(message, status_code)
176
174
  status_text = status_code == 404 ? "Not Found" : "Bad Request"
177
175
  {
178
176
  status: "#{status_code} #{status_text}",
@@ -181,7 +179,7 @@ module RubyLsp
181
179
  }
182
180
  end
183
181
 
184
- def not_found
182
+ private def not_found
185
183
  {
186
184
  status: "404 Not Found",
187
185
  content_type: "text/html; charset=utf-8",
@@ -191,7 +189,7 @@ module RubyLsp
191
189
 
192
190
  # HTML Pages
193
191
 
194
- def index_page
192
+ private def index_page
195
193
  {
196
194
  status: "200 OK",
197
195
  content_type: "text/html; charset=utf-8",
@@ -337,7 +335,7 @@ module RubyLsp
337
335
  }
338
336
  end
339
337
 
340
- def graph_page(node_key)
338
+ private def graph_page(node_key)
341
339
  return not_found if node_key.empty?
342
340
 
343
341
  {
@@ -808,6 +806,10 @@ module RubyLsp
808
806
  return escapeForMermaid(`Merge (${d.branches_count} branches)\\n(L${node.line})`);
809
807
  }
810
808
 
809
+ if (node.type === 'OrNode') {
810
+ return escapeForMermaid(`Or (||)\\n(L${node.line})`);
811
+ }
812
+
811
813
  // Default format for other nodes
812
814
  let label = node.type;
813
815
  if (d.name) label += `: ${d.name}`;
@@ -838,6 +840,7 @@ module RubyLsp
838
840
  ParamNode: 'paramNode',
839
841
  LiteralNode: 'literalNode',
840
842
  MergeNode: 'mergeNode',
843
+ OrNode: 'mergeNode',
841
844
  BlockParamSlot: 'blockParamNode'
842
845
  };
843
846
  return styles[nodeType] || 'otherNode';