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
@@ -10,7 +10,8 @@ module RubyLsp
10
10
  # Core layer shortcuts
11
11
  Types = ::TypeGuessr::Core::Types
12
12
  IR = ::TypeGuessr::Core::IR
13
- private_constant :Types, :IR
13
+ NodeContextHelper = ::TypeGuessr::Core::NodeContextHelper
14
+ private_constant :Types, :IR, :NodeContextHelper
14
15
 
15
16
  def initialize(index, runtime_adapter)
16
17
  super(index)
@@ -26,14 +27,7 @@ module RubyLsp
26
27
  guess_type_from_ir(node_context)
27
28
  end
28
29
 
29
- private
30
-
31
- # Attempt to guess type using IR-based inference
32
- # Returns GuessedType only when type can be resolved
33
- #
34
- # @param node_context [RubyLsp::NodeContext] The context of the node
35
- # @return [GuessedType, nil] The guessed type or nil if unknown
36
- def guess_type_from_ir(node_context)
30
+ private def guess_type_from_ir(node_context)
37
31
  node = node_context.node
38
32
 
39
33
  # For CallNode, we need to infer the receiver's type
@@ -64,9 +58,9 @@ module RubyLsp
64
58
  # @param node [Prism::Node] The Prism node
65
59
  # @param node_context [RubyLsp::NodeContext] The context of the node
66
60
  # @return [TypeGuessr::Core::IR::Node, nil] The IR node or nil
67
- def find_ir_node(node, node_context)
68
- scope_id = generate_scope_id(node_context)
69
- node_hash = generate_node_hash(node, node_context)
61
+ private def find_ir_node(node, node_context)
62
+ scope_id = NodeContextHelper.generate_scope_id(node_context)
63
+ node_hash = NodeContextHelper.generate_node_hash(node, node_context)
70
64
  return nil unless node_hash
71
65
 
72
66
  node_key = "#{scope_id}:#{node_hash}"
@@ -76,7 +70,7 @@ module RubyLsp
76
70
  # Extract the variable node from a CallNode's receiver
77
71
  # @param call_node [Prism::CallNode] The call node
78
72
  # @return [Prism::Node, nil] The receiver variable node or nil
79
- def extract_receiver_variable(call_node)
73
+ private def extract_receiver_variable(call_node)
80
74
  receiver = call_node.receiver
81
75
  return nil unless receiver
82
76
 
@@ -92,7 +86,7 @@ module RubyLsp
92
86
  # Check if the node is a variable node that we can analyze
93
87
  # @param node [Prism::Node] The node to check
94
88
  # @return [Boolean] true if the node is a variable node
95
- def variable_node?(node)
89
+ private def variable_node?(node)
96
90
  case node
97
91
  when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode, Prism::LocalVariableTargetNode,
98
92
  Prism::InstanceVariableReadNode, Prism::InstanceVariableWriteNode, Prism::InstanceVariableTargetNode,
@@ -104,97 +98,6 @@ module RubyLsp
104
98
  false
105
99
  end
106
100
  end
