type-guessr 0.0.2 → 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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/lib/ruby_lsp/type_guessr/addon.rb +4 -5
  4. data/lib/ruby_lsp/type_guessr/code_index_adapter.rb +17 -0
  5. data/lib/ruby_lsp/type_guessr/{graph_builder.rb → debug_graph_builder.rb} +3 -3
  6. data/lib/ruby_lsp/type_guessr/debug_server.rb +2 -2
  7. data/lib/ruby_lsp/type_guessr/dsl/activerecord_adapter.rb +404 -0
  8. data/lib/ruby_lsp/type_guessr/dsl/ar_schema_watcher.rb +96 -0
  9. data/lib/ruby_lsp/type_guessr/dsl/ar_type_mapper.rb +51 -0
  10. data/lib/ruby_lsp/type_guessr/dsl.rb +3 -0
  11. data/lib/ruby_lsp/type_guessr/dsl_type_registrar.rb +60 -0
  12. data/lib/ruby_lsp/type_guessr/hover.rb +46 -40
  13. data/lib/ruby_lsp/type_guessr/rails_server_addon.rb +83 -0
  14. data/lib/ruby_lsp/type_guessr/runtime_adapter.rb +90 -16
  15. data/lib/type-guessr.rb +2 -13
  16. data/lib/type_guessr/core/cache/gem_signature_cache.rb +3 -2
  17. data/lib/type_guessr/core/cache.rb +5 -0
  18. data/lib/{ruby_lsp/type_guessr → type_guessr/core}/config.rb +2 -2
  19. data/lib/type_guessr/core/converter/call_converter.rb +161 -0
  20. data/lib/type_guessr/core/converter/container_mutation_converter.rb +241 -0
  21. data/lib/type_guessr/core/converter/context.rb +144 -0
  22. data/lib/type_guessr/core/converter/control_flow_converter.rb +425 -0
  23. data/lib/type_guessr/core/converter/definition_converter.rb +246 -0
  24. data/lib/type_guessr/core/converter/literal_converter.rb +217 -0
  25. data/lib/type_guessr/core/converter/prism_converter.rb +9 -1682
  26. data/lib/type_guessr/core/converter/rbs_converter.rb +15 -1
  27. data/lib/type_guessr/core/converter/registration.rb +100 -0
  28. data/lib/type_guessr/core/converter/variable_converter.rb +225 -0
  29. data/lib/type_guessr/core/converter.rb +4 -0
  30. data/lib/type_guessr/core/index.rb +3 -0
  31. data/lib/type_guessr/core/inference/resolver.rb +206 -208
  32. data/lib/type_guessr/core/inference.rb +4 -0
  33. data/lib/type_guessr/core/ir.rb +3 -0
  34. data/lib/type_guessr/core/logger.rb +3 -5
  35. data/lib/type_guessr/core/registry/method_registry.rb +9 -0
  36. data/lib/type_guessr/core/registry/signature_registry.rb +73 -16
  37. data/lib/type_guessr/core/registry.rb +6 -0
  38. data/lib/type_guessr/core/type_serializer.rb +18 -14
  39. data/lib/type_guessr/core/type_simplifier.rb +5 -5
  40. data/lib/type_guessr/core/types.rb +64 -22
  41. data/lib/type_guessr/core.rb +29 -0
  42. data/lib/type_guessr/mcp/server.rb +55 -46
  43. data/lib/type_guessr/mcp/standalone_runtime.rb +70 -110
  44. data/lib/type_guessr/version.rb +1 -1
  45. metadata +25 -5
  46. data/.mcp.json +0 -9
@@ -8,8 +8,6 @@ module TypeGuessr
8
8
  # Provides type inference, method signature lookup, and method search
9
9
  # for use by the MCP server. All public query methods are thread-safe.
10
10
  class StandaloneRuntime
11
- NodeContextHelper = Core::NodeContextHelper
12
-
13
11
  # @param converter [Core::Converter::PrismConverter]
14
12
  # @param location_index [Core::Index::LocationIndex]
15
13
  # @param signature_registry [Core::Registry::SignatureRegistry]
@@ -41,6 +39,7 @@ module TypeGuessr
41
39
 
42
40
  @mutex.synchronize do
43
41
  @location_index.remove_file(file_path)
42
+ @method_registry.remove_file(file_path)
44
43
  @resolver.clear_cache
45
44
 
