rubydex 0.2.3-aarch64-linux → 0.2.5-aarch64-linux

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/THIRD_PARTY_LICENSES.html +2 -2
  3. data/exe/rubydex_mcp +17 -0
  4. data/ext/rubydex/definition.c +56 -0
  5. data/ext/rubydex/extconf.rb +8 -0
  6. data/lib/rubydex/3.2/rubydex.so +0 -0
  7. data/lib/rubydex/3.3/rubydex.so +0 -0
  8. data/lib/rubydex/3.4/rubydex.so +0 -0
  9. data/lib/rubydex/4.0/rubydex.so +0 -0
  10. data/lib/rubydex/bin/rubydex_mcp +0 -0
  11. data/lib/rubydex/declaration.rb +12 -0
  12. data/lib/rubydex/graph.rb +3 -1
  13. data/lib/rubydex/librubydex_sys.so +0 -0
  14. data/lib/rubydex/version.rb +1 -1
  15. data/rbi/rubydex.rbi +14 -1
  16. data/rust/Cargo.lock +4 -4
  17. data/rust/rubydex/src/indexing/local_graph.rs +38 -0
  18. data/rust/rubydex/src/indexing/ruby_indexer.rs +6 -0
  19. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +5 -1
  20. data/rust/rubydex/src/indexing.rs +38 -12
  21. data/rust/rubydex/src/lib.rs +1 -0
  22. data/rust/rubydex/src/listing.rs +14 -3
  23. data/rust/rubydex/src/main.rs +27 -2
  24. data/rust/rubydex/src/model/definitions.rs +7 -18
  25. data/rust/rubydex/src/model/graph.rs +21 -4
  26. data/rust/rubydex/src/model/ids.rs +27 -1
  27. data/rust/rubydex/src/operation/applier.rs +519 -0
  28. data/rust/rubydex/src/operation/mod.rs +284 -0
  29. data/rust/rubydex/src/operation/printer.rs +260 -0
  30. data/rust/rubydex/src/operation/ruby_builder.rs +2915 -0
  31. data/rust/rubydex/src/resolution.rs +6 -1
  32. data/rust/rubydex/src/resolution_tests.rs +217 -209
  33. data/rust/rubydex/src/test_utils/graph_test.rs +19 -4
  34. data/rust/rubydex/src/test_utils/local_graph_test.rs +7 -6
  35. data/rust/rubydex-mcp/Cargo.toml +1 -1
  36. data/rust/rubydex-mcp/src/server.rs +10 -7
  37. data/rust/rubydex-sys/src/definition_api.rs +24 -0
  38. data/rust/rubydex-sys/src/graph_api.rs +1 -1
  39. metadata +9 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb4dc307c0f77d9a898130008b29f531a7d96bb56f03dc0f97c776485463c83f
4
- data.tar.gz: 5837fc152fdfc40d4925b76c299f59d4e1a0800450cf51cbf76dc4b0d7b0729a
3
+ metadata.gz: 96c428371246628899d384b7e650919ee41c9eaa42413af5244175c9972fb44b
4
+ data.tar.gz: 702b06d961ffdd5da12dcb7eb074f83b4d2bc30d3e06bc29a4749a39d0501d58
5
5
  SHA512:
6
- metadata.gz: 98a6ca55ab2df591692f2ad9b464846f39e7d1d444c63bd228875565369cdd4d53f7ab0424989b338da5bdc6e59f86d8fb32357b530232b174c20e6f2113bbed
7
- data.tar.gz: b7c9a8504e76a6a0d2d36cc057938a816b0fbb2a8b048a6289030c838d25876cba7ccf066260f8d36c639dcbe6e0c6a26f5d1253eb46819294253bac9c8c1a62
6
+ metadata.gz: d9aed2a16df08353c7b66d1dbfb652746f2241c4c6782fb714d89c4ae9b64487e552c5f0b418bfd36b6d01a1477f2cc445e8be68ce0083501edad77220f9b8f8
7
+ data.tar.gz: c6f95989ccb6da1bed31659e052f5b83ac8b2870c24d2ca59ac10003b60dc199cba778e0f67188f58389f6dd7fddae295ded50b3ad1f9e4fbb2b720526a2ccd9
@@ -2957,10 +2957,10 @@ limitations under the License.
2957
2957
  1.0.25</a></li>
