type-guessr 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 896566907c498dda0251546023a2d6cd8431cc8fdb5f7cbb72c6ef902067d95a
4
+ data.tar.gz: a8104ed21dff5b62e066223b7fe3a9ff4f9f8b49a5ae2296bd9537a86478d07c
5
+ SHA512:
6
+ metadata.gz: d0823d8578c5d00d9475f1473349b1ec9aeb96e3e025907a00b933b1e4b32f04663e2e1fe26df0977565d224d54eae4c9a355011826bc8ba2b56496a7750dd3a
7
+ data.tar.gz: 02000caaf827f2e0297a80ae7296261b4d803e7f49ba8a64d114733dfdea82e079ee5967e9a7c02a311538d095b1fc25bbf3784db4c0a8d5a1be46cac7a5c24a
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Shia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/riseshia/type-guessr)
2
+
3
+ # TypeGuessr
4
+
5
+ > **Warning**: This project is under active development. Breaking changes may occur without notice.
6
+ >
7
+ > CHANGELOG will be maintained once the project reaches a stable phase. Until then, please refer to the commit history.
8
+
9
+ A Ruby LSP addon that provides heuristic type inference to enhance IDE features like Hover and Go to Definition.
10
+
11
+ ## Features
12
+
13
+ - **Type Inference**: Automatically infers variable types based on method call patterns
14
+ - **Hover Tooltips**: Shows guessed types when hovering over variables
15
+ - **Heuristic Approach**: Works without type annotations by analyzing method usage
16
+ - **Smart Matching**: Finds classes that have all the methods called on a variable
17
+
18
+ ## Installation
19
+
20
+ TypeGuessr is a Ruby LSP addon. Add it to your project's Gemfile under the development group:
21
+
22
+ ```ruby
23
+ group :development do
24
+ gem 'type-guessr', require: false
25
+ end
26
+ ```
27
+
28
+ Then run:
29
+
30
+ ```bash
31
+ bundle install
32
+ ```
33
+
34
+ After installation, restart your editor or reload the Ruby LSP server to activate the addon.
35
+
36
+ ## Usage
37
+
38
+ Once installed, the addon will automatically be loaded by Ruby LSP. Hover over variables, parameters, or instance variables to see guessed types.
39
+
40
+ ### Example
41
+
42
+ ```ruby
43
+ class Recipe
44
+ def ingredients
45
+ []
46
+ end
47
+
48
+ def steps
49
+ []
50
+ end
51
+ end
52
+
53
+ def process(recipe)
54
+ recipe.ingredients # Hover over 'recipe' shows: Guessed type: Recipe
55
+ recipe.steps
56
+ end
57
+ ```
58
+
59
+ The addon analyzes method calls (`ingredients`, `steps`) and finds that only the `Recipe` class has both methods, so it infers the type as `Recipe`.
60
+
61
+ ## Configuration
62
+
63
+ Create a `.type-guessr.yml` file in your project root to customize behavior. See [.type-guessr.yml.example](.type-guessr.yml.example) for all available options.
64
+
65
+ After changing configuration, restart Ruby LSP (VSCode: reload window).
66
+
67
+ ### Debug Mode
68
+
69
+ When `debug: true`, the addon will:
70
+ - Log debug information to stderr
71
+ - Show inference basis in hover tooltips
72
+ - Start a debug web server (unless `debug_server: false`)
73
+
74
+ ### Debug Web Server
75
+
76
+ When enabled, a web server starts at `http://127.0.0.1:<port>` (default port: 7010). This provides a web interface to inspect the type inference:
77
+
78
+ - **Search**: Search for methods to visualize their IR dependency graphs
79
+ - **Graph Visualization**: Interactive dependency graph
80
+
81
+ This is useful for understanding how TypeGuessr analyzes your codebase and debugging type inference issues.
82
+
83
+ ## Development
84
+
85
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
86
+
87
+ ## Contributing
88
+
89
+ Bug reports and pull requests are welcome on GitHub at https://github.com/riseshia/type-guessr.
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_lsp/addon"
4
+ require_relative "config"
5
+ require_relative "runtime_adapter"
6
+ require_relative "hover"
7
+ require_relative "debug_server"
8
+ require_relative "../../type_guessr/core/rbs_provider"
9
+
10
+ module RubyLsp
11
+ module TypeGuessr
12
+ # TypeGuessr addon for Ruby LSP
13
+ # Provides heuristic type inference without requiring type annotations
14
+ class Addon < ::RubyLsp::Addon
15
+ # Node types to add to Ruby LSP's ALLOWED_TARGETS for hover support
16
+ HOVER_TARGET_NODES = [
17
+ Prism::LocalVariableReadNode,
18
+ Prism::LocalVariableWriteNode,
19
+ Prism::LocalVariableTargetNode,
20
+ Prism::InstanceVariableReadNode,
21
+ Prism::InstanceVariableWriteNode,
22
+ Prism::InstanceVariableTargetNode,
23
+ Prism::ClassVariableReadNode,
24
+ Prism::ClassVariableWriteNode,
25
+ Prism::ClassVariableTargetNode,
26
+ Prism::GlobalVariableReadNode,
27
+ Prism::GlobalVariableWriteNode,
28
+ Prism::GlobalVariableTargetNode,
29
+ Prism::SelfNode,
30
+ Prism::RequiredParameterNode,
31
+ Prism::OptionalParameterNode,
32
+ Prism::RestParameterNode,
33
+ Prism::RequiredKeywordParameterNode,
34
+ Prism::OptionalKeywordParameterNode,
35
+ Prism::KeywordRestParameterNode,
36
+ Prism::BlockParameterNode,
37
+ Prism::ForwardingParameterNode,
38
+ Prism::CallNode,
39
+ Prism::DefNode,
40
+ ].freeze
41
+
42
+ attr_reader :runtime_adapter
43
+
44
+ def name
45
+ "TypeGuessr"
46
+ end
47
+
48
+ def activate(global_state, message_queue)
49
+ @global_state = global_state
50
+ @message_queue = message_queue
51
+ @runtime_adapter = RuntimeAdapter.new(global_state, message_queue)
52
+ @debug_server = nil
53
+
54
+ # Extend Ruby LSP's hover targets to include variables and parameters
55
+ extend_hover_targets
56
+
57
+ # Preload RBS environment
58
+ ::TypeGuessr::Core::RBSProvider.instance.preload
59
+
60
+ # Start background indexing
61
+ @runtime_adapter.start_indexing
62
+
63
+ # Swap TypeInferrer for enhanced Go to Definition
64
+ @runtime_adapter.swap_type_inferrer
65
+
66
+ # Start debug server if enabled
67
+ start_debug_server if Config.debug_server_enabled?
68
+
69
+ debug_status = Config.debug? ? " (debug mode enabled)" : ""
70
+ message_queue << RubyLsp::Notification.window_log_message(
71
+ "[TypeGuessr] Activated#{debug_status}",
72
+ type: RubyLsp::Constant::MessageType::LOG
73
+ )
74
+ end
75
+
76
+ def deactivate
77
+ @runtime_adapter&.restore_type_inferrer
78
+ @debug_server&.stop
79
+ end
80
+
81
+ def create_hover_listener(response_builder, node_context, dispatcher)
82
+ return unless Config.enabled?
83
+
84
+ Hover.new(@runtime_adapter, response_builder, node_context, dispatcher, @global_state)
85
+ end
86
+
87
+ # Handle file changes
88
+ def workspace_did_change_watched_files(changes)
89
+ return unless Config.enabled?
90
+
91
+ changes.each do |change|
92
+ uri = URI(change[:uri])
93
+ next unless uri.path&.end_with?(".rb")
94
+
95
+ case change[:type]
96
+ when RubyLsp::Constant::FileChangeType::CREATED,
97
+ RubyLsp::Constant::FileChangeType::CHANGED
98
+ reindex_file(uri)
99
+ when RubyLsp::Constant::FileChangeType::DELETED
100
+ file_path = uri.to_standardized_path
101
+ @runtime_adapter.instance_variable_get(:@location_index).remove_file(file_path) if file_path
102
+ end
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ def extend_hover_targets
109
+ targets = RubyLsp::Listeners::Hover::ALLOWED_TARGETS
110
+
111
+ HOVER_TARGET_NODES.each do |target|
112
+ targets << target unless targets.include?(target)
113
+ end
114
+ end
115
+
116
+ def reindex_file(uri)
117
+ file_path = uri.path
118
+ return unless file_path && File.exist?(file_path)
119
+
120
+ source = File.read(file_path)
121
+ @runtime_adapter.index_source(uri.to_s, source)
122
+ rescue StandardError => e
123
+ warn("[TypeGuessr] Error indexing #{uri}: #{e.message}")
124
+ end
125
+
126
+ def start_debug_server
127
+ port = Config.debug_server_port
128
+ warn("[TypeGuessr] Starting debug server on port #{port}...")
129
+ @debug_server = DebugServer.new(@global_state, @runtime_adapter, port: port)
130
+ @debug_server.start
131
+ warn("[TypeGuessr] Debug server started: http://127.0.0.1:#{port}")
132
+ rescue StandardError => e
133
+ warn("[TypeGuessr] Failed to start debug server: #{e.class}: #{e.message}")
134
+ warn("[TypeGuessr] #{e.backtrace&.first(5)&.join("\n")}")
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLsp
4
+ module TypeGuessr
5
+ # Loads TypeGuessr settings from .type-guessr.yml in the current working directory.
6
+ #
7
+ # Defaults:
8
+ # - enabled: true
9
+ # - debug: false
10
+ # - union_cutoff: 10
11
+ # - hash_shape_max_fields: 15
12
+ # - max_chain_depth: 5
13
+ module Config
14
+ CONFIG_FILENAME = ".type-guessr.yml"
15
+
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!
24
+ @cached_config = nil
25
+ @cached_mtime = nil
26
+ end
27
+
28
+ def enabled?
29
+ value = load_config.fetch("enabled", true)
30
+ value != false
31
+ end
32
+
33
+ def debug?
34
+ load_config["debug"] == true
35
+ end
36
+
37
+ def debug_server_enabled?
38
+ config = load_config
39
+ return config["debug_server"] if config.key?("debug_server")
40
+
41
+ debug?
42
+ end
43
+
44
+ def debug_server_port
45
+ load_config.fetch("debug_server_port", 7010)
46
+ end
47
+
48
+ def union_cutoff
49
+ load_config.fetch("union_cutoff", DEFAULT_UNION_CUTOFF)
50
+ end
51
+
52
+ def hash_shape_max_fields
53
+ load_config.fetch("hash_shape_max_fields", DEFAULT_HASH_SHAPE_MAX_FIELDS)
54
+ end
55
+
56
+ def max_chain_depth
57
+ load_config.fetch("max_chain_depth", DEFAULT_MAX_CHAIN_DEPTH)
58
+ end
59
+
60
+ def load_config
61
+ 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
66
+
67
+ require "yaml"
68
+
69
+ raw = File.read(path)
70
+ data = YAML.safe_load(raw, permitted_classes: [], permitted_symbols: [], aliases: false)
71
+ data = {} unless data.is_a?(Hash)
72
+
73
+ @cached_config = default_config.merge(data)
74
+ @cached_mtime = mtime
75
+ @cached_config
76
+ rescue StandardError => e
77
+ warn("[TypeGuessr] Error loading config file: #{e.message}")
78
+ default_config
79
+ end
80
+
81
+ def default_config
82
+ {
83
+ "enabled" => true,
84
+ "debug" => false
85
+ }
86
+ end
87
+ private_class_method :default_config
88
+ end
89
+ end
90
+ end