46
45
  context = Core::Converter::PrismConverter::Context.new(
@@ -62,6 +61,7 @@ module TypeGuessr
62
61
  def remove_indexed_file(file_path)
63
62
  @mutex.synchronize do
64
63
  @location_index.remove_file(file_path)
64
+ @method_registry.remove_file(file_path)
65
65
  @resolver.clear_cache
66
66
  end
67
67
  end
@@ -87,38 +87,6 @@ module TypeGuessr
87
87
  @signature_registry.preload
88
88
  end
89
89
 
90
- # Infer type at a specific file location
91
- # @param file_path [String] Absolute path to the Ruby file
92
- # @param line [Integer] Line number (1-based)
93
- # @param column [Integer] Column number (0-based)
94
- # @return [Hash] Inference result with :type, :reason, :node_type keys, or :error on failure
95
- def infer_at(file_path, line, column)
96
- file_path = File.expand_path(file_path)
97
-
98
- source = File.read(file_path)
99
- parse_result = Prism.parse_lex(source)
100
- return { error: "Failed to parse file" } unless parse_result.value
101
-
102
- ast = parse_result.value.first
103
- code_units_cache = parse_result.code_units_cache(Encoding::UTF_8)
104
-
105
- char_position = line_column_to_offset(source, line, column)
106
- return { error: "Invalid line/column" } unless char_position
107
-
108
- node_context = RubyLsp::RubyDocument.locate(
109
- ast,
110
- char_position,
111
- code_units_cache: code_units_cache
112
- )
113
-
114
- prism_node = node_context.node
115
- return { error: "No node found at position" } unless prism_node
116
-
117
- infer_from_prism_node(prism_node, node_context)
118
- rescue StandardError => e
119
- { error: e.message, backtrace: e.backtrace&.first(3) }
120
- end
121
-
122
90
  # Get method signature for a class#method
123
91
  # @param class_name [String] Fully qualified class name (e.g., "User", "Admin::User")
124
92
  # @param method_name [String] Method name (e.g., "save", "initialize")
@@ -132,7 +100,9 @@ module TypeGuessr
132
100
  if entry.is_a?(Core::Registry::SignatureRegistry::MethodEntry)
133
101
  return {
134
102
  source: "rbs",
135
- signatures: entry.signature_strings
103
+ signatures: entry.signature_strings,
104
+ class_name: class_name,
105
+ method_name: method_name
136
106
  }
137
107
  end
138
108
 
@@ -145,7 +115,8 @@ module TypeGuessr
145
115
  }
146
116
  end
147
117
 
148
- return { error: "Method not found: #{class_name}##{method_name}" }
118
+ return { error: "Method not found: #{class_name}##{method_name}", class_name: class_name,
119
+ method_name: method_name }
149
120
  end
150
121
 
151
122
  sig = @mutex.synchronize { @signature_builder.build_from_def_node(def_node) }
@@ -156,98 +127,87 @@ module TypeGuessr
156
127
  method_name: method_name
157
128
  }
158
129
  rescue StandardError => e
159
- { error: e.message }
130
+ { error: e.message, class_name: class_name, method_name: method_name }
131
+ end
132
+
133
+ # Get signatures for multiple methods in one call
134
+ # @param methods [Array<Hash>] Array of { class_name:, method_name: } hashes
135
+ # @return [Array<Hash>] Array of signature results (same format as method_signature)
136
+ def method_signatures(methods)
137
+ methods.map do |entry|
138
+ method_signature(entry[:class_name], entry[:method_name])
139
+ end
140
+ end
141
+
142
+ # Get source code for a single method
143
+ # @param class_name [String] Fully qualified class name
144
+ # @param method_name [String] Method name
145
+ # @return [Hash] Source result with :source, :file_path, :line keys, or :error on failure
146
+ def method_source(class_name, method_name)
147
+ def_node = @mutex.synchronize { @method_registry.lookup(class_name, method_name) }
148
+ unless def_node
149
+ return { error: "Method not found: #{class_name}##{method_name}",
150
+ class_name: class_name, method_name: method_name }
151
+ end
152
+
153
+ file_path = @mutex.synchronize { @method_registry.source_file_for(class_name, method_name) }
154
+ unless file_path
155
+ return { error: "Source file not found: #{class_name}##{method_name}",
156
+ class_name: class_name, method_name: method_name }
157
+ end
158
+
159
+ source = File.read(file_path)
160
+ prism_result = Prism.parse(source)
161
+ node_context = RubyLsp::RubyDocument.locate(
162
+ prism_result.value, def_node.loc,
163
+ code_units_cache: prism_result.code_units_cache(Encoding::UTF_8)
164
+ )
165
+ prism_def = node_context.node.is_a?(Prism::DefNode) ? node_context.node : node_context.parent
166
+
167
+ {
168
+ class_name: class_name,
169
+ method_name: method_name,
170
+ source: prism_def.slice,
171
+ file_path: file_path,
172
+ line: prism_def.location.start_line
173
+ }
174
+ rescue StandardError => e
175
+ { error: e.message, class_name: class_name, method_name: method_name }
176
+ end
177
+
178
+ # Get source code for multiple methods in one call
179
+ # @param methods [Array<Hash>] Array of { class_name:, method_name: } hashes
180
+ # @return [Array<Hash>] Array of source results (same format as method_source)
181
+ def method_sources(methods)
182
+ methods.map do |entry|
183
+ method_source(entry[:class_name], entry[:method_name])
184
+ end
160
185
  end