107
-
108
- # Generate scope_id from node_context
109
- # Format: "ClassName#method_name" or "ClassName" or "#method_name" or ""
110
- # @param node_context [RubyLsp::NodeContext] The context of the node
111
- # @return [String] The scope identifier
112
- def generate_scope_id(node_context)
113
- class_path = node_context.nesting.map do |n|
114
- n.is_a?(String) ? n : n.name.to_s
115
- end.join("::")
116
-
117
- method_name = node_context.surrounding_method
118
-
119
- if method_name
120
- "#{class_path}##{method_name}"
121
- else
122
- class_path
123
- end
124
- end
125
-
126
- # Generate node_hash from Prism node to match IR node_hash format
127
- # @param node [Prism::Node] The Prism node
128
- # @param node_context [RubyLsp::NodeContext] The context (for block param detection)
129
- # @return [String, nil] The node hash or nil
130
- def generate_node_hash(node, node_context)
131
- line = node.location.start_line
132
- case node
133
- when Prism::LocalVariableWriteNode, Prism::LocalVariableTargetNode
134
- "local_write:#{node.name}:#{line}"
135
- when Prism::LocalVariableReadNode
136
- "local_read:#{node.name}:#{line}"
137
- when Prism::InstanceVariableWriteNode, Prism::InstanceVariableTargetNode
138
- "ivar_write:#{node.name}:#{line}"
139
- when Prism::InstanceVariableReadNode
140
- "ivar_read:#{node.name}:#{line}"
141
- when Prism::RequiredParameterNode, Prism::OptionalParameterNode, Prism::RestParameterNode,
142
- Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode,
143
- Prism::KeywordRestParameterNode, Prism::BlockParameterNode
144
- # Check if this is a block parameter
145
- if block_parameter?(node, node_context)
146
- index = block_parameter_index(node, node_context)
147
- "bparam:#{index}:#{line}"
148
- else
149
- "param:#{node.name}:#{line}"
150
- end
151
- end
152
- end
153
-
154
- # Check if a parameter node is inside a block (not a method definition)
155
- # @param node [Prism::Node] The parameter node
156
- # @param node_context [RubyLsp::NodeContext] The context
157
- # @return [Boolean] true if inside a block
158
- def block_parameter?(node, node_context)
159
- call_node = node_context.call_node
160
- return false unless call_node&.block
161
-
162
- block_params = call_node.block.parameters&.parameters
163
- return false unless block_params
164
-
165
- all_params = collect_block_params(block_params)
166
- all_params.include?(node)
167
- end
168
-
169
- # Get the index of a block parameter
170
- # @param node [Prism::Node] The parameter node
171
- # @param node_context [RubyLsp::NodeContext] The context
172
- # @return [Integer] The parameter index
173
- def block_parameter_index(node, node_context)
174
- call_node = node_context.call_node
175
- return 0 unless call_node&.block
176
-
177
- block_params = call_node.block.parameters&.parameters
178
- return 0 unless block_params
179
-
180
- all_params = collect_block_params(block_params)
181
- all_params.index(node) || 0
182
- end
183
-
184
- # Collect all parameters from block parameters node
185
- # @param block_params [Prism::ParametersNode] The block parameters
186
- # @return [Array<Prism::Node>] All parameter nodes
187
- def collect_block_params(block_params)
188
- params = []
189
- params.concat(block_params.requireds) if block_params.respond_to?(:requireds)
190
- params.concat(block_params.optionals) if block_params.respond_to?(:optionals)
191
- params << block_params.rest if block_params.respond_to?(:rest) && block_params.rest
192
- params.concat(block_params.posts) if block_params.respond_to?(:posts)
193
- params.concat(block_params.keywords) if block_params.respond_to?(:keywords)
194
- params << block_params.keyword_rest if block_params.respond_to?(:keyword_rest) && block_params.keyword_rest
195
- params << block_params.block if block_params.respond_to?(:block) && block_params.block
196
- params.compact
197
- end
198
101
  end
199
102
  end
200
103
  end
data/lib/type-guessr.rb CHANGED
@@ -8,21 +8,13 @@ end
8
8
  # Load version
9
9
  require_relative "type_guessr/version"
10
10
 
11
- # Load core components (IR-based architecture)
12
- require_relative "type_guessr/core/types"
13
- require_relative "type_guessr/core/ir/nodes"
14
- require_relative "type_guessr/core/index/location_index"
15
- require_relative "type_guessr/core/converter/prism_converter"
16
- require_relative "type_guessr/core/converter/rbs_converter"
17
- require_relative "type_guessr/core/inference/result"
18
- require_relative "type_guessr/core/inference/resolver"
19
- require_relative "type_guessr/core/rbs_provider"
20
- require_relative "type_guessr/core/logger"
11
+ # Load core components
12
+ require_relative "type_guessr/core"
21
13
 