2958
2958
  <li><a
2959
2959
  href=" https://github.com/modelcontextprotocol/rust-sdk/ ">rmcp-macros
2960
- 0.15.0</a></li>
2960
+ 1.6.0</a></li>
2961
2961
  <li><a
2962
2962
  href=" https://github.com/modelcontextprotocol/rust-sdk/ ">rmcp
2963
- 0.15.0</a></li>
2963
+ 1.4.0</a></li>
2964
2964
  <li><a
2965
2965
  href=" https://github.com/rust-lang/rustc-hash ">rustc-hash
2966
2966
  2.1.1</a></li>
data/exe/rubydex_mcp ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "rbconfig"
5
+
6
+ host_os = RbConfig::CONFIG.fetch("host_os")
7
+ executable = host_os.match?(/mswin|mingw|cygwin/) ? "rubydex_mcp.exe" : "rubydex_mcp"
8
+ binary = File.expand_path("../lib/rubydex/bin/#{executable}", __dir__)
9
+
10
+ unless File.executable?(binary)
11
+ abort(<<~MESSAGE.chomp)
12
+ rubydex_mcp is not available at #{binary}.
13
+ Install a precompiled rubydex gem, or reinstall rubydex with Cargo available so the MCP executable can be built locally.
14
+ MESSAGE
15
+ end
16
+
17
+ exec(binary, *ARGV)
@@ -195,6 +195,60 @@ static VALUE rdxr_definition_declaration(VALUE self) {
195
195
  return rb_class_new_instance(2, argv, decl_class);
196
196
  }
197
197
 