161
186
 
162
187
  # Search for methods matching a query pattern
163
188
  # @param query [String] Search query (e.g., "User#save", "save", "initialize")
189
+ # @param include_signatures [Boolean] When true, include inferred signature for each result
164
190
  # @return [Array<Hash>] Array of matching methods with :class_name, :method_name, :full_name, :location
165
- def search_methods(query)
191
+ def search_methods(query, include_signatures: false)
166
192
  @mutex.synchronize do
167
193
  results = @method_registry.search(query)
168
194
  results.map do |class_name, method_name, def_node|
169
- {
195
+ entry = {
170
196
  class_name: class_name,
171
197
  method_name: method_name,
172
198
  full_name: "#{class_name}##{method_name}",
173
199
  location: def_node.loc ? { offset: def_node.loc } : nil
174
200
  }
201
+ if include_signatures
202
+ sig = @signature_builder.build_from_def_node(def_node)
203
+ entry[:signature] = sig.to_s
204
+ end
205
+ entry
175
206
  end
176
207
  end
177
208
  rescue StandardError => e
178
209
  { error: e.message }
179
210
  end
180
-
181
- private
182
-
183
- # Resolve a Prism AST node to a type inference result
184
- # @param prism_node [Prism::Node] The AST node at the cursor position
185
- # @param node_context [RubyLsp::NodeContext] Context from RubyDocument.locate
186
- # @return [Hash] Inference result
187
- private def infer_from_prism_node(prism_node, node_context)
188
- exclude_method = prism_node.is_a?(Prism::DefNode)
189
- scope_id = NodeContextHelper.generate_scope_id(node_context, exclude_method: exclude_method)
190
- node_hash = NodeContextHelper.generate_node_hash(prism_node, node_context)
191
- return { error: "Unsupported node type: #{prism_node.class}" } unless node_hash
192
-
193
- node_key = "#{scope_id}:#{node_hash}"
194
-
195
- ir_node = @mutex.synchronize { @location_index.find_by_key(node_key) }
196
- return { error: "Node not indexed", node_key: node_key, node_type: prism_node.class.name } unless ir_node
197
-
198
- infer_from_ir_node(ir_node)
199
- end
200
-
201
- # Infer type from an IR node, dispatching by node type
202
- # @param ir_node [Core::IR::Node] The indexed IR node
203
- # @return [Hash] Inference result
204
- private def infer_from_ir_node(ir_node)
205
- if ir_node.is_a?(Core::IR::DefNode)
206
- sig = @mutex.synchronize { @signature_builder.build_from_def_node(ir_node) }
207
- return {
208
- type: "method_signature",
209
- signature: sig.to_s,
210
- node_type: "DefNode"
211
- }
212
- end
213
-
214
- result = @mutex.synchronize { @resolver.infer(ir_node) }
215
-
216
- if ir_node.is_a?(Core::IR::CallNode)
217
- {
218
- type: result.type.to_s,
219
- method: ir_node.method.to_s,
220
- reason: result.reason,
221
- node_type: "CallNode"
222
- }
223
- else
224
- {
225
- type: result.type.to_s,
226
- reason: result.reason,
227
- node_type: ir_node.class.name.split("::").last
228
- }
229
- end
230
- end
231
-
232
- # Convert 1-based line / 0-based column to byte offset
233
- # @param source [String] Source code
234
- # @param line [Integer] Line number (1-based)
235
- # @param column [Integer] Column number (0-based)
236
- # @return [Integer, nil] Byte offset, or nil if line/column is out of bounds
237
- private def line_column_to_offset(source, line, column)
238
- current_line = 1
239
- current_offset = 0
240
-
241
- source.each_char do |char|
242
- return current_offset + column if current_line == line
243
-
244
- current_line += 1 if char == "\n"
245
- current_offset += char.bytesize
246
- end
247
-
248
- # Last line (no trailing newline)
249
- current_offset + column if current_line == line
250
- end
251
211
  end
252
212
  end
253
213
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TypeGuessr
4
- VERSION = "0.0.2"
4
+ VERSION = "0.0.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: type-guessr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - riseshia
@@ -45,32 +45,52 @@ executables:
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
- - ".mcp.json"
49
48
  - LICENSE
50
49
  - README.md
51
50
  - exe/type-guessr
52
51
  - lib/ruby_lsp/type_guessr/addon.rb
53
52
  - lib/ruby_lsp/type_guessr/code_index_adapter.rb
54
- - lib/ruby_lsp/type_guessr/config.rb
55
53
  - lib/ruby_lsp/type_guessr/constants.rb
54
+ - lib/ruby_lsp/type_guessr/debug_graph_builder.rb
56
55
  - lib/ruby_lsp/type_guessr/debug_server.rb
57
- - lib/ruby_lsp/type_guessr/graph_builder.rb
56
+ - lib/ruby_lsp/type_guessr/dsl.rb
57
+ - lib/ruby_lsp/type_guessr/dsl/activerecord_adapter.rb
58
+ - lib/ruby_lsp/type_guessr/dsl/ar_schema_watcher.rb
59
+ - lib/ruby_lsp/type_guessr/dsl/ar_type_mapper.rb
60
+ - lib/ruby_lsp/type_guessr/dsl_type_registrar.rb
58
61
  - lib/ruby_lsp/type_guessr/hover.rb
62
+ - lib/ruby_lsp/type_guessr/rails_server_addon.rb
59
63
  - lib/ruby_lsp/type_guessr/runtime_adapter.rb
60
64
  - lib/ruby_lsp/type_guessr/type_inferrer.rb
61
65
  - lib/type-guessr.rb
66
+ - lib/type_guessr/core.rb
67
+ - lib/type_guessr/core/cache.rb
62
68
  - lib/type_guessr/core/cache/gem_dependency_resolver.rb
63
69
  - lib/type_guessr/core/cache/gem_signature_cache.rb
64
70
  - lib/type_guessr/core/cache/gem_signature_extractor.rb
71
+ - lib/type_guessr/core/config.rb
72
+ - lib/type_guessr/core/converter.rb
73
+ - lib/type_guessr/core/converter/call_converter.rb
74
+ - lib/type_guessr/core/converter/container_mutation_converter.rb
75
+ - lib/type_guessr/core/converter/context.rb
76
+ - lib/type_guessr/core/converter/control_flow_converter.rb
77
+ - lib/type_guessr/core/converter/definition_converter.rb
78
+ - lib/type_guessr/core/converter/literal_converter.rb
65
79
  - lib/type_guessr/core/converter/prism_converter.rb
66
80
  - lib/type_guessr/core/converter/rbs_converter.rb
81
+ - lib/type_guessr/core/converter/registration.rb
82
+ - lib/type_guessr/core/converter/variable_converter.rb
83
+ - lib/type_guessr/core/index.rb
67
84
  - lib/type_guessr/core/index/location_index.rb
85
+ - lib/type_guessr/core/inference.rb
68
86
  - lib/type_guessr/core/inference/resolver.rb
69
87
  - lib/type_guessr/core/inference/result.rb
88
+ - lib/type_guessr/core/ir.rb
70
89
  - lib/type_guessr/core/ir/nodes.rb
71
90
  - lib/type_guessr/core/logger.rb
72
91
  - lib/type_guessr/core/node_context_helper.rb
73
92
  - lib/type_guessr/core/node_key_generator.rb
93
+ - lib/type_guessr/core/registry.rb
74
94
  - lib/type_guessr/core/registry/class_variable_registry.rb
75
95
  - lib/type_guessr/core/registry/instance_variable_registry.rb
76
96
  - lib/type_guessr/core/registry/method_registry.rb
@@ -104,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
124
  - !ruby/object:Gem::Version
105
125
  version: '0'
106
126
  requirements: []
107
- rubygems_version: 3.6.9
127
+ rubygems_version: 4.0.3
108
128
  specification_version: 4
109
129
  summary: A heuristic type inference tool for Ruby
110
130
  test_files: []
data/.mcp.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "type-guessr": {
4
- "command": "ruby",
5
- "args": ["exe/type-guessr", "mcp", "."]
6
- }
7
- }
8
- }
9
-