22
14
  # Load Ruby LSP integration
23
15
  # NOTE: addon.rb is NOT required here - it's auto-discovered by Ruby LSP
24
16
  # Requiring it here would cause double activation
25
- require_relative "ruby_lsp/type_guessr/config"
17
+ require_relative "ruby_lsp/type_guessr/constants"
26
18
  require_relative "ruby_lsp/type_guessr/runtime_adapter"
27
19
  require_relative "ruby_lsp/type_guessr/hover"
28
20
  require_relative "ruby_lsp/type_guessr/debug_server"
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
4
+
5
+ module TypeGuessr
6
+ module Core
7
+ module Cache
8
+ # Resolves gem dependencies from Gemfile.lock and partitions files by gem.
9
+ # Provides topological ordering for dependency-aware cache building.
10
+ class GemDependencyResolver
11
+ GEM_PATH_PATTERN = %r{/gems/([^/]+)-(\d[^/]*)/}
12
+
13
+ # @param lockfile_path [String] Path to Gemfile.lock
14
+ def initialize(lockfile_path)
15
+ @lockfile_path = lockfile_path
16
+ @lockfile = parse_lockfile
17
+ end
18
+
19
+ # Partition file paths into gem files and project files
20
+ # @param file_paths [Array<String>] All indexable file paths
21
+ # @return [Hash] { gems: { name => { version:, files:, transitive_deps: {} } }, project_files: [] }
22
+ def partition(file_paths)
23
+ gems = {}
24
+ project_files = []
25
+
26
+ file_paths.each do |path|
27
+ match = path.match(GEM_PATH_PATTERN)
28
+ if match
29
+ gem_name = match[1]
30
+ gem_version = match[2]
31
+
32
+ # Only include gems from the lockfile
33
+ if @lockfile.key?(gem_name)
34
+ gems[gem_name] ||= {
35
+ version: gem_version,
36
+ files: [],
37
+ transitive_deps: resolve_transitive_deps(gem_name)
38
+ }
39
+ gems[gem_name][:files] << path
40
+ else
41
+ project_files << path
42
+ end
43
+ else
44
+ project_files << path
45
+ end
46
+ end
47
+
48
+ { gems: gems, project_files: project_files }
49
+ end
50
+
51
+ # Return gem names in topological order (leaves first, roots last)
52
+ # @param gem_names [Array<String>] Gem names to sort
53
+ # @return [Array<String>] Sorted gem names
54
+ def topological_order(gem_names)
55
+ visited = {}
56
+ order = []
57
+
58
+ gem_names.each do |name|
59
+ visit(name, gem_names, visited, order)
60
+ end
61
+
62
+ order
63
+ end
64
+
65
+ private def parse_lockfile
66
+ return {} unless File.exist?(@lockfile_path)
67
+
68
+ content = File.read(@lockfile_path)
69
+ parser = Bundler::LockfileParser.new(content)
70
+
71
+ parser.specs.to_h do |spec|
72
+ deps = spec.dependencies.map(&:name)
73
+ [spec.name, { version: spec.version.to_s, deps: deps }]
74
+ end
75
+ end
76
+
77
+ private def resolve_transitive_deps(gem_name)
78
+ result = {}
79
+ queue = (@lockfile.dig(gem_name, :deps) || []).dup
80
+ visited = Set.new
81
+
82
+ while (dep_name = queue.shift)
83
+ next if visited.include?(dep_name)
84
+
85
+ visited << dep_name
86
+
87
+ dep_info = @lockfile[dep_name]
88
+ next unless dep_info
89
+
90
+ result[dep_name] = dep_info[:version]
91
+ queue.concat(dep_info[:deps] || [])
92
+ end
93
+
94
+ result
95
+ end
96
+
97
+ private def visit(name, valid_names, visited, order)
98
+ return if visited[name]
99
+
100
+ visited[name] = true
101
+
102
+ # Visit dependencies first
103
+ deps = @lockfile.dig(name, :deps) || []
104
+ deps.each do |dep|
105
+ visit(dep, valid_names, visited, order) if valid_names.include?(dep)
106
+ end
107
+
108
+ order << name
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "digest/sha2"
5
+ require "fileutils"
6
+ require_relative "../../version"
7
+
8
+ module TypeGuessr
9
+ module Core
10
+ module Cache
11
+ # Manages disk-based cache of gem method signatures.
12
+ # Cache key = gem name + version + hash of transitive dependencies.
13
+ # Files stored at: ~/.cache/type-guessr/gem-signatures/{name}-{version}-{dep_hash}.json
14
+ class GemSignatureCache
15
+ CACHE_FORMAT_VERSION = 2
16
+
17
+ # @param cache_dir [String, nil] Override cache directory (for testing)
18
+ def initialize(cache_dir: nil)
19
+ @cache_dir = cache_dir || default_cache_dir
20
+ end
21
+
22
+ # Check if a cached file exists for the given gem
23
+ # @param gem_name [String]
24
+ # @param gem_version [String]
25
+ # @param transitive_deps [Hash{String => String}] { dep_name => dep_version }
26
+ # @return [Boolean]
27
+ def cached?(gem_name, gem_version, transitive_deps)
28
+ File.exist?(cache_path(gem_name, gem_version, transitive_deps))
29
+ end
30
+
31
+ # Load cached signatures
32
+ # @param gem_name [String]
33
+ # @param gem_version [String]
34
+ # @param transitive_deps [Hash{String => String}]
35
+ # @return [Hash, nil] { "instance_methods" => {...}, "class_methods" => {...} } or nil on failure
36
+ def load(gem_name, gem_version, transitive_deps)
37
+ path = cache_path(gem_name, gem_version, transitive_deps)
38
+ return nil unless File.exist?(path)
39
+
40
+ data = JSON.parse(File.read(path))
41
+ return nil unless data["version"] == CACHE_FORMAT_VERSION
42
+
43
+ {
44
+ "instance_methods" => data["instance_methods"] || {},
45
+ "class_methods" => data["class_methods"] || {},
46
+ "fully_inferred" => data.fetch("fully_inferred", true),
47
+ "inference_timeout" => data.fetch("inference_timeout", false)
48
+ }
49
+ rescue JSON::ParserError, Errno::ENOENT
50
+ nil
51
+ end
52
+
53
+ # Save signatures to cache
54
+ # @param gem_name [String]
55
+ # @param gem_version [String]
56
+ # @param transitive_deps [Hash{String => String}]
57
+ # @param instance_methods [Hash] { class_name => { method_name => serialized_entry } }
58
+ # @param class_methods [Hash] { class_name => { method_name => serialized_entry } }
59
+ # @param fully_inferred [Boolean] Whether types are fully inferred (false = Unguessed placeholders)
60
+ def save(gem_name, gem_version, transitive_deps,
61
+ instance_methods:, class_methods:, fully_inferred: true, inference_timeout: false)
62
+ path = cache_path(gem_name, gem_version, transitive_deps)
63
+ FileUtils.mkdir_p(File.dirname(path))
64
+
65
+ data = {
66
+ "version" => CACHE_FORMAT_VERSION,
67
+ "fully_inferred" => fully_inferred,
68
+ "inference_timeout" => inference_timeout,
69
+ "instance_methods" => instance_methods,
70
+ "class_methods" => class_methods
71
+ }
72
+
73
+ File.write(path, JSON.generate(data))
74
+ end
75
+
76
+ # Delete all cached files
77
+ def clear!
78
+ FileUtils.rm_rf(@cache_dir)
79
+ end
80
+
81
+ private def default_cache_dir
82
+ xdg_cache = ENV.fetch("XDG_CACHE_HOME", File.join(Dir.home, ".cache"))
83
+ File.join(xdg_cache, "type-guessr", "gem-signatures", TypeGuessr::VERSION)
84
+ end
85
+
86
+ private def cache_path(gem_name, gem_version, transitive_deps)
87
+ dep_hash = compute_dep_hash(transitive_deps)
88
+ File.join(@cache_dir, "#{gem_name}-#{gem_version}-#{dep_hash}.json")
89
+ end
90
+
91
+ private def compute_dep_hash(transitive_deps)
92
+ sorted_pairs = transitive_deps.sort.map { |name, version| "#{name}:#{version}" }.join(",")
93
+ Digest::SHA256.hexdigest("v1:#{sorted_pairs}")[0..5]
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../type_serializer"
4
+
5
+ module TypeGuessr
6
+ module Core
7
+ module Cache
8
+ # Extracts method signatures from indexed gem files.
9
+ # Iterates DefNodes in the method registry and builds serialized signatures.
10
+ class GemSignatureExtractor
11
+ # @param signature_builder [SignatureBuilder]
12
+ # @param method_registry [Registry::MethodRegistry]
13
+ # @param location_index [Index::LocationIndex]
14
+ def initialize(signature_builder:, method_registry:, location_index:)
15
+ @signature_builder = signature_builder
16
+ @method_registry = method_registry
17
+ @location_index = location_index
18
+ end
19
+
20
+ # Extract all method signatures from indexed gem files
21
+ # @param gem_files [Array<String>] File paths belonging to this gem
22
+ # @param timeout [Float, nil] Max seconds for inference. Returns nil on timeout.
23
+ # @return [Hash, nil] { instance_methods:, class_methods: } or nil on timeout
24
+ def extract(gem_files, timeout: nil)
25
+ gem_def_nodes = collect_def_nodes(gem_files)
26
+ instance_methods = {}
27
+ class_methods = {}
28
+ deadline = timeout ? Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout : nil
29
+ check_counter = 0
30
+
31
+ @method_registry.each_entry do |class_name, method_name, def_node|
32
+ next unless gem_def_nodes.include?(def_node)
33
+
34
+ if deadline
35
+ check_counter += 1
36
+ return nil if (check_counter % 100).zero? && Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
37
+ end
38
+
39
+ sig = @signature_builder.build_from_def_node(def_node)
40
+ serialized = serialize_signature(sig)
41
+
42
+ if def_node.singleton
43
+ class_methods[class_name] ||= {}
44
+ class_methods[class_name][method_name] = serialized
45
+ else
46
+ instance_methods[class_name] ||= {}
47
+ instance_methods[class_name][method_name] = serialized
48
+ end
49
+
50
+ # module_function: also register as class method
51
+ if def_node.module_function
52
+ class_methods[class_name] ||= {}
53
+ class_methods[class_name][method_name] = serialized
54
+ end
55
+ rescue StandardError
56
+ # Skip methods that fail to infer (circular deps, etc.)
57
+ next
58
+ end
59
+
60
+ { instance_methods: instance_methods, class_methods: class_methods }
61
+ end
62
+
63
+ # Collect all DefNodes from gem files using the location index
64
+ # @param gem_files [Array<String>] File paths belonging to this gem
65
+ # @return [Set<IR::DefNode>] Set of DefNodes for O(1) membership check
66
+ private def collect_def_nodes(gem_files)
67
+ result = Set.new
68
+ gem_files.each do |file_path|
69
+ @location_index.nodes_for_file(file_path).each do |node|
70
+ result << node if node.is_a?(IR::DefNode)
71
+ end
72
+ end
73
+ result
74
+ end
75
+
76
+ private def serialize_signature(method_signature)
77
+ {
78
+ "return_type" => TypeSerializer.serialize(method_signature.return_type),
79
+ "params" => method_signature.params.map do |p|
80
+ { "name" => p.name.to_s, "kind" => p.kind.to_s, "type" => TypeSerializer.serialize(p.type) }
81
+ end
82
+ }
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "cache/gem_signature_cache"
4
+ require_relative "cache/gem_dependency_resolver"
5
+ require_relative "cache/gem_signature_extractor"
@@ -1,68 +1,55 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RubyLsp
4
- module TypeGuessr
3
+ module TypeGuessr
4
+ module Core
5
5
  # Loads TypeGuessr settings from .type-guessr.yml in the current working directory.