198
+ static VALUE rdxi_build_definition(VALUE graph_obj, void *graph, uint64_t definition_id) {
199
+ DefinitionKind kind = rdx_definition_kind(graph, definition_id);
200
+ VALUE defn_class = rdxi_definition_class_for_kind(kind);
201
+ VALUE argv[] = {graph_obj, ULL2NUM(definition_id)};
202
+
203
+ return rb_class_new_instance(2, argv, defn_class);
204
+ }
205
+
206
+ // Definition#lexical_owner -> Rubydex::Definition?
207
+ // Returns the lexically enclosing definition, if any.
208
+ static VALUE rdxr_definition_lexical_owner(VALUE self) {
209
+ HandleData *data;
210
+ TypedData_Get_Struct(self, HandleData, &handle_type, data);
211
+
212
+ void *graph;
213
+ TypedData_Get_Struct(data->graph_obj, void *, &graph_type, graph);
214
+
215
+ const uint64_t *owner_id = rdx_definition_lexical_nesting_id(graph, data->id);
216
+ if (owner_id == NULL) {
217
+ return Qnil;
218
+ }
219
+
220
+ VALUE owner = rdxi_build_definition(data->graph_obj, graph, *owner_id);
221
+ free_u64(owner_id);
222
+
223
+ return owner;
224
+ }
225
+
226
+ // Definition#lexical_nesting -> Array<Rubydex::Definition>
227
+ // Returns the lexical nesting from the direct owner up to the root.
228
+ static VALUE rdxr_definition_lexical_nesting(VALUE self) {
229
+ HandleData *data;
230
+ TypedData_Get_Struct(self, HandleData, &handle_type, data);
231
+
232
+ void *graph;
233
+ TypedData_Get_Struct(data->graph_obj, void *, &graph_type, graph);
234
+
235
+ VALUE nesting = rb_ary_new();
236
+ uint64_t definition_id = data->id;
237
+
238
+ while (true) {
239
+ const uint64_t *owner_id = rdx_definition_lexical_nesting_id(graph, definition_id);
240
+ if (owner_id == NULL) {
241
+ break;
242
+ }
243
+
244
+ rb_ary_push(nesting, rdxi_build_definition(data->graph_obj, graph, *owner_id));
245
+ definition_id = *owner_id;
246
+ free_u64(owner_id);
247
+ }
248
+
249
+ return nesting;
250
+ }
251
+
198
252
  static VALUE rdxi_build_constant_reference(VALUE graph_obj, const CConstantReference *cref) {
199
253
  VALUE ref_class = (cref->declaration_id == 0)
200
254
  ? cUnresolvedConstantReference
@@ -306,6 +360,8 @@ void rdxi_initialize_definition(VALUE mod) {
306
360
  rb_define_method(cDefinition, "deprecated?", rdxr_definition_deprecated, 0);
307
361
  rb_define_method(cDefinition, "name_location", rdxr_definition_name_location, 0);
308
362
  rb_define_method(cDefinition, "declaration", rdxr_definition_declaration, 0);
363
+ rb_define_method(cDefinition, "lexical_owner", rdxr_definition_lexical_owner, 0);
364
+ rb_define_method(cDefinition, "lexical_nesting", rdxr_definition_lexical_nesting, 0);
309
365
 
310
366
  cClassDefinition = rb_define_class_under(mRubydex, "ClassDefinition", cDefinition);
311
367
  rb_define_method(cClassDefinition, "superclass", rdxr_class_definition_superclass, 0);
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "mkmf"
4
+ require "fileutils"
4
5
  require "pathname"
5
6
 
6
7
  unless system("cargo", "--version", out: File::NULL, err: File::NULL)
@@ -81,6 +82,12 @@ else
81
82
  end
82
83
 
83
84
  lib_dir = gem_dir.join("lib").join("rubydex")
85
+ mcp_bin_dir = lib_dir.join("bin")
86
+ FileUtils.mkdir_p(mcp_bin_dir)
87
+
88
+ mcp_executable = Gem.win_platform? ? "rubydex_mcp.exe" : "rubydex_mcp"
89
+ mcp_src = target_dir.join(mcp_executable)
90
+ mcp_dst = mcp_bin_dir.join(mcp_executable)
84
91
 
85
92
  copy_dylib_commands = if Gem.win_platform?
86
93
  ""
@@ -107,6 +114,7 @@ new_makefile = makefile.gsub("$(OBJS): $(HDRS) $(ruby_headers)", <<~MAKEFILE.cho
107
114
  .rust_built: $(RUST_SRCS)
108
115
  \t#{cargo_command} || (echo "Compiling Rust failed" && exit 1)
109
116
  \t$(COPY) #{bindings_path} #{__dir__}
117
+ \t$(COPY) #{mcp_src} #{mcp_dst}
110
118
  \ttouch $@
111
119
 
112
120
  compile_rust: .rust_built
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -20,6 +20,13 @@ module Rubydex
20
20
  end
21
21
  end
22
22
 
23
+ class Namespace < Declaration
24
+ #: (*String ancestor_names) -> bool
25
+ def has_ancestor?(*ancestor_names)
26
+ ancestors.any? { |ancestor| ancestor_names.include?(ancestor.name) }
27
+ end
28
+ end
29
+
23
30
  class Class < Namespace
24
31
  include Visibility
25
32
  end
@@ -28,6 +35,11 @@ module Rubydex
28
35
  include Visibility
29
36
  end
30
37
 
38
+ class SingletonClass < Namespace
39
+ #: () -> Declaration
40
+ alias_method :attached_class, :owner
41
+ end
42
+
31
43
  class Constant < Declaration
32
44
  include Visibility
33
45
  end
data/lib/rubydex/graph.rb CHANGED
@@ -17,6 +17,8 @@ module Rubydex
17
17
  "tmp",
18
18
  ].freeze
19
19
 
20
+ INDEXABLE_EXTENSIONS = [".rb", ".rake", ".rbs", ".ru"].freeze
21
+
20
22
  #: String
21
23
  attr_accessor :workspace_path
22
24
 
@@ -45,7 +47,7 @@ module Rubydex
45
47
 
46
48
  if File.directory?(full_path)
47
49
  paths << full_path unless IGNORED_DIRECTORIES.include?(entry)
48
- elsif File.extname(entry) == ".rb"
50
+ elsif INDEXABLE_EXTENSIONS.include?(File.extname(entry))
49
51
  paths << full_path
50
52
  end
51
53
  end
Binary file
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubydex
4
- VERSION = "0.2.3"
4
+ VERSION = "0.2.5"
5
5
  end
data/rbi/rubydex.rbi CHANGED
@@ -104,6 +104,9 @@ class Rubydex::Namespace < Rubydex::Declaration
104
104
  sig { returns(T::Enumerable[Rubydex::Namespace]) }
105
105
  def ancestors; end
106
106
 
107
+ sig { params(ancestor_names: String).returns(T::Boolean) }
108
+ def has_ancestor?(*ancestor_names); end
109
+
107
110
  sig { returns(T::Enumerable[Rubydex::Namespace]) }
108
111
  def descendants; end
109
112
 
@@ -122,7 +125,11 @@ end
122
125
 
123
126
  class Rubydex::Class < Rubydex::Namespace; end
124
127
  class Rubydex::Module < Rubydex::Namespace; end
125
- class Rubydex::SingletonClass < Rubydex::Namespace; end
128
+
129
+ class Rubydex::SingletonClass < Rubydex::Namespace
130
+ sig { returns(Rubydex::Declaration) }
131
+ def attached_class; end
132
+ end
126
133
 
127
134
  class Rubydex::Definition
128
135
  abstract!
@@ -145,6 +152,12 @@ class Rubydex::Definition
145
152
  sig { returns(T.nilable(Rubydex::Declaration)) }
146
153
  def declaration; end
147
154
 
155
+ sig { returns(T.nilable(Rubydex::Definition)) }
156
+ def lexical_owner; end
157
+
158
+ sig { returns(T::Array[Rubydex::Definition]) }
159
+ def lexical_nesting; end
160
+
148
161
  class << self
149
162
  private
150
163
 
data/rust/Cargo.lock CHANGED
@@ -971,9 +971,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
971
971
 
972
972
  [[package]]
973
973
  name = "rmcp"
974
- version = "0.15.0"
974
+ version = "1.4.0"
975
975
  source = "registry+https://github.com/rust-lang/crates.io-index"
976
- checksum = "1bef41ebc9ebed2c1b1d90203e9d1756091e8a00bbc3107676151f39868ca0ee"
976
+ checksum = "f542f74cf247da16f19bbc87e298cd201e912314f4083e88cdd671f44f5fcb53"
977
977
  dependencies = [
978
978
  "async-trait",
979
979
  "base64",
@@ -993,9 +993,9 @@ dependencies = [
993
993
 
994
994
  [[package]]
995
995
  name = "rmcp-macros"
996
- version = "0.15.0"
996
+ version = "1.6.0"
997
997
  source = "registry+https://github.com/rust-lang/crates.io-index"
998
- checksum = "0e88ad84b8b6237a934534a62b379a5be6388915663c0cc598ceb9b3292bbbfe"
998
+ checksum = "7caa6743cc0888e433105fe1bc551a7f607940b126a37bc97b478e86064627eb"
999
999
  dependencies = [
1000
1000
  "darling",
1001
1001
  "proc-macro2",
@@ -206,6 +206,44 @@ impl LocalGraph {
206
206
  &self.name_dependents
207
207
  }
208
208
 
209
+ /// Creates a `LocalGraph` from pre-built parts (used by the operation applier pipeline).
210
+ #[must_use]
211
+ pub fn from_parts(
212
+ uri_id: UriId,
213
+ document: Document,
214
+ strings: IdentityHashMap<StringId, StringRef>,
215
+ names: IdentityHashMap<NameId, NameRef>,
216
+ ) -> Self {
217
+ let mut name_dependents: IdentityHashMap<NameId, Vec<NameDependent>> = IdentityHashMap::default();
218
+ for (name_id, name_ref) in &names {
219
+ if let NameRef::Unresolved(name) = name_ref {
220
+ if let Some(&parent_scope) = name.parent_scope().as_ref() {
221
+ name_dependents
222
+ .entry(parent_scope)
223
+ .or_default()
224
+ .push(NameDependent::ChildName(*name_id));
225
+ }
226
+ if let Some(&nesting_id) = name.nesting().as_ref() {
227
+ name_dependents
228
+ .entry(nesting_id)
229
+ .or_default()
230
+ .push(NameDependent::NestedName(*name_id));
231
+ }
232
+ }
233
+ }
234
+
235
+ Self {
236
+ uri_id,
237
+ document,
238
+ definitions: IdentityHashMap::default(),
239
+ strings,
240
+ names,
241
+ constant_references: IdentityHashMap::default(),
242
+ method_references: IdentityHashMap::default(),
243
+ name_dependents,
244
+ }
245
+ }
246
+
209
247
  // Into parts
210
248
 
211
249
  #[must_use]
@@ -2481,5 +2481,11 @@ impl Visit<'_> for RubyIndexer<'_> {
2481
2481
  }
2482
2482
 
2483
2483
  #[cfg(test)]
2484
+ fn backend() -> super::IndexerBackend {
2485
+ super::IndexerBackend::RubyIndexer
2486
+ }
2487
+
2488
+ #[cfg(test)]
2489
+ #[allow(clippy::duplicate_mod)]
2484
2490
  #[path = "ruby_indexer_tests.rs"]
2485
2491
  mod tests;
@@ -1,3 +1,7 @@
1
+ // This file is included via #[path] by both ruby_indexer.rs and operation/applier.rs
2
+ // to run the same tests against both indexing backends. Each parent module provides
3
+ // a `backend()` function that `index_source` calls via `super::backend()`.
4
+
1
5
  use crate::{
2
6
  assert_def_comments_eq, assert_def_mixins_eq, assert_def_name_eq, assert_def_name_offset_eq, assert_def_str_eq,
3
7
  assert_def_superclass_ref_eq, assert_definition_at, assert_dependents, assert_local_diagnostics_eq,
@@ -86,7 +90,7 @@ macro_rules! assert_method_references_eq {
86
90
  }
87
91
 
88
92
  fn index_source(source: &str) -> LocalGraphTest {
89
- LocalGraphTest::new("file:///foo.rb", source)
93
+ LocalGraphTest::new_with_backend("file:///foo.rb", source, super::backend())
90
94
  }
91
95
 
92
96
  mod constant_tests {
@@ -3,6 +3,7 @@ use crate::{
3
3
  indexing::{local_graph::LocalGraph, rbs_indexer::RBSIndexer, ruby_indexer::RubyIndexer},
4
4
  job_queue::{Job, JobQueue},
5
5
  model::graph::Graph,
6
+ operation::ruby_builder::RubyOperationBuilder,
6
7
  };
7
8
  use crossbeam_channel::{Sender, unbounded};
8
9
  use std::{ffi::OsStr, fs, path::PathBuf, sync::Arc};
@@ -12,6 +13,15 @@ pub mod local_graph;
12
13
  pub mod rbs_indexer;
13
14
  pub mod ruby_indexer;
14
15
 
16
+ /// Which backend to use for indexing Ruby files.
17
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
18
+ pub enum IndexerBackend {
19
+ /// The original tree-walking indexer.
20
+ RubyIndexer,
21
+ /// The two-phase operation builder + applier pipeline.
22
+ OperationBuilder,
23
+ }
24
+
15
25
  /// The language of a source document, used to dispatch to the appropriate indexer
16
26
  pub enum LanguageId {
17
27
  Ruby,
@@ -42,15 +52,22 @@ impl LanguageId {
42
52
  /// Job that indexes a single file
43
53
  pub struct IndexingJob {
44
54
  path: PathBuf,
55
+ backend: IndexerBackend,
45
56
  local_graph_tx: Sender<LocalGraph>,
46
57
  errors_tx: Sender<Errors>,
47
58
  }
48
59
 
49
60
  impl IndexingJob {
50
61
  #[must_use]
51
- pub fn new(path: PathBuf, local_graph_tx: Sender<LocalGraph>, errors_tx: Sender<Errors>) -> Self {
62
+ pub fn new(
63
+ path: PathBuf,
64
+ backend: IndexerBackend,
65
+ local_graph_tx: Sender<LocalGraph>,
66
+ errors_tx: Sender<Errors>,
67
+ ) -> Self {
52
68
  Self {
53
69
  path,
70
+ backend,
54
71
  local_graph_tx,
55
72
  errors_tx,
56
73
  }
@@ -84,7 +101,7 @@ impl Job for IndexingJob {
84
101
  };
85
102
 
86
103
  let language = self.path.extension().map_or(LanguageId::Ruby, LanguageId::from);
87
- let local_graph = build_local_graph(url.to_string(), &source, &language);
104
+ let local_graph = build_local_graph(url.to_string(), &source, &language, self.backend);
88
105
 
89
106
  self.local_graph_tx
90
107
  .send(local_graph)
@@ -94,7 +111,7 @@ impl Job for IndexingJob {
94
111
 
95
112
  /// Indexes a single source string in memory, dispatching to the appropriate indexer based on `language_id`.
96
113
  pub fn index_source(graph: &mut Graph, uri: &str, source: &str, language_id: &LanguageId) {
97
- let local_graph = build_local_graph(uri.to_string(), source, language_id);
114
+ let local_graph = build_local_graph(uri.to_string(), source, language_id, IndexerBackend::RubyIndexer);
98
115
  graph.consume_document_changes(local_graph);
99
116
  }
100
117
 
@@ -103,7 +120,7 @@ pub fn index_source(graph: &mut Graph, uri: &str, source: &str, language_id: &La
103
120
  /// # Panics
104
121
  ///
105
122
  /// Will panic if the graph cannot be wrapped in an Arc<Mutex<>>
106
- pub fn index_files(graph: &mut Graph, paths: Vec<PathBuf>) -> Vec<Errors> {
123
+ pub fn index_files(graph: &mut Graph, paths: Vec<PathBuf>, backend: IndexerBackend) -> Vec<Errors> {
107
124
  let queue = Arc::new(JobQueue::new());
108
125
  let (local_graphs_tx, local_graphs_rx) = unbounded();
109
126
  let (errors_tx, errors_rx) = unbounded();
@@ -111,6 +128,7 @@ pub fn index_files(graph: &mut Graph, paths: Vec<PathBuf>) -> Vec<Errors> {
111
128
  for path in paths {
112
129
  queue.push(Box::new(IndexingJob::new(
113
130
  path,
131
+ backend,
114
132
  local_graphs_tx.clone(),
115
133
  errors_tx.clone(),
116
134
  )));
@@ -134,13 +152,21 @@ pub fn index_files(graph: &mut Graph, paths: Vec<PathBuf>) -> Vec<Errors> {
134
152
  }
135
153
 
136
154
  /// Indexes a source string using the appropriate indexer for the given language.
137
- fn build_local_graph(uri: String, source: &str, language: &LanguageId) -> LocalGraph {
155
+ #[must_use]
156
+ pub fn build_local_graph(uri: String, source: &str, language: &LanguageId, backend: IndexerBackend) -> LocalGraph {
138
157
  match language {
139
- LanguageId::Ruby => {
140
- let mut indexer = RubyIndexer::new(uri, source);
141
- indexer.index();
142
- indexer.local_graph()
143
- }
158
+ LanguageId::Ruby => match backend {
159
+ IndexerBackend::RubyIndexer => {
160
+ let mut indexer = RubyIndexer::new(uri, source);
161
+ indexer.index();
162
+ indexer.local_graph()
163
+ }
164
+ IndexerBackend::OperationBuilder => {
165
+ let builder = RubyOperationBuilder::new(uri, source);
166
+ let result = builder.build();
167
+ crate::operation::applier::apply_operations(result)
168
+ }
169
+ },
144
170
  LanguageId::Rbs => {
145
171
  let mut indexer = RBSIndexer::new(uri, source);
146
172
  indexer.index();
@@ -175,7 +201,7 @@ mod tests {
175
201
  let relative_to_pwd = &dots.join(absolute_path);
176
202
 
177
203
  let mut graph = Graph::new();
178
- let errors = index_files(&mut graph, vec![relative_to_pwd.clone()]);
204
+ let errors = index_files(&mut graph, vec![relative_to_pwd.clone()], IndexerBackend::RubyIndexer);
179
205
 
180
206
  assert!(errors.is_empty());
181
207
  assert_eq!(graph.documents().len(), 2);
@@ -196,7 +222,7 @@ mod tests {
196
222
  let uri = Url::from_file_path(&path).unwrap().to_string();
197
223
 
198
224
  let mut graph = Graph::new();
199
- let errors = index_files(&mut graph, vec![path]);
225
+ let errors = index_files(&mut graph, vec![path], IndexerBackend::RubyIndexer);
200
226
 
201
227
  assert!(errors.is_empty(), "Expected no errors, got: {errors:#?}");
202
228
  assert_eq!(6, graph.definitions().len());
@@ -7,6 +7,7 @@ pub mod job_queue;
7
7
  pub mod listing;
8
8
  pub mod model;
9
9
  pub mod offset;
10
+ pub mod operation;
10
11
  pub mod position;
11
12
  pub mod query;
12
13
  pub mod resolution;
@@ -38,9 +38,14 @@ impl FileDiscoveryJob {
38
38
  }
39
39
  }
40
40
 
41
+ fn is_indexable_file(path: &Path) -> bool {
42
+ path.extension()
43
+ .is_some_and(|ext| ext == "rb" || ext == "rake" || ext == "rbs" || ext == "ru")
44
+ }
45
+
41
46
  impl FileDiscoveryJob {
42
47
  fn handle_file(&self, path: &Path) {
43
- if path.extension().is_some_and(|ext| ext == "rb" || ext == "rbs") {
48
+ if is_indexable_file(path) {
44
49
  self.paths_tx
45
50
  .send(path.to_path_buf())
46
51
  .expect("file receiver dropped before run completion");
@@ -266,22 +271,28 @@ mod tests {
266
271
  }
267
272
 
268
273
  #[test]
269
- fn collect_rbs_files() {
274
+ fn collect_indexable_files() {
270
275
  let context = Context::new();
271
276
  let ruby_file = PathBuf::from("lib").join("foo.rb");
277
+ let rake_file = PathBuf::from("lib").join("task.rake");
272
278
  let rbs_file = PathBuf::from("sig").join("foo.rbs");
279
+ let rack_file = PathBuf::from("config.ru");
273
280
  let txt_file = PathBuf::from("lib").join("notes.txt");
274
281
  context.touch(&ruby_file);
282
+ context.touch(&rake_file);
275
283
  context.touch(&rbs_file);
284
+ context.touch(&rack_file);
276
285
  context.touch(&txt_file);
277
286
 
278
- let (files, errors) = collect_document_paths(&context, &["lib", "sig"]);
287
+ let (files, errors) = collect_document_paths(&context, &["lib", "sig", "config.ru"]);
279
288
 
280
289
  assert!(errors.is_empty());
281
290
 
282
291
  assert_eq!(
283
292
  [
293
+ rack_file.to_str().unwrap().to_string(),
284
294
  ruby_file.to_str().unwrap().to_string(),
295
+ rake_file.to_str().unwrap().to_string(),
285
296
  rbs_file.to_str().unwrap().to_string(),
286
297
  ],
287
298
  files.as_slice()
@@ -2,7 +2,8 @@ use clap::{Parser, ValueEnum};
2
2
  use std::{collections::HashSet, mem};
3
3
 
4
4
  use rubydex::{
5
- indexing, integrity, listing,
5
+ indexing::{self, IndexerBackend},
6
+ integrity, listing,
6
7
  model::graph::Graph,
7
8
  resolution::Resolver,
8
9
  stats::{
@@ -31,6 +32,14 @@ struct Args {
31
32
  #[arg(long = "check-integrity", help = "Check the integrity of the graph after resolution")]
32
33
  check_integrity: bool,
33
34
 
35
+ #[arg(
36
+ long = "indexer",
37
+ value_enum,
38
+ default_value = "ruby-indexer",
39
+ help = "Which indexer backend to use for Ruby files"
40
+ )]
41
+ indexer: Indexer,
42
+
34
43
  #[arg(
35
44
  long = "report-orphans",
36
45
  value_name = "PATH",
@@ -49,6 +58,21 @@ enum StopAfter {
49
58
  Resolution,
50
59
  }
51
60
 
61
+ #[derive(Debug, Clone, ValueEnum)]
62
+ enum Indexer {
63
+ RubyIndexer,
64
+ OperationBuilder,
65
+ }
66
+
67
+ impl From<&Indexer> for IndexerBackend {
68
+ fn from(indexer: &Indexer) -> Self {
69
+ match indexer {
70
+ Indexer::RubyIndexer => IndexerBackend::RubyIndexer,
71
+ Indexer::OperationBuilder => IndexerBackend::OperationBuilder,
72
+ }
73
+ }
74
+ }
75
+
52
76
  fn exit(print_stats: bool) {
53
77
  if print_stats {
54
78
  Timer::print_breakdown();
@@ -80,7 +104,8 @@ fn main() {
80
104
  // Indexing
81
105
 
82
106
  let mut graph = Graph::new();
83
- let errors = time_it!(indexing, { indexing::index_files(&mut graph, file_paths) });
107
+ let backend = IndexerBackend::from(&args.indexer);
108
+ let errors = time_it!(indexing, { indexing::index_files(&mut graph, file_paths, backend) });
84
109
 
85
110
  for error in errors {
86
111
  eprintln!("{error}");
@@ -29,7 +29,7 @@ use crate::{
29
29
  assert_mem_size,
30
30
  model::{
31
31
  comment::Comment,
32
- ids::{ConstantReferenceId, DefinitionId, NameId, StringId, UriId},
32
+ ids::{self, ConstantReferenceId, DefinitionId, NameId, StringId, UriId},
33
33
  visibility::Visibility,
34
34
  },
35
35
  offset::Offset,
@@ -289,7 +289,7 @@ impl ClassDefinition {
289
289
 
290
290
  #[must_use]
291
291
  pub fn id(&self) -> DefinitionId {
292
- DefinitionId::from(&format!("{}{}{}", *self.uri_id, self.offset.start(), *self.name_id))
292
+ ids::namespace_definition_id(self.uri_id, &self.offset, self.name_id)
293
293
  }
294
294
 
295
295
  #[must_use]
@@ -411,7 +411,7 @@ impl SingletonClassDefinition {
411
411
 
412
412
  #[must_use]
413
413
  pub fn id(&self) -> DefinitionId {
414
- DefinitionId::from(&format!("{}{}{}", *self.uri_id, self.offset.start(), *self.name_id))
414
+ ids::namespace_definition_id(self.uri_id, &self.offset, self.name_id)
415
415
  }
416
416
 
417
417
  #[must_use]
@@ -515,7 +515,7 @@ impl ModuleDefinition {
515
515
 
516
516
  #[must_use]
517
517
  pub fn id(&self) -> DefinitionId {
518
- DefinitionId::from(&format!("{}{}{}", *self.uri_id, self.offset.start(), *self.name_id))
518
+ ids::namespace_definition_id(self.uri_id, &self.offset, self.name_id)
519
519
  }
520
520
 
521
521
  #[must_use]
@@ -611,7 +611,7 @@ impl ConstantDefinition {
611
611
 
612
612
  #[must_use]
613
613
  pub fn id(&self) -> DefinitionId {
614
- DefinitionId::from(&format!("{}{}{}", *self.uri_id, self.offset.start(), *self.name_id))
614
+ ids::namespace_definition_id(self.uri_id, &self.offset, self.name_id)
615
615
  }
616
616
 
617
617
  #[must_use]
@@ -926,7 +926,7 @@ pub struct MethodDefinition {
926
926
  assert_mem_size!(MethodDefinition, 96);
927
927
 
928
928
  /// The receiver of a singleton method definition.
929
- #[derive(Debug)]
929
+ #[derive(Debug, Clone)]
930
930
  pub enum Receiver {
931
931
  /// `def self.foo` - receiver is the enclosing definition (class, module, singleton class or DSL)
932
932
  SelfReceiver(DefinitionId),
@@ -965,18 +965,7 @@ impl MethodDefinition {
965
965
 
966
966
  #[must_use]
967
967
  pub fn id(&self) -> DefinitionId {
968
- let mut formatted_id = format!("{}{}{}", *self.uri_id, self.offset.start(), *self.str_id);
969
-
970
- if let Some(receiver) = &self.receiver {
971
- match receiver {
972
- Receiver::SelfReceiver(def_id) => formatted_id.push_str(&def_id.to_string()),
973
- Receiver::ConstantReceiver(name_id) => {
974
- formatted_id.push_str(&name_id.to_string());
975
- }
976
- }
977
- }
978
-
979
- DefinitionId::from(&formatted_id)
968
+ ids::method_definition_id(self.uri_id, &self.offset, self.str_id, self.receiver.as_ref())
980
969
  }
981
970
 
982
971
  #[must_use]