type-guessr 0.0.2 → 0.0.4
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 +3 -3
- data/lib/ruby_lsp/type_guessr/addon.rb +4 -5
- data/lib/ruby_lsp/type_guessr/code_index_adapter.rb +18 -1
- data/lib/ruby_lsp/type_guessr/{graph_builder.rb → debug_graph_builder.rb} +3 -3
- data/lib/ruby_lsp/type_guessr/debug_server.rb +2 -2
- 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 +46 -40
- data/lib/ruby_lsp/type_guessr/rails_server_addon.rb +83 -0
- data/lib/ruby_lsp/type_guessr/runtime_adapter.rb +90 -16
- data/lib/type-guessr.rb +2 -13
- data/lib/type_guessr/core/cache/gem_signature_cache.rb +3 -2
- data/lib/type_guessr/core/cache.rb +5 -0
- data/lib/{ruby_lsp/type_guessr → type_guessr/core}/config.rb +2 -2
- 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 +312 -0
- data/lib/type_guessr/core/converter/literal_converter.rb +217 -0
- data/lib/type_guessr/core/converter/prism_converter.rb +9 -1682
- data/lib/type_guessr/core/converter/rbs_converter.rb +15 -1
- 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.rb +3 -0
- data/lib/type_guessr/core/inference/resolver.rb +206 -208
- data/lib/type_guessr/core/inference.rb +4 -0
- data/lib/type_guessr/core/ir.rb +3 -0
- data/lib/type_guessr/core/logger.rb +3 -5
- data/lib/type_guessr/core/registry/method_registry.rb +9 -0
- data/lib/type_guessr/core/registry/signature_registry.rb +73 -16
- data/lib/type_guessr/core/registry.rb +6 -0
- data/lib/type_guessr/core/type_serializer.rb +18 -14
- data/lib/type_guessr/core/type_simplifier.rb +5 -5
- data/lib/type_guessr/core/types.rb +64 -22
- data/lib/type_guessr/core.rb +29 -0
- data/lib/type_guessr/mcp/server.rb +55 -46
- data/lib/type_guessr/mcp/standalone_runtime.rb +70 -110
- data/lib/type_guessr/version.rb +1 -1
- metadata +24 -4
- data/.mcp.json +0 -9
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../type_guessr/core/types"
|
|
4
|
+
|
|
5
|
+
module RubyLsp
|
|
6
|
+
module TypeGuessr
|
|
7
|
+
module Dsl
|
|
8
|
+
# Maps ActiveRecord column type strings to TypeGuessr Types.
|
|
9
|
+
# Pure mapping with no external dependencies.
|
|
10
|
+
# AR-specific: Mongoid provides Ruby types directly in field declarations.
|
|
11
|
+
module ArTypeMapper
|
|
12
|
+
def self.map(ar_type, nullable: true)
|
|
13
|
+
base = case ar_type.to_s
|
|
14
|
+
when "string", "text"
|
|
15
|
+
::TypeGuessr::Core::Types::ClassInstance.for("String")
|
|
16
|
+
when "integer", "bigint"
|
|
17
|
+
::TypeGuessr::Core::Types::ClassInstance.for("Integer")
|
|
18
|
+
when "boolean"
|
|
19
|
+
::TypeGuessr::Core::Types::Union.new([
|
|
20
|
+
::TypeGuessr::Core::Types::ClassInstance.for("TrueClass"),
|
|
21
|
+
::TypeGuessr::Core::Types::ClassInstance.for("FalseClass"),
|
|
22
|
+
])
|
|
23
|
+
when "float"
|
|
24
|
+
::TypeGuessr::Core::Types::ClassInstance.for("Float")
|
|
25
|
+
when "decimal"
|
|
26
|
+
::TypeGuessr::Core::Types::ClassInstance.for("BigDecimal")
|
|
27
|
+
when "date"
|
|
28
|
+
::TypeGuessr::Core::Types::ClassInstance.for("Date")
|
|
29
|
+
when "datetime", "timestamp"
|
|
30
|
+
::TypeGuessr::Core::Types::ClassInstance.for("ActiveSupport::TimeWithZone")
|
|
31
|
+
when "json", "jsonb"
|
|
32
|
+
::TypeGuessr::Core::Types::ClassInstance.for("Hash")
|
|
33
|
+
else
|
|
34
|
+
::TypeGuessr::Core::Types::Unknown.instance
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if nullable && !base.is_a?(::TypeGuessr::Core::Types::Unknown)
|
|
38
|
+
nil_type = ::TypeGuessr::Core::Types::ClassInstance.for("NilClass")
|
|
39
|
+
if base.is_a?(::TypeGuessr::Core::Types::Union)
|
|
40
|
+
::TypeGuessr::Core::Types::Union.new(base.types + [nil_type])
|
|
41
|
+
else
|
|
42
|
+
::TypeGuessr::Core::Types::Union.new([base, nil_type])
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
base
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "dsl"
|
|
4
|
+
|
|
5
|
+
module RubyLsp
|
|
6
|
+
module TypeGuessr
|
|
7
|
+
# Pure orchestrator for DSL type registration.
|
|
8
|
+
# No framework logic, no cache logic — delegates everything to Adapters.
|
|
9
|
+
class DslTypeRegistrar
|
|
10
|
+
def initialize(signature_registry:, code_index:, project_root:, adapters: nil)
|
|
11
|
+
@signature_registry = signature_registry
|
|
12
|
+
@code_index = code_index
|
|
13
|
+
@project_root = project_root
|
|
14
|
+
@adapters = adapters || default_adapters
|
|
15
|
+
@base_registered = false
|
|
16
|
+
@log_callback = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def on_log(&block)
|
|
20
|
+
@log_callback = block
|
|
21
|
+
active_adapters.each { |a| a.on_log(&block) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def register_all(runner_client: nil)
|
|
25
|
+
unless @base_registered
|
|
26
|
+
active_adapters.each { |a| a.register_base_methods(signature_registry: @signature_registry) }
|
|
27
|
+
@base_registered = true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
active_adapters.each do |adapter|
|
|
31
|
+
adapter.register_models(
|
|
32
|
+
runner_client: runner_client,
|
|
33
|
+
signature_registry: @signature_registry,
|
|
34
|
+
code_index: @code_index
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def check_and_refresh(runner_client: nil)
|
|
40
|
+
active_adapters.each do |adapter|
|
|
41
|
+
next unless adapter.changed?
|
|
42
|
+
|
|
43
|
+
adapter.refresh(
|
|
44
|
+
runner_client: runner_client,
|
|
45
|
+
signature_registry: @signature_registry,
|
|
46
|
+
code_index: @code_index
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private def active_adapters
|
|
52
|
+
@adapters.select(&:applicable?)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private def default_adapters
|
|
56
|
+
[Dsl::ActiveRecordAdapter.new(project_root: @project_root)]
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -121,49 +121,55 @@ module RubyLsp
|
|
|
121
121
|
class_name = extract_class_name(receiver_type)
|
|
122
122
|
|
|
123
123
|
if class_name
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
139
|
+
end
|
|
131
140
|
end
|
|
132
|
-
end
|
|
133
141
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
content += "\n\n**Overloads:**\n"
|
|
161
|
-
sig_strs.each { |s| content += "- `#{s}`\n" }
|
|
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
|
|
162
168
|
end
|
|
163
|
-
end
|
|
164
169
|
|
|
165
|
-
|
|
166
|
-
|
|
170
|
+
@response_builder.push(content, category: :documentation)
|
|
171
|
+
return
|
|
172
|
+
end
|
|
167
173
|
end
|
|
168
174
|
end
|
|
169
175
|
end
|
|
@@ -354,7 +360,7 @@ module RubyLsp
|
|
|
354
360
|
end
|
|
355
361
|
|
|
356
362
|
private def debug_enabled?
|
|
357
|
-
Config.debug?
|
|
363
|
+
::TypeGuessr::Core::Config.debug?
|
|
358
364
|
end
|
|
359
365
|
|
|
360
366
|
private def build_debug_info(result, ir_node = nil)
|
|
@@ -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
|
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
|
-
require_relative "../../type_guessr/core
|
|
5
|
-
require_relative "
|
|
6
|
-
require_relative "../../type_guessr/core/registry/method_registry"
|
|
7
|
-
require_relative "../../type_guessr/core/registry/instance_variable_registry"
|
|
8
|
-
require_relative "../../type_guessr/core/registry/class_variable_registry"
|
|
9
|
-
require_relative "../../type_guessr/core/registry/signature_registry"
|
|
10
|
-
require_relative "../../type_guessr/core/inference/resolver"
|
|
11
|
-
require_relative "../../type_guessr/core/signature_builder"
|
|
12
|
-
require_relative "../../type_guessr/core/type_simplifier"
|
|
13
|
-
require_relative "../../type_guessr/core/node_context_helper"
|
|
14
|
-
require_relative "../../type_guessr/core/cache/gem_signature_cache"
|
|
15
|
-
require_relative "../../type_guessr/core/cache/gem_dependency_resolver"
|
|
16
|
-
require_relative "../../type_guessr/core/cache/gem_signature_extractor"
|
|
4
|
+
require_relative "../../type_guessr/core"
|
|
5
|
+
require_relative "dsl_type_registrar"
|
|
17
6
|
require_relative "code_index_adapter"
|
|
18
7
|
require_relative "type_inferrer"
|
|
19
8
|
|
|
@@ -29,7 +18,6 @@ module RubyLsp
|
|
|
29
18
|
@message_queue = message_queue
|
|
30
19
|
@converter = ::TypeGuessr::Core::Converter::PrismConverter.new
|
|
31
20
|
@location_index = ::TypeGuessr::Core::Index::LocationIndex.new
|
|
32
|
-
@signature_registry = ::TypeGuessr::Core::Registry::SignatureRegistry.instance
|
|
33
21
|
@indexing_completed = false
|
|
34
22
|
@mutex = Mutex.new
|
|
35
23
|
@original_type_inferrer = nil
|
|
@@ -37,6 +25,10 @@ module RubyLsp
|
|
|
37
25
|
# Create CodeIndexAdapter wrapping RubyIndexer
|
|
38
26
|
@code_index = CodeIndexAdapter.new(global_state&.index)
|
|
39
27
|
|
|
28
|
+
# Create SignatureRegistry with code_index for ancestor chain traversal
|
|
29
|
+
@signature_registry = ::TypeGuessr::Core::Registry::SignatureRegistry.new(code_index: @code_index)
|
|
30
|
+
::TypeGuessr::Core::Registry::SignatureRegistry.instance = @signature_registry
|
|
31
|
+
|
|
40
32
|
# Create method registry with code_index for inheritance lookup
|
|
41
33
|
@method_registry = ::TypeGuessr::Core::Registry::MethodRegistry.new(
|
|
42
34
|
code_index: @code_index
|
|
@@ -211,6 +203,19 @@ module RubyLsp
|
|
|
211
203
|
end
|
|
212
204
|
end
|
|
213
205
|
|
|
206
|
+
# Check if a method should skip stdlib/gem RBS lookup in hover.
|
|
207
|
+
# Returns true for entries registered by DSL adapters (e.g., AR column accessor)
|
|
208
|
+
# so that hover goes directly to the resolver fallback instead of showing RBS signatures.
|
|
209
|
+
def skip_stdlib_rbs_method?(class_name, method_name)
|
|
210
|
+
entry = @signature_registry.lookup(class_name, method_name)
|
|
211
|
+
entry.is_a?(::TypeGuessr::Core::Registry::SignatureRegistry::GemMethodEntry) && entry.skip_stdlib_rbs?
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def skip_stdlib_rbs_class_method?(class_name, method_name)
|
|
215
|
+
entry = @signature_registry.lookup_class_method(class_name, method_name)
|
|
216
|
+
entry.is_a?(::TypeGuessr::Core::Registry::SignatureRegistry::GemMethodEntry) && entry.skip_stdlib_rbs?
|
|
217
|
+
end
|
|
218
|
+
|
|
214
219
|
# Start background indexing of all project files
|
|
215
220
|
def start_indexing
|
|
216
221
|
Thread.new do
|
|
@@ -248,10 +253,19 @@ module RubyLsp
|
|
|
248
253
|
# Connect on-demand inference callback for Unguessed gem methods
|
|
249
254
|
@signature_registry.on_demand_inferrer = method(:infer_gem_file_on_demand)
|
|
250
255
|
|
|
256
|
+
# Register DSL types (AR column accessors, enums, associations, scopes)
|
|
257
|
+
register_dsl_types
|
|
258
|
+
|
|
251
259
|
@indexing_completed = true
|
|
252
260
|
|
|
261
|
+
# Start schema watch loop for auto-refresh
|
|
262
|
+
start_schema_watch_loop
|
|
263
|
+
|
|
253
264
|
# Background inference: fully infer gems that have Unguessed entries (opt-in)
|
|
254
|
-
|
|
265
|
+
if ::TypeGuessr::Core::Config.background_gem_indexing? && result && result[:unguessed_gems].any?
|
|
266
|
+
background_infer_gems(result[:unguessed_gems],
|
|
267
|
+
result[:cache])
|
|
268
|
+
end
|
|
255
269
|
rescue StandardError => e
|
|
256
270
|
log_message("Error during file indexing: #{e.message}\n#{e.backtrace.first(10).join("\n")}")
|
|
257
271
|
@indexing_completed = true
|
|
@@ -444,7 +458,7 @@ module RubyLsp
|
|
|
444
458
|
method_registry: temp_method_registry,
|
|
445
459
|
location_index: temp_location_index
|
|
446
460
|
)
|
|
447
|
-
timeout = Config.gem_inference_timeout
|
|
461
|
+
timeout = ::TypeGuessr::Core::Config.gem_inference_timeout
|
|
448
462
|
signatures = extractor.extract(files, timeout: timeout)
|
|
449
463
|
|
|
450
464
|
t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
@@ -754,6 +768,66 @@ module RubyLsp
|
|
|
754
768
|
log_message("Index stabilized at #{previous_count} entries.")
|
|
755
769
|
end
|
|
756
770
|
|
|
771
|
+
private def discover_runner_client
|
|
772
|
+
rails_addon = ::RubyLsp::Addon.get("Ruby LSP Rails", ">= 0.4.0", "< 0.5.0")
|
|
773
|
+
rails_addon.rails_runner_client
|
|
774
|
+
rescue ::RubyLsp::Addon::AddonNotFoundError, ::RubyLsp::Addon::IncompatibleApiError
|
|
775
|
+
nil
|
|
776
|
+
rescue StandardError => e
|
|
777
|
+
log_message("Failed to discover RunnerClient: #{e.message}")
|
|
778
|
+
nil
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
private def register_dsl_types
|
|
782
|
+
workspace_path = @global_state.workspace_path
|
|
783
|
+
return unless workspace_path
|
|
784
|
+
|
|
785
|
+
@dsl_registrar = DslTypeRegistrar.new(
|
|
786
|
+
signature_registry: @signature_registry,
|
|
787
|
+
code_index: @code_index,
|
|
788
|
+
project_root: workspace_path
|
|
789
|
+
)
|
|
790
|
+
@dsl_registrar.on_log { |msg| log_message("[DSL] #{msg}") }
|
|
791
|
+
|
|
792
|
+
runner_client = wait_for_runner_client
|
|
793
|
+
@dsl_registrar.register_all(runner_client: runner_client)
|
|
794
|
+
rescue StandardError => e
|
|
795
|
+
log_message("DSL type registration failed: #{e.message}\n#{e.backtrace.first(3).join("\n")}")
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
# Wait for ruby-lsp-rails RunnerClient to transition from NullClient.
|
|
799
|
+
# 3s interval × max 10 attempts = max 30s.
|
|
800
|
+
private def wait_for_runner_client
|
|
801
|
+
log_message("[DSL] Waiting for RunnerClient...")
|
|
802
|
+
max_attempts = 10
|
|
803
|
+
|
|
804
|
+
max_attempts.times do |_i|
|
|
805
|
+
client = discover_runner_client
|
|
806
|
+
if client.respond_to?(:connected?) && client.connected?
|
|
807
|
+
log_message("[DSL] RunnerClient ready (#{client.class})")
|
|
808
|
+
return client
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
sleep(3)
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
log_message("[DSL] RunnerClient timeout after #{max_attempts} attempts")
|
|
815
|
+
nil
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
private def start_schema_watch_loop
|
|
819
|
+
return unless @dsl_registrar
|
|
820
|
+
|
|
821
|
+
Thread.new do
|
|
822
|
+
loop do
|
|
823
|
+
sleep(45)
|
|
824
|
+
@dsl_registrar.check_and_refresh(runner_client: discover_runner_client)
|
|
825
|
+
rescue StandardError => e
|
|
826
|
+
log_message("Schema watch error: #{e.message}")
|
|
827
|
+
end
|
|
828
|
+
end
|
|
829
|
+
end
|
|
830
|
+
|
|
757
831
|
private def log_message(message)
|
|
758
832
|
return unless @message_queue
|
|
759
833
|
return if @message_queue.closed?
|
data/lib/type-guessr.rb
CHANGED
|
@@ -8,23 +8,12 @@ end
|
|
|
8
8
|
# Load version
|
|
9
9
|
require_relative "type_guessr/version"
|
|
10
10
|
|
|
11
|
-
# Load core components
|
|
12
|
-
require_relative "type_guessr/core
|
|
13
|
-
require_relative "type_guessr/core/node_key_generator"
|
|
14
|
-
require_relative "type_guessr/core/node_context_helper"
|
|
15
|
-
require_relative "type_guessr/core/ir/nodes"
|
|
16
|
-
require_relative "type_guessr/core/index/location_index"
|
|
17
|
-
require_relative "type_guessr/core/converter/prism_converter"
|
|
18
|
-
require_relative "type_guessr/core/converter/rbs_converter"
|
|
19
|
-
require_relative "type_guessr/core/inference/result"
|
|
20
|
-
require_relative "type_guessr/core/inference/resolver"
|
|
21
|
-
require_relative "type_guessr/core/registry/signature_registry"
|
|
22
|
-
require_relative "type_guessr/core/logger"
|
|
11
|
+
# Load core components
|
|
12
|
+
require_relative "type_guessr/core"
|
|
23
13
|
|
|
24
14
|
# Load Ruby LSP integration
|
|
25
15
|
# NOTE: addon.rb is NOT required here - it's auto-discovered by Ruby LSP
|
|
26
16
|
# Requiring it here would cause double activation
|
|
27
|
-
require_relative "ruby_lsp/type_guessr/config"
|
|
28
17
|
require_relative "ruby_lsp/type_guessr/constants"
|
|
29
18
|
require_relative "ruby_lsp/type_guessr/runtime_adapter"
|
|
30
19
|
require_relative "ruby_lsp/type_guessr/hover"
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "digest/sha2"
|
|
5
5
|
require "fileutils"
|
|
6
|
+
require_relative "../../version"
|
|
6
7
|
|
|
7
8
|
module TypeGuessr
|
|
8
9
|
module Core
|
|
@@ -11,7 +12,7 @@ module TypeGuessr
|
|
|
11
12
|
# Cache key = gem name + version + hash of transitive dependencies.
|
|
12
13
|
# Files stored at: ~/.cache/type-guessr/gem-signatures/{name}-{version}-{dep_hash}.json
|
|
13
14
|
class GemSignatureCache
|
|
14
|
-
CACHE_FORMAT_VERSION =
|
|
15
|
+
CACHE_FORMAT_VERSION = 2
|
|
15
16
|
|
|
16
17
|
# @param cache_dir [String, nil] Override cache directory (for testing)
|
|
17
18
|
def initialize(cache_dir: nil)
|
|
@@ -79,7 +80,7 @@ module TypeGuessr
|
|
|
79
80
|
|
|
80
81
|
private def default_cache_dir
|
|
81
82
|
xdg_cache = ENV.fetch("XDG_CACHE_HOME", File.join(Dir.home, ".cache"))
|
|
82
|
-
File.join(xdg_cache, "type-guessr", "gem-signatures")
|
|
83
|
+
File.join(xdg_cache, "type-guessr", "gem-signatures", TypeGuessr::VERSION)
|
|
83
84
|
end
|
|
84
85
|
|
|
85
86
|
private def cache_path(gem_name, gem_version, transitive_deps)
|