6
6
  #
7
7
  # Defaults:
8
8
  # - enabled: true
9
9
  # - debug: false
10
- # - union_cutoff: 10
11
- # - hash_shape_max_fields: 15
12
- # - max_chain_depth: 5
13
10
  module Config
14
11
  CONFIG_FILENAME = ".type-guessr.yml"
15
12
 
16
- # Default values for type inference limits
17
- DEFAULT_UNION_CUTOFF = 10
18
- DEFAULT_HASH_SHAPE_MAX_FIELDS = 15
19
- DEFAULT_MAX_CHAIN_DEPTH = 5
20
-
21
- module_function
22
-
23
- def reset!
13
+ module_function def reset!
24
14
  @cached_config = nil
25
- @cached_mtime = nil
26
15
  end
27
16
 
28
- def enabled?
17
+ module_function def enabled?
29
18
  value = load_config.fetch("enabled", true)
30
19
  value != false
31
20
  end
32
21
 
33
- def debug?
22
+ module_function def debug?
34
23
  load_config["debug"] == true
35
24
  end
36
25
 
37
- def debug_server_enabled?
26
+ module_function def debug_server_enabled?
38
27
  config = load_config
39
28
  return config["debug_server"] if config.key?("debug_server")
40
29
 
41
30
  debug?
