konpeito 0.1.0
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 +7 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +75 -0
- data/CONTRIBUTING.md +123 -0
- data/LICENSE +21 -0
- data/README.md +257 -0
- data/Rakefile +11 -0
- data/bin/konpeito +6 -0
- data/konpeito.gemspec +43 -0
- data/lib/konpeito/ast/typed_ast.rb +620 -0
- data/lib/konpeito/ast/visitor.rb +78 -0
- data/lib/konpeito/cache/cache_manager.rb +230 -0
- data/lib/konpeito/cache/dependency_graph.rb +192 -0
- data/lib/konpeito/cache.rb +8 -0
- data/lib/konpeito/cli/base_command.rb +187 -0
- data/lib/konpeito/cli/build_command.rb +220 -0
- data/lib/konpeito/cli/check_command.rb +104 -0
- data/lib/konpeito/cli/config.rb +231 -0
- data/lib/konpeito/cli/deps_command.rb +128 -0
- data/lib/konpeito/cli/doctor_command.rb +340 -0
- data/lib/konpeito/cli/fmt_command.rb +199 -0
- data/lib/konpeito/cli/init_command.rb +312 -0
- data/lib/konpeito/cli/lsp_command.rb +40 -0
- data/lib/konpeito/cli/run_command.rb +150 -0
- data/lib/konpeito/cli/test_command.rb +248 -0
- data/lib/konpeito/cli/watch_command.rb +212 -0
- data/lib/konpeito/cli.rb +301 -0
- data/lib/konpeito/codegen/builtin_methods.rb +229 -0
- data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
- data/lib/konpeito/codegen/debug_info.rb +352 -0
- data/lib/konpeito/codegen/inliner.rb +486 -0
- data/lib/konpeito/codegen/jvm_backend.rb +197 -0
- data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
- data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
- data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
- data/lib/konpeito/codegen/monomorphizer.rb +359 -0
- data/lib/konpeito/codegen/profile_runtime.c +341 -0
- data/lib/konpeito/codegen/profiler.rb +99 -0
- data/lib/konpeito/compiler.rb +592 -0
- data/lib/konpeito/dependency_resolver.rb +296 -0
- data/lib/konpeito/diagnostics/collector.rb +127 -0
- data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
- data/lib/konpeito/diagnostics/renderer.rb +144 -0
- data/lib/konpeito/formatter/formatter.rb +1214 -0
- data/lib/konpeito/hir/builder.rb +7167 -0
- data/lib/konpeito/hir/nodes.rb +2465 -0
- data/lib/konpeito/lsp/document_manager.rb +820 -0
- data/lib/konpeito/lsp/server.rb +183 -0
- data/lib/konpeito/lsp/transport.rb +38 -0
- data/lib/konpeito/parser/prism_adapter.rb +65 -0
- data/lib/konpeito/platform.rb +103 -0
- data/lib/konpeito/profile/report.rb +136 -0
- data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
- data/lib/konpeito/stdlib/compression/compression.rb +72 -0
- data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
- data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
- data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
- data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
- data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
- data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
- data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
- data/lib/konpeito/stdlib/http/extconf.rb +19 -0
- data/lib/konpeito/stdlib/http/http.rb +125 -0
- data/lib/konpeito/stdlib/http/http.rbs +57 -0
- data/lib/konpeito/stdlib/http/http_native.c +440 -0
- data/lib/konpeito/stdlib/json/extconf.rb +17 -0
- data/lib/konpeito/stdlib/json/json.rb +44 -0
- data/lib/konpeito/stdlib/json/json.rbs +33 -0
- data/lib/konpeito/stdlib/json/json_native.c +286 -0
- data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
- data/lib/konpeito/stdlib/ui/ui.rb +318 -0
- data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
- data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
- data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
- data/lib/konpeito/type_checker/inferrer.rb +565 -0
- data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
- data/lib/konpeito/type_checker/type_resolver.rb +276 -0
- data/lib/konpeito/type_checker/types.rb +1434 -0
- data/lib/konpeito/type_checker/unification.rb +323 -0
- data/lib/konpeito/ui/animation/animated_state.rb +80 -0
- data/lib/konpeito/ui/animation/easing.rb +59 -0
- data/lib/konpeito/ui/animation/value_tween.rb +66 -0
- data/lib/konpeito/ui/app.rb +379 -0
- data/lib/konpeito/ui/box.rb +38 -0
- data/lib/konpeito/ui/castella.rb +70 -0
- data/lib/konpeito/ui/castella_native.rb +76 -0
- data/lib/konpeito/ui/chart/area_chart.rb +305 -0
- data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
- data/lib/konpeito/ui/chart/base_chart.rb +210 -0
- data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
- data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
- data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
- data/lib/konpeito/ui/chart/line_chart.rb +289 -0
- data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
- data/lib/konpeito/ui/chart/scales.rb +77 -0
- data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
- data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
- data/lib/konpeito/ui/column.rb +271 -0
- data/lib/konpeito/ui/core.rb +2199 -0
- data/lib/konpeito/ui/dsl.rb +443 -0
- data/lib/konpeito/ui/frame.rb +171 -0
- data/lib/konpeito/ui/frame_native.rb +494 -0
- data/lib/konpeito/ui/markdown/ast.rb +124 -0
- data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
- data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
- data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
- data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
- data/lib/konpeito/ui/markdown/parser.rb +805 -0
- data/lib/konpeito/ui/markdown/renderer.rb +639 -0
- data/lib/konpeito/ui/markdown/theme.rb +165 -0
- data/lib/konpeito/ui/render_node.rb +260 -0
- data/lib/konpeito/ui/row.rb +207 -0
- data/lib/konpeito/ui/spacer.rb +18 -0
- data/lib/konpeito/ui/style.rb +799 -0
- data/lib/konpeito/ui/theme.rb +563 -0
- data/lib/konpeito/ui/themes/material.rb +35 -0
- data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
- data/lib/konpeito/ui/widgets/button.rb +103 -0
- data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
- data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
- data/lib/konpeito/ui/widgets/container.rb +91 -0
- data/lib/konpeito/ui/widgets/data_table.rb +667 -0
- data/lib/konpeito/ui/widgets/divider.rb +29 -0
- data/lib/konpeito/ui/widgets/image.rb +105 -0
- data/lib/konpeito/ui/widgets/input.rb +485 -0
- data/lib/konpeito/ui/widgets/markdown.rb +57 -0
- data/lib/konpeito/ui/widgets/modal.rb +163 -0
- data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
- data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
- data/lib/konpeito/ui/widgets/net_image.rb +100 -0
- data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
- data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
- data/lib/konpeito/ui/widgets/slider.rb +133 -0
- data/lib/konpeito/ui/widgets/switch.rb +84 -0
- data/lib/konpeito/ui/widgets/tabs.rb +157 -0
- data/lib/konpeito/ui/widgets/text.rb +110 -0
- data/lib/konpeito/ui/widgets/tree.rb +426 -0
- data/lib/konpeito/version.rb +5 -0
- data/lib/konpeito.rb +109 -0
- data/test_native_array.rb +172 -0
- data/test_native_array_class.rb +197 -0
- data/test_native_class.rb +151 -0
- data/tools/konpeito-asm/build.sh +65 -0
- data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
- data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
- data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
- metadata +267 -0
|
@@ -0,0 +1,820 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module Konpeito
|
|
7
|
+
module LSP
|
|
8
|
+
# Manages open documents, their ASTs, and type information
|
|
9
|
+
class DocumentManager
|
|
10
|
+
def initialize(transport)
|
|
11
|
+
@transport = transport
|
|
12
|
+
@documents = {} # uri => DocumentState
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Open a document and analyze it
|
|
16
|
+
# @param uri [String] The document URI
|
|
17
|
+
# @param content [String] The document content
|
|
18
|
+
def open(uri, content)
|
|
19
|
+
analyze(uri, content)
|
|
20
|
+
publish_diagnostics(uri)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Update a document and re-analyze
|
|
24
|
+
# @param uri [String] The document URI
|
|
25
|
+
# @param content [String] The new content
|
|
26
|
+
def change(uri, content)
|
|
27
|
+
analyze(uri, content)
|
|
28
|
+
publish_diagnostics(uri)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Close a document
|
|
32
|
+
# @param uri [String] The document URI
|
|
33
|
+
def close(uri)
|
|
34
|
+
@documents.delete(uri)
|
|
35
|
+
# Clear diagnostics
|
|
36
|
+
@transport.notify("textDocument/publishDiagnostics", {
|
|
37
|
+
uri: uri,
|
|
38
|
+
diagnostics: []
|
|
39
|
+
})
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get document state
|
|
43
|
+
# @param uri [String] The document URI
|
|
44
|
+
# @return [DocumentState, nil]
|
|
45
|
+
def get(uri)
|
|
46
|
+
@documents[uri]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Handle hover request
|
|
50
|
+
# @param uri [String] The document URI
|
|
51
|
+
# @param position [Hash] The position { line:, character: }
|
|
52
|
+
# @return [Hash, nil] Hover response or nil
|
|
53
|
+
def hover(uri, position)
|
|
54
|
+
doc = @documents[uri]
|
|
55
|
+
return nil unless doc&.hm_inferrer
|
|
56
|
+
|
|
57
|
+
# Convert LSP position (0-indexed) to Prism position (1-indexed line)
|
|
58
|
+
line = position[:line] + 1
|
|
59
|
+
column = position[:character]
|
|
60
|
+
|
|
61
|
+
# Find the node at position
|
|
62
|
+
node = find_node_at_position(doc.ast, line, column)
|
|
63
|
+
return nil unless node
|
|
64
|
+
|
|
65
|
+
# Get type information based on node type
|
|
66
|
+
type_string = get_type_info(doc, node)
|
|
67
|
+
return nil unless type_string
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
contents: {
|
|
71
|
+
kind: "markdown",
|
|
72
|
+
value: "```ruby\n#{type_string}\n```"
|
|
73
|
+
},
|
|
74
|
+
range: node_to_lsp_range(node)
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Get type information for a node
|
|
79
|
+
def get_type_info(doc, node)
|
|
80
|
+
hm = doc.hm_inferrer
|
|
81
|
+
|
|
82
|
+
case node
|
|
83
|
+
when Prism::DefNode
|
|
84
|
+
# For method definitions, look up the function type
|
|
85
|
+
func_types = hm.instance_variable_get(:@function_types)
|
|
86
|
+
func_type = func_types[node.name]
|
|
87
|
+
if func_type
|
|
88
|
+
final_type = hm.finalize(func_type)
|
|
89
|
+
"def #{node.name}: #{format_type(final_type)}"
|
|
90
|
+
else
|
|
91
|
+
"def #{node.name}"
|
|
92
|
+
end
|
|
93
|
+
when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode
|
|
94
|
+
# For local variables, look up from environment
|
|
95
|
+
var_name = node.name
|
|
96
|
+
env = hm.instance_variable_get(:@env).first
|
|
97
|
+
scheme = env[var_name]
|
|
98
|
+
if scheme
|
|
99
|
+
final_type = hm.finalize(scheme.type)
|
|
100
|
+
"#{var_name}: #{format_type(final_type)}"
|
|
101
|
+
else
|
|
102
|
+
type = hm.type_for(node)
|
|
103
|
+
type ? format_type(hm.finalize(type)) : nil
|
|
104
|
+
end
|
|
105
|
+
when Prism::CallNode
|
|
106
|
+
# For method calls, show the return type
|
|
107
|
+
type = hm.type_for(node)
|
|
108
|
+
if type
|
|
109
|
+
final_type = hm.finalize(type)
|
|
110
|
+
format_type(final_type)
|
|
111
|
+
end
|
|
112
|
+
else
|
|
113
|
+
# Default: try to get type from node
|
|
114
|
+
type = hm.type_for(node)
|
|
115
|
+
if type
|
|
116
|
+
final_type = hm.finalize(type)
|
|
117
|
+
format_type(final_type)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Handle completion request
|
|
123
|
+
# @param uri [String] The document URI
|
|
124
|
+
# @param position [Hash] The position { line:, character: }
|
|
125
|
+
# @return [Hash] Completion response
|
|
126
|
+
def completion(uri, position)
|
|
127
|
+
doc = @documents[uri]
|
|
128
|
+
return { isIncomplete: false, items: [] } unless doc
|
|
129
|
+
|
|
130
|
+
line = position[:line] + 1
|
|
131
|
+
column = position[:character]
|
|
132
|
+
|
|
133
|
+
# Get the text before cursor to determine completion context
|
|
134
|
+
lines = doc.content.split("\n")
|
|
135
|
+
current_line = lines[position[:line]] || ""
|
|
136
|
+
text_before_cursor = current_line[0...column]
|
|
137
|
+
|
|
138
|
+
items = []
|
|
139
|
+
|
|
140
|
+
# Check if we're completing after a "."
|
|
141
|
+
if text_before_cursor =~ /(\w+)\.\s*$/
|
|
142
|
+
receiver_name = $1
|
|
143
|
+
items = complete_methods_for_receiver(doc, receiver_name, line, column)
|
|
144
|
+
else
|
|
145
|
+
# Complete variables and methods from scope
|
|
146
|
+
items = complete_from_scope(doc)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
{ isIncomplete: false, items: items }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Handle definition request (go to definition)
|
|
153
|
+
# @param uri [String] The document URI
|
|
154
|
+
# @param position [Hash] The position { line:, character: }
|
|
155
|
+
# @return [Hash, Array, nil] Location or array of locations
|
|
156
|
+
def definition(uri, position)
|
|
157
|
+
doc = @documents[uri]
|
|
158
|
+
return nil unless doc&.ast
|
|
159
|
+
|
|
160
|
+
line = position[:line] + 1
|
|
161
|
+
column = position[:character]
|
|
162
|
+
|
|
163
|
+
# Find the node at position
|
|
164
|
+
node = find_node_at_position(doc.ast, line, column)
|
|
165
|
+
return nil unless node
|
|
166
|
+
|
|
167
|
+
# Find the definition based on node type
|
|
168
|
+
case node
|
|
169
|
+
when Prism::CallNode
|
|
170
|
+
# Find method definition
|
|
171
|
+
method_name = node.name
|
|
172
|
+
def_node = find_method_definition(doc.ast, method_name)
|
|
173
|
+
if def_node
|
|
174
|
+
{
|
|
175
|
+
uri: uri,
|
|
176
|
+
range: node_to_lsp_range(def_node)
|
|
177
|
+
}
|
|
178
|
+
end
|
|
179
|
+
when Prism::LocalVariableReadNode
|
|
180
|
+
# Find variable assignment
|
|
181
|
+
var_name = node.name
|
|
182
|
+
write_node = find_variable_definition(doc.ast, var_name, line)
|
|
183
|
+
if write_node
|
|
184
|
+
{
|
|
185
|
+
uri: uri,
|
|
186
|
+
range: node_to_lsp_range(write_node)
|
|
187
|
+
}
|
|
188
|
+
end
|
|
189
|
+
else
|
|
190
|
+
nil
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Handle references request (find all references)
|
|
195
|
+
# @param uri [String] The document URI
|
|
196
|
+
# @param position [Hash] The position { line:, character: }
|
|
197
|
+
# @param include_declaration [Boolean] Whether to include the declaration
|
|
198
|
+
# @return [Array<Hash>] Array of Location objects
|
|
199
|
+
def references(uri, position, include_declaration: true)
|
|
200
|
+
doc = @documents[uri]
|
|
201
|
+
return [] unless doc&.ast
|
|
202
|
+
|
|
203
|
+
line = position[:line] + 1
|
|
204
|
+
column = position[:character]
|
|
205
|
+
|
|
206
|
+
# Find the node at position
|
|
207
|
+
node = find_node_at_position(doc.ast, line, column)
|
|
208
|
+
return [] unless node
|
|
209
|
+
|
|
210
|
+
# Find all references based on node type
|
|
211
|
+
case node
|
|
212
|
+
when Prism::CallNode
|
|
213
|
+
# Find all calls to this method
|
|
214
|
+
method_name = node.name
|
|
215
|
+
finder = ReferenceFinder.new(method_name, :method)
|
|
216
|
+
finder.visit(doc.ast)
|
|
217
|
+
locations = finder.found_nodes.map do |ref_node|
|
|
218
|
+
{ uri: uri, range: node_to_lsp_range(ref_node) }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Include definition if requested
|
|
222
|
+
if include_declaration
|
|
223
|
+
def_node = find_method_definition(doc.ast, method_name)
|
|
224
|
+
if def_node
|
|
225
|
+
locations.unshift({ uri: uri, range: node_to_lsp_range(def_node) })
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
locations
|
|
230
|
+
when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode
|
|
231
|
+
# Find all usages of this variable
|
|
232
|
+
var_name = node.name
|
|
233
|
+
finder = ReferenceFinder.new(var_name, :variable)
|
|
234
|
+
finder.visit(doc.ast)
|
|
235
|
+
finder.found_nodes.map do |ref_node|
|
|
236
|
+
{ uri: uri, range: node_to_lsp_range(ref_node) }
|
|
237
|
+
end
|
|
238
|
+
when Prism::DefNode
|
|
239
|
+
# Find all calls to this method
|
|
240
|
+
method_name = node.name
|
|
241
|
+
finder = ReferenceFinder.new(method_name, :method)
|
|
242
|
+
finder.visit(doc.ast)
|
|
243
|
+
locations = finder.found_nodes.map do |ref_node|
|
|
244
|
+
{ uri: uri, range: node_to_lsp_range(ref_node) }
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Include the definition itself if requested
|
|
248
|
+
if include_declaration
|
|
249
|
+
locations.unshift({ uri: uri, range: node_to_lsp_range(node) })
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
locations
|
|
253
|
+
else
|
|
254
|
+
[]
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Handle rename request
|
|
259
|
+
# @param uri [String] The document URI
|
|
260
|
+
# @param position [Hash] The position { line:, character: }
|
|
261
|
+
# @param new_name [String] The new name for the symbol
|
|
262
|
+
# @return [Hash, nil] WorkspaceEdit or nil if rename not possible
|
|
263
|
+
def rename(uri, position, new_name)
|
|
264
|
+
doc = @documents[uri]
|
|
265
|
+
return nil unless doc&.ast
|
|
266
|
+
|
|
267
|
+
line = position[:line] + 1
|
|
268
|
+
column = position[:character]
|
|
269
|
+
|
|
270
|
+
# Find the node at position
|
|
271
|
+
node = find_node_at_position(doc.ast, line, column)
|
|
272
|
+
return nil unless node
|
|
273
|
+
|
|
274
|
+
# Get the current name and find all references
|
|
275
|
+
current_name, refs = case node
|
|
276
|
+
when Prism::CallNode
|
|
277
|
+
method_name = node.name
|
|
278
|
+
finder = ReferenceFinder.new(method_name, :method)
|
|
279
|
+
finder.visit(doc.ast)
|
|
280
|
+
|
|
281
|
+
# Also include the definition
|
|
282
|
+
def_node = find_method_definition(doc.ast, method_name)
|
|
283
|
+
refs = finder.found_nodes.dup
|
|
284
|
+
refs.unshift(def_node) if def_node
|
|
285
|
+
|
|
286
|
+
[method_name.to_s, refs]
|
|
287
|
+
when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode
|
|
288
|
+
var_name = node.name
|
|
289
|
+
finder = ReferenceFinder.new(var_name, :variable)
|
|
290
|
+
finder.visit(doc.ast)
|
|
291
|
+
[var_name.to_s, finder.found_nodes]
|
|
292
|
+
when Prism::DefNode
|
|
293
|
+
method_name = node.name
|
|
294
|
+
finder = ReferenceFinder.new(method_name, :method)
|
|
295
|
+
finder.visit(doc.ast)
|
|
296
|
+
|
|
297
|
+
# Include the definition itself
|
|
298
|
+
refs = [node] + finder.found_nodes
|
|
299
|
+
|
|
300
|
+
[method_name.to_s, refs]
|
|
301
|
+
else
|
|
302
|
+
return nil
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
return nil if refs.empty?
|
|
306
|
+
|
|
307
|
+
# Build text edits - calculate the range for just the name part
|
|
308
|
+
text_edits = refs.map do |ref_node|
|
|
309
|
+
range = name_range_for_node(ref_node, current_name)
|
|
310
|
+
{ range: range, newText: new_name }
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Return WorkspaceEdit
|
|
314
|
+
{
|
|
315
|
+
changes: {
|
|
316
|
+
uri => text_edits
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Prepare rename - check if rename is valid and return the current name range
|
|
322
|
+
# @param uri [String] The document URI
|
|
323
|
+
# @param position [Hash] The position { line:, character: }
|
|
324
|
+
# @return [Hash, nil] Range and placeholder text
|
|
325
|
+
def prepare_rename(uri, position)
|
|
326
|
+
doc = @documents[uri]
|
|
327
|
+
return nil unless doc&.ast
|
|
328
|
+
|
|
329
|
+
line = position[:line] + 1
|
|
330
|
+
column = position[:character]
|
|
331
|
+
|
|
332
|
+
node = find_node_at_position(doc.ast, line, column)
|
|
333
|
+
return nil unless node
|
|
334
|
+
|
|
335
|
+
case node
|
|
336
|
+
when Prism::CallNode
|
|
337
|
+
name = node.name.to_s
|
|
338
|
+
{ range: name_range_for_node(node, name), placeholder: name }
|
|
339
|
+
when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode
|
|
340
|
+
name = node.name.to_s
|
|
341
|
+
{ range: name_range_for_node(node, name), placeholder: name }
|
|
342
|
+
when Prism::DefNode
|
|
343
|
+
name = node.name.to_s
|
|
344
|
+
{ range: name_range_for_node(node, name), placeholder: name }
|
|
345
|
+
else
|
|
346
|
+
nil
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
private
|
|
351
|
+
|
|
352
|
+
# Get the LSP range for just the name part of a node
|
|
353
|
+
def name_range_for_node(node, name)
|
|
354
|
+
case node
|
|
355
|
+
when Prism::DefNode
|
|
356
|
+
# For def nodes, the name starts after "def "
|
|
357
|
+
loc = node.name_loc
|
|
358
|
+
{
|
|
359
|
+
start: { line: loc.start_line - 1, character: loc.start_column },
|
|
360
|
+
end: { line: loc.end_line - 1, character: loc.end_column }
|
|
361
|
+
}
|
|
362
|
+
when Prism::CallNode
|
|
363
|
+
# For call nodes, use the message_loc
|
|
364
|
+
loc = node.message_loc
|
|
365
|
+
if loc
|
|
366
|
+
{
|
|
367
|
+
start: { line: loc.start_line - 1, character: loc.start_column },
|
|
368
|
+
end: { line: loc.end_line - 1, character: loc.end_column }
|
|
369
|
+
}
|
|
370
|
+
else
|
|
371
|
+
node_to_lsp_range(node)
|
|
372
|
+
end
|
|
373
|
+
when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode
|
|
374
|
+
# For variables, the whole node is the name
|
|
375
|
+
loc = node.location
|
|
376
|
+
{
|
|
377
|
+
start: { line: loc.start_line - 1, character: loc.start_column },
|
|
378
|
+
end: { line: loc.start_line - 1, character: loc.start_column + name.length }
|
|
379
|
+
}
|
|
380
|
+
else
|
|
381
|
+
node_to_lsp_range(node)
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# Complete methods based on receiver type
|
|
386
|
+
def complete_methods_for_receiver(doc, receiver_name, line, column)
|
|
387
|
+
return [] unless doc.hm_inferrer
|
|
388
|
+
|
|
389
|
+
hm = doc.hm_inferrer
|
|
390
|
+
env = hm.instance_variable_get(:@env).first
|
|
391
|
+
scheme = env[receiver_name.to_sym]
|
|
392
|
+
|
|
393
|
+
return [] unless scheme
|
|
394
|
+
|
|
395
|
+
type = hm.finalize(scheme.type)
|
|
396
|
+
types_mod = TypeChecker::Types
|
|
397
|
+
|
|
398
|
+
items = []
|
|
399
|
+
|
|
400
|
+
case type
|
|
401
|
+
when types_mod::ClassInstance
|
|
402
|
+
# Get methods for this class from RBS or built-in
|
|
403
|
+
methods = get_methods_for_type(type.name)
|
|
404
|
+
items = methods.map do |method_name, signature|
|
|
405
|
+
{
|
|
406
|
+
label: method_name.to_s,
|
|
407
|
+
kind: 2, # Method
|
|
408
|
+
detail: signature
|
|
409
|
+
}
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
items
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Complete variables and methods from current scope
|
|
417
|
+
def complete_from_scope(doc)
|
|
418
|
+
items = []
|
|
419
|
+
|
|
420
|
+
if doc.hm_inferrer
|
|
421
|
+
hm = doc.hm_inferrer
|
|
422
|
+
env = hm.instance_variable_get(:@env).first
|
|
423
|
+
func_types = hm.instance_variable_get(:@function_types)
|
|
424
|
+
|
|
425
|
+
# Add variables
|
|
426
|
+
env.each do |name, scheme|
|
|
427
|
+
type = hm.finalize(scheme.type)
|
|
428
|
+
kind = type.is_a?(TypeChecker::FunctionType) ? 3 : 6 # Function or Variable
|
|
429
|
+
items << {
|
|
430
|
+
label: name.to_s,
|
|
431
|
+
kind: kind,
|
|
432
|
+
detail: format_type(type)
|
|
433
|
+
}
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# Add function definitions
|
|
437
|
+
func_types.each do |name, func_type|
|
|
438
|
+
next if env.key?(name) # Already added
|
|
439
|
+
items << {
|
|
440
|
+
label: name.to_s,
|
|
441
|
+
kind: 3, # Function
|
|
442
|
+
detail: format_type(hm.finalize(func_type))
|
|
443
|
+
}
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
items
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# Get methods for a type (from built-in knowledge)
|
|
451
|
+
def get_methods_for_type(type_name)
|
|
452
|
+
# Basic built-in methods for common types
|
|
453
|
+
case type_name.to_sym
|
|
454
|
+
when :Integer
|
|
455
|
+
{
|
|
456
|
+
to_s: "() -> String",
|
|
457
|
+
to_f: "() -> Float",
|
|
458
|
+
abs: "() -> Integer",
|
|
459
|
+
times: "{ (Integer) -> void } -> Integer"
|
|
460
|
+
}
|
|
461
|
+
when :Float
|
|
462
|
+
{
|
|
463
|
+
to_s: "() -> String",
|
|
464
|
+
to_i: "() -> Integer",
|
|
465
|
+
abs: "() -> Float",
|
|
466
|
+
round: "() -> Integer",
|
|
467
|
+
floor: "() -> Integer",
|
|
468
|
+
ceil: "() -> Integer"
|
|
469
|
+
}
|
|
470
|
+
when :String
|
|
471
|
+
{
|
|
472
|
+
to_s: "() -> String",
|
|
473
|
+
to_i: "() -> Integer",
|
|
474
|
+
length: "() -> Integer",
|
|
475
|
+
size: "() -> Integer",
|
|
476
|
+
upcase: "() -> String",
|
|
477
|
+
downcase: "() -> String",
|
|
478
|
+
strip: "() -> String"
|
|
479
|
+
}
|
|
480
|
+
when :Array
|
|
481
|
+
{
|
|
482
|
+
length: "() -> Integer",
|
|
483
|
+
size: "() -> Integer",
|
|
484
|
+
first: "() -> T",
|
|
485
|
+
last: "() -> T",
|
|
486
|
+
push: "(T) -> Array",
|
|
487
|
+
pop: "() -> T",
|
|
488
|
+
map: "{ (T) -> U } -> Array[U]",
|
|
489
|
+
each: "{ (T) -> void } -> Array"
|
|
490
|
+
}
|
|
491
|
+
when :Hash
|
|
492
|
+
{
|
|
493
|
+
keys: "() -> Array",
|
|
494
|
+
values: "() -> Array",
|
|
495
|
+
size: "() -> Integer",
|
|
496
|
+
empty?: "() -> bool"
|
|
497
|
+
}
|
|
498
|
+
else
|
|
499
|
+
{}
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# Find method definition in AST
|
|
504
|
+
def find_method_definition(ast, method_name)
|
|
505
|
+
finder = DefinitionFinder.new(method_name, :method)
|
|
506
|
+
finder.visit(ast)
|
|
507
|
+
finder.found_node
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# Find variable definition (first assignment)
|
|
511
|
+
def find_variable_definition(ast, var_name, before_line)
|
|
512
|
+
finder = DefinitionFinder.new(var_name, :variable, before_line)
|
|
513
|
+
finder.visit(ast)
|
|
514
|
+
finder.found_node
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# Analyze a document and store results
|
|
518
|
+
def analyze(uri, content)
|
|
519
|
+
file_path = uri_to_path(uri)
|
|
520
|
+
|
|
521
|
+
# Parse with Prism
|
|
522
|
+
parse_result = Prism.parse(content)
|
|
523
|
+
ast = parse_result.value
|
|
524
|
+
parse_errors = parse_result.errors
|
|
525
|
+
|
|
526
|
+
# Initialize RBS loader
|
|
527
|
+
rbs_loader = TypeChecker::RBSLoader.new
|
|
528
|
+
|
|
529
|
+
# Load RBS files if they exist
|
|
530
|
+
rbs_path = file_path.sub(/\.rb$/, ".rbs")
|
|
531
|
+
if File.exist?(rbs_path)
|
|
532
|
+
rbs_loader.load(rbs_paths: [rbs_path])
|
|
533
|
+
else
|
|
534
|
+
rbs_loader.load(rbs_paths: [])
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
# Build typed AST with HM inference
|
|
538
|
+
builder = AST::TypedASTBuilder.new(
|
|
539
|
+
rbs_loader,
|
|
540
|
+
use_hm: true,
|
|
541
|
+
file_path: file_path,
|
|
542
|
+
source: content
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
typed_ast = nil
|
|
546
|
+
hm_inferrer = nil
|
|
547
|
+
diagnostics = []
|
|
548
|
+
|
|
549
|
+
begin
|
|
550
|
+
typed_ast = builder.build(ast)
|
|
551
|
+
hm_inferrer = builder.instance_variable_get(:@hm_inferrer)
|
|
552
|
+
diagnostics = builder.diagnostics
|
|
553
|
+
rescue StandardError => e
|
|
554
|
+
# If type checking fails, still store the AST
|
|
555
|
+
diagnostics << Diagnostics::Diagnostic.new(
|
|
556
|
+
severity: :error,
|
|
557
|
+
code: "E100",
|
|
558
|
+
message: "Type checking error: #{e.message}"
|
|
559
|
+
)
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
# Add parse errors to diagnostics
|
|
563
|
+
parse_errors.each do |error|
|
|
564
|
+
span = if error.location
|
|
565
|
+
Diagnostics::SourceSpan.new(
|
|
566
|
+
file_path: file_path,
|
|
567
|
+
start_line: error.location.start_line,
|
|
568
|
+
start_column: error.location.start_column,
|
|
569
|
+
end_line: error.location.end_line,
|
|
570
|
+
end_column: error.location.end_column,
|
|
571
|
+
source: content
|
|
572
|
+
)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
diagnostics << Diagnostics::Diagnostic.new(
|
|
576
|
+
severity: :error,
|
|
577
|
+
code: "E010",
|
|
578
|
+
message: error.message,
|
|
579
|
+
span: span
|
|
580
|
+
)
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
@documents[uri] = DocumentState.new(
|
|
584
|
+
content: content,
|
|
585
|
+
ast: ast,
|
|
586
|
+
typed_ast: typed_ast,
|
|
587
|
+
hm_inferrer: hm_inferrer,
|
|
588
|
+
diagnostics: diagnostics
|
|
589
|
+
)
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
# Publish diagnostics to the client
|
|
593
|
+
def publish_diagnostics(uri)
|
|
594
|
+
doc = @documents[uri]
|
|
595
|
+
return unless doc
|
|
596
|
+
|
|
597
|
+
lsp_diagnostics = doc.diagnostics.map do |diag|
|
|
598
|
+
{
|
|
599
|
+
range: span_to_lsp_range(diag.span),
|
|
600
|
+
severity: severity_to_lsp(diag.severity),
|
|
601
|
+
code: diag.code,
|
|
602
|
+
source: "konpeito",
|
|
603
|
+
message: diag.message
|
|
604
|
+
}
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
@transport.notify("textDocument/publishDiagnostics", {
|
|
608
|
+
uri: uri,
|
|
609
|
+
diagnostics: lsp_diagnostics
|
|
610
|
+
})
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
# Convert file URI to path
|
|
614
|
+
def uri_to_path(uri)
|
|
615
|
+
if uri.start_with?("file://")
|
|
616
|
+
URI.decode_www_form_component(uri.sub("file://", ""))
|
|
617
|
+
else
|
|
618
|
+
uri
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
# Convert diagnostic severity to LSP severity
|
|
623
|
+
def severity_to_lsp(severity)
|
|
624
|
+
case severity
|
|
625
|
+
when :error then 1
|
|
626
|
+
when :warning then 2
|
|
627
|
+
when :note, :info then 3
|
|
628
|
+
when :help, :hint then 4
|
|
629
|
+
else 1
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
# Convert SourceSpan to LSP range
|
|
634
|
+
def span_to_lsp_range(span)
|
|
635
|
+
return { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } unless span
|
|
636
|
+
|
|
637
|
+
{
|
|
638
|
+
start: { line: span.start_line - 1, character: span.start_column },
|
|
639
|
+
end: { line: (span.end_line || span.start_line) - 1, character: span.end_column || span.start_column }
|
|
640
|
+
}
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
# Convert Prism node to LSP range
|
|
644
|
+
def node_to_lsp_range(node)
|
|
645
|
+
return nil unless node.respond_to?(:location) && node.location
|
|
646
|
+
|
|
647
|
+
loc = node.location
|
|
648
|
+
{
|
|
649
|
+
start: { line: loc.start_line - 1, character: loc.start_column },
|
|
650
|
+
end: { line: loc.end_line - 1, character: loc.end_column }
|
|
651
|
+
}
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
# Find the most specific node at a given position
|
|
655
|
+
def find_node_at_position(ast, line, column)
|
|
656
|
+
return nil unless ast
|
|
657
|
+
|
|
658
|
+
visitor = NodeFinder.new(line, column)
|
|
659
|
+
visitor.visit(ast)
|
|
660
|
+
visitor.found_node
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
# Format type for display
|
|
664
|
+
def format_type(type)
|
|
665
|
+
types_mod = TypeChecker::Types
|
|
666
|
+
|
|
667
|
+
case type
|
|
668
|
+
when TypeChecker::TypeVar
|
|
669
|
+
type.instance ? format_type(type.instance) : type.to_s
|
|
670
|
+
when TypeChecker::FunctionType
|
|
671
|
+
params = type.param_types.map { |t| format_type(t) }.join(", ")
|
|
672
|
+
"(#{params}) -> #{format_type(type.return_type)}"
|
|
673
|
+
when types_mod::ClassInstance
|
|
674
|
+
if type.type_args.empty?
|
|
675
|
+
type.name.to_s
|
|
676
|
+
else
|
|
677
|
+
args = type.type_args.map { |a| format_type(a) }.join(", ")
|
|
678
|
+
"#{type.name}[#{args}]"
|
|
679
|
+
end
|
|
680
|
+
when types_mod::Union
|
|
681
|
+
type.types.map { |t| format_type(t) }.join(" | ")
|
|
682
|
+
when types_mod::NativeClassType
|
|
683
|
+
"#{type.class_name} (native)"
|
|
684
|
+
else
|
|
685
|
+
type.to_s
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
# Document state container
|
|
690
|
+
DocumentState = Struct.new(
|
|
691
|
+
:content,
|
|
692
|
+
:ast,
|
|
693
|
+
:typed_ast,
|
|
694
|
+
:hm_inferrer,
|
|
695
|
+
:diagnostics,
|
|
696
|
+
keyword_init: true
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
# Visitor to find node at position
|
|
700
|
+
class NodeFinder
|
|
701
|
+
attr_reader :found_node
|
|
702
|
+
|
|
703
|
+
def initialize(line, column)
|
|
704
|
+
@target_line = line
|
|
705
|
+
@target_column = column
|
|
706
|
+
@found_node = nil
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
def visit(node)
|
|
710
|
+
return unless node
|
|
711
|
+
|
|
712
|
+
# Check if this node contains the target position
|
|
713
|
+
if node.respond_to?(:location) && node.location && position_in_range?(node.location)
|
|
714
|
+
@found_node = node
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
# Visit all child nodes
|
|
718
|
+
visit_children(node)
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
private
|
|
722
|
+
|
|
723
|
+
def visit_children(node)
|
|
724
|
+
node.child_nodes.each do |child|
|
|
725
|
+
visit(child) if child
|
|
726
|
+
end
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
def position_in_range?(loc)
|
|
730
|
+
start_ok = loc.start_line < @target_line ||
|
|
731
|
+
(loc.start_line == @target_line && loc.start_column <= @target_column)
|
|
732
|
+
end_ok = loc.end_line > @target_line ||
|
|
733
|
+
(loc.end_line == @target_line && loc.end_column >= @target_column)
|
|
734
|
+
start_ok && end_ok
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
# Visitor to find definition of a method or variable
|
|
739
|
+
class DefinitionFinder
|
|
740
|
+
attr_reader :found_node
|
|
741
|
+
|
|
742
|
+
def initialize(name, kind, before_line = nil)
|
|
743
|
+
@name = name.to_sym
|
|
744
|
+
@kind = kind
|
|
745
|
+
@before_line = before_line
|
|
746
|
+
@found_node = nil
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
def visit(node)
|
|
750
|
+
return unless node
|
|
751
|
+
|
|
752
|
+
case @kind
|
|
753
|
+
when :method
|
|
754
|
+
if node.is_a?(Prism::DefNode) && node.name == @name
|
|
755
|
+
@found_node = node
|
|
756
|
+
end
|
|
757
|
+
when :variable
|
|
758
|
+
if node.is_a?(Prism::LocalVariableWriteNode) && node.name == @name
|
|
759
|
+
# Only find definitions before the reference line
|
|
760
|
+
if @before_line.nil? || node.location.start_line < @before_line
|
|
761
|
+
@found_node ||= node # Keep first occurrence
|
|
762
|
+
end
|
|
763
|
+
end
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
# Visit children
|
|
767
|
+
visit_children(node)
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
private
|
|
771
|
+
|
|
772
|
+
def visit_children(node)
|
|
773
|
+
node.child_nodes.each do |child|
|
|
774
|
+
visit(child) if child
|
|
775
|
+
end
|
|
776
|
+
end
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
# Finds all references to a symbol (variable or method)
|
|
780
|
+
class ReferenceFinder
|
|
781
|
+
attr_reader :found_nodes
|
|
782
|
+
|
|
783
|
+
def initialize(name, kind)
|
|
784
|
+
@name = name.to_sym
|
|
785
|
+
@kind = kind
|
|
786
|
+
@found_nodes = []
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
def visit(node)
|
|
790
|
+
return unless node
|
|
791
|
+
|
|
792
|
+
case @kind
|
|
793
|
+
when :method
|
|
794
|
+
# Find method calls
|
|
795
|
+
if node.is_a?(Prism::CallNode) && node.name == @name
|
|
796
|
+
@found_nodes << node
|
|
797
|
+
end
|
|
798
|
+
when :variable
|
|
799
|
+
# Find variable reads and writes
|
|
800
|
+
if (node.is_a?(Prism::LocalVariableReadNode) || node.is_a?(Prism::LocalVariableWriteNode)) &&
|
|
801
|
+
node.name == @name
|
|
802
|
+
@found_nodes << node
|
|
803
|
+
end
|
|
804
|
+
end
|
|
805
|
+
|
|
806
|
+
# Visit children
|
|
807
|
+
visit_children(node)
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
private
|
|
811
|
+
|
|
812
|
+
def visit_children(node)
|
|
813
|
+
node.child_nodes.each do |child|
|
|
814
|
+
visit(child) if child
|
|
815
|
+
end
|
|
816
|
+
end
|
|
817
|
+
end
|
|
818
|
+
end
|
|
819
|
+
end
|
|
820
|
+
end
|