42
31
  end
43
32
 
44
- def debug_server_port
45
- load_config.fetch("debug_server_port", 7010)
33
+ module_function def background_gem_indexing?
34
+ load_config.fetch("background_gem_indexing", false) == true
46
35
  end
47
36
 
48
- def union_cutoff
49
- load_config.fetch("union_cutoff", DEFAULT_UNION_CUTOFF)
37
+ module_function def debug_server_port
38
+ load_config.fetch("debug_server_port", 7010)
50
39
  end
51
40
 
52
- def hash_shape_max_fields
53
- load_config.fetch("hash_shape_max_fields", DEFAULT_HASH_SHAPE_MAX_FIELDS)
41
+ module_function def gem_inference_timeout
42
+ load_config.fetch("gem_inference_timeout", 1.0)
54
43
  end
55
44
 
56
- def max_chain_depth
57
- load_config.fetch("max_chain_depth", DEFAULT_MAX_CHAIN_DEPTH)
58
- end
45
+ module_function def load_config
46
+ return @cached_config if @cached_config
59
47
 
60
- def load_config
61
48
  path = File.join(Dir.pwd, CONFIG_FILENAME)
62
- return default_config if !File.exist?(path)
63
-
64
- mtime = File.mtime(path)
65
- return @cached_config if @cached_config && @cached_mtime == mtime
49
+ if !File.exist?(path)
50
+ @cached_config = default_config
51
+ return @cached_config
52
+ end
66
53
 
67
54
  require "yaml"
68
55
 
@@ -71,14 +58,12 @@ module RubyLsp
71
58
  data = {} unless data.is_a?(Hash)
72
59
 
73
60
  @cached_config = default_config.merge(data)
74
- @cached_mtime = mtime
75
- @cached_config
76
61
  rescue StandardError => e
77
62
  warn("[TypeGuessr] Error loading config file: #{e.message}")
78
63
  default_config
79
64
  end
80
65
 
81
- def default_config
66
+ module_function def default_config
82
67
  {
83
68
  "enabled" => true,
84
69
  "debug" => false