rubydex 0.1.0.beta12-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 (109) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +23 -0
  3. data/README.md +125 -0
  4. data/THIRD_PARTY_LICENSES.html +4562 -0
  5. data/exe/rdx +47 -0
  6. data/ext/rubydex/declaration.c +453 -0
  7. data/ext/rubydex/declaration.h +23 -0
  8. data/ext/rubydex/definition.c +284 -0
  9. data/ext/rubydex/definition.h +28 -0
  10. data/ext/rubydex/diagnostic.c +6 -0
  11. data/ext/rubydex/diagnostic.h +11 -0
  12. data/ext/rubydex/document.c +97 -0
  13. data/ext/rubydex/document.h +10 -0
  14. data/ext/rubydex/extconf.rb +138 -0
  15. data/ext/rubydex/graph.c +681 -0
  16. data/ext/rubydex/graph.h +10 -0
  17. data/ext/rubydex/handle.h +44 -0
  18. data/ext/rubydex/location.c +22 -0
  19. data/ext/rubydex/location.h +15 -0
  20. data/ext/rubydex/reference.c +123 -0
  21. data/ext/rubydex/reference.h +15 -0
  22. data/ext/rubydex/rubydex.c +22 -0
  23. data/ext/rubydex/utils.c +108 -0
  24. data/ext/rubydex/utils.h +34 -0
  25. data/lib/rubydex/3.2/rubydex.so +0 -0
  26. data/lib/rubydex/3.3/rubydex.so +0 -0
  27. data/lib/rubydex/3.4/rubydex.so +0 -0
  28. data/lib/rubydex/4.0/rubydex.so +0 -0
  29. data/lib/rubydex/comment.rb +17 -0
  30. data/lib/rubydex/diagnostic.rb +21 -0
  31. data/lib/rubydex/failures.rb +15 -0
  32. data/lib/rubydex/graph.rb +98 -0
  33. data/lib/rubydex/keyword.rb +17 -0
  34. data/lib/rubydex/keyword_parameter.rb +13 -0
  35. data/lib/rubydex/librubydex_sys.so +0 -0
  36. data/lib/rubydex/location.rb +90 -0
  37. data/lib/rubydex/mixin.rb +22 -0
  38. data/lib/rubydex/version.rb +5 -0
  39. data/lib/rubydex.rb +23 -0
  40. data/rbi/rubydex.rbi +422 -0
  41. data/rust/Cargo.lock +1851 -0
  42. data/rust/Cargo.toml +29 -0
  43. data/rust/about.hbs +78 -0
  44. data/rust/about.toml +10 -0
  45. data/rust/rubydex/Cargo.toml +42 -0
  46. data/rust/rubydex/src/compile_assertions.rs +13 -0
  47. data/rust/rubydex/src/diagnostic.rs +110 -0
  48. data/rust/rubydex/src/errors.rs +28 -0
  49. data/rust/rubydex/src/indexing/local_graph.rs +224 -0
  50. data/rust/rubydex/src/indexing/rbs_indexer.rs +1551 -0
  51. data/rust/rubydex/src/indexing/ruby_indexer.rs +2329 -0
  52. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +4962 -0
  53. data/rust/rubydex/src/indexing.rs +210 -0
  54. data/rust/rubydex/src/integrity.rs +279 -0
  55. data/rust/rubydex/src/job_queue.rs +205 -0
  56. data/rust/rubydex/src/lib.rs +17 -0
  57. data/rust/rubydex/src/listing.rs +371 -0
  58. data/rust/rubydex/src/main.rs +160 -0
  59. data/rust/rubydex/src/model/built_in.rs +83 -0
  60. data/rust/rubydex/src/model/comment.rs +24 -0
  61. data/rust/rubydex/src/model/declaration.rs +671 -0
  62. data/rust/rubydex/src/model/definitions.rs +1682 -0
  63. data/rust/rubydex/src/model/document.rs +222 -0
  64. data/rust/rubydex/src/model/encoding.rs +22 -0
  65. data/rust/rubydex/src/model/graph.rs +3754 -0
  66. data/rust/rubydex/src/model/id.rs +110 -0
  67. data/rust/rubydex/src/model/identity_maps.rs +58 -0
  68. data/rust/rubydex/src/model/ids.rs +60 -0
  69. data/rust/rubydex/src/model/keywords.rs +256 -0
  70. data/rust/rubydex/src/model/name.rs +298 -0
  71. data/rust/rubydex/src/model/references.rs +111 -0
  72. data/rust/rubydex/src/model/string_ref.rs +50 -0
  73. data/rust/rubydex/src/model/visibility.rs +41 -0
  74. data/rust/rubydex/src/model.rs +15 -0
  75. data/rust/rubydex/src/offset.rs +147 -0
  76. data/rust/rubydex/src/position.rs +6 -0
  77. data/rust/rubydex/src/query.rs +1841 -0
  78. data/rust/rubydex/src/resolution.rs +6517 -0
  79. data/rust/rubydex/src/stats/memory.rs +71 -0
  80. data/rust/rubydex/src/stats/orphan_report.rs +264 -0
  81. data/rust/rubydex/src/stats/timer.rs +127 -0
  82. data/rust/rubydex/src/stats.rs +11 -0
  83. data/rust/rubydex/src/test_utils/context.rs +226 -0
  84. data/rust/rubydex/src/test_utils/graph_test.rs +730 -0
  85. data/rust/rubydex/src/test_utils/local_graph_test.rs +602 -0
  86. data/rust/rubydex/src/test_utils.rs +52 -0
  87. data/rust/rubydex/src/visualization/dot.rs +192 -0
  88. data/rust/rubydex/src/visualization.rs +6 -0
  89. data/rust/rubydex/tests/cli.rs +185 -0
  90. data/rust/rubydex-mcp/Cargo.toml +28 -0
  91. data/rust/rubydex-mcp/src/main.rs +48 -0
  92. data/rust/rubydex-mcp/src/server.rs +1145 -0
  93. data/rust/rubydex-mcp/src/tools.rs +49 -0
  94. data/rust/rubydex-mcp/tests/mcp.rs +302 -0
  95. data/rust/rubydex-sys/Cargo.toml +20 -0
  96. data/rust/rubydex-sys/build.rs +14 -0
  97. data/rust/rubydex-sys/cbindgen.toml +12 -0
  98. data/rust/rubydex-sys/src/declaration_api.rs +485 -0
  99. data/rust/rubydex-sys/src/definition_api.rs +443 -0
  100. data/rust/rubydex-sys/src/diagnostic_api.rs +99 -0
  101. data/rust/rubydex-sys/src/document_api.rs +85 -0
  102. data/rust/rubydex-sys/src/graph_api.rs +948 -0
  103. data/rust/rubydex-sys/src/lib.rs +79 -0
  104. data/rust/rubydex-sys/src/location_api.rs +79 -0
  105. data/rust/rubydex-sys/src/name_api.rs +135 -0
  106. data/rust/rubydex-sys/src/reference_api.rs +267 -0
  107. data/rust/rubydex-sys/src/utils.rs +70 -0
  108. data/rust/rustfmt.toml +2 -0
  109. metadata +159 -0
@@ -0,0 +1,17 @@
1
+ pub mod compile_assertions;
2
+ pub mod diagnostic;
3
+ pub mod errors;
4
+ pub mod indexing;
5
+ pub mod integrity;
6
+ pub mod job_queue;
7
+ pub mod listing;
8
+ pub mod model;
9
+ pub mod offset;
10
+ pub mod position;
11
+ pub mod query;
12
+ pub mod resolution;
13
+ pub mod stats;
14
+ pub mod visualization;
15
+
16
+ #[cfg(any(test, feature = "test_utils"))]
17
+ pub mod test_utils;
@@ -0,0 +1,371 @@
1
+ use crate::{
2
+ errors::Errors,
3
+ job_queue::{Job, JobQueue},
4
+ };
5
+ use crossbeam_channel::{Sender, unbounded};
6
+ use std::{
7
+ collections::HashSet,
8
+ fs,
9
+ hash::BuildHasher,
10
+ path::{Path, PathBuf},
11
+ sync::Arc,
12
+ };
13
+
14
+ pub struct FileDiscoveryJob {
15
+ path: PathBuf,
16
+ queue: Arc<JobQueue>,
17
+ paths_tx: Sender<PathBuf>,
18
+ errors_tx: Sender<Errors>,
19
+ excluded_paths: Arc<HashSet<PathBuf>>,
20
+ }
21
+
22
+ impl FileDiscoveryJob {
23
+ #[must_use]
24
+ pub fn new(
25
+ path: PathBuf,
26
+ queue: Arc<JobQueue>,
27
+ paths_tx: Sender<PathBuf>,
28
+ errors_tx: Sender<Errors>,
29
+ excluded_paths: Arc<HashSet<PathBuf>>,
30
+ ) -> Self {
31
+ Self {
32
+ path,
33
+ queue,
34
+ paths_tx,
35
+ errors_tx,
36
+ excluded_paths,
37
+ }
38
+ }
39
+ }
40
+
41
+ impl FileDiscoveryJob {
42
+ fn handle_file(&self, path: &Path) {
43
+ if path.extension().is_some_and(|ext| ext == "rb" || ext == "rbs") {
44
+ self.paths_tx
45
+ .send(path.to_path_buf())
46
+ .expect("file receiver dropped before run completion");
47
+ }
48
+ }
49
+
50
+ fn handle_symlink(&self, path: &PathBuf) {
51
+ let Ok(canonicalized) = fs::canonicalize(path) else {
52
+ self.send_error(Errors::FileError(format!(
53
+ "Failed to canonicalize symlink: `{}`",
54
+ path.display(),
55
+ )));
56
+
57
+ return;
58
+ };
59
+
60
+ if self.excluded_paths.contains(&canonicalized) {
61
+ return;
62
+ }
63
+
64
+ self.queue.push(Box::new(FileDiscoveryJob::new(
65
+ canonicalized,
66
+ Arc::clone(&self.queue),
67
+ self.paths_tx.clone(),
68
+ self.errors_tx.clone(),
69
+ Arc::clone(&self.excluded_paths),
70
+ )));
71
+ }
72
+
73
+ fn send_error(&self, error: Errors) {
74
+ self.errors_tx
75
+ .send(error)
76
+ .expect("error receiver dropped before run completion");
77
+ }
78
+ }
79
+
80
+ impl Job for FileDiscoveryJob {
81
+ fn run(&self) {
82
+ if self.path.is_dir() {
83
+ let Ok(read_dir) = self.path.read_dir() else {
84
+ self.send_error(Errors::FileError(format!(
85
+ "Failed to read directory `{}`",
86
+ self.path.display(),
87
+ )));
88
+
89
+ return;
90
+ };
91
+
92
+ for result in read_dir {
93
+ let Ok(entry) = result else {
94
+ self.send_error(Errors::FileError(format!(
95
+ "Failed to read directory `{}`: {result:?}",
96
+ self.path.display(),
97
+ )));
98
+
99
+ continue;
100
+ };
101
+
102
+ let kind = entry.file_type().unwrap();
103
+
104
+ if kind.is_dir() {
105
+ if self.excluded_paths.contains(&entry.path()) {
106
+ continue;
107
+ }
108
+
109
+ self.queue.push(Box::new(FileDiscoveryJob::new(
110
+ entry.path(),
111
+ Arc::clone(&self.queue),
112
+ self.paths_tx.clone(),
113
+ self.errors_tx.clone(),
114
+ Arc::clone(&self.excluded_paths),
115
+ )));
116
+ } else if kind.is_file() {
117
+ self.handle_file(&entry.path());
118
+ } else if kind.is_symlink() {
119
+ self.handle_symlink(&entry.path());
120
+ } else {
121
+ self.send_error(Errors::FileError(format!(
122
+ "Path `{}` is not a file or directory",
123
+ entry.path().display()
124
+ )));
125
+ }
126
+ }
127
+ } else if self.path.is_file() {
128
+ self.handle_file(&self.path);
129
+ } else if self.path.is_symlink() {
130
+ self.handle_symlink(&self.path);
131
+ } else {
132
+ self.send_error(Errors::FileError(format!(
133
+ "Path `{}` is not a file or directory",
134
+ self.path.display()
135
+ )));
136
+ }
137
+ }
138
+ }
139
+
140
+ /// Recursively collects all Ruby files for the given workspace and dependencies, returning a vector of document instances
141
+ ///
142
+ /// # Errors
143
+ ///
144
+ /// Returns a `MultipleErrors` if any of the paths do not exist
145
+ ///
146
+ /// # Panics
147
+ ///
148
+ /// Panics if the errors receiver is dropped before the run completion
149
+ #[must_use]
150
+ pub fn collect_file_paths<S: BuildHasher>(
151
+ paths: Vec<String>,
152
+ excluded: &HashSet<PathBuf, S>,
153
+ ) -> (Vec<PathBuf>, Vec<Errors>) {
154
+ let queue = Arc::new(JobQueue::new());
155
+ let (files_tx, files_rx) = unbounded();
156
+ let (errors_tx, errors_rx) = unbounded();
157
+
158
+ // Canonicalize the excluded paths since they may be symlinks
159
+ let excluded: Arc<HashSet<PathBuf>> = Arc::new(excluded.iter().filter_map(|p| fs::canonicalize(p).ok()).collect());
160
+
161
+ for path in paths {
162
+ let Ok(canonicalized) = fs::canonicalize(&path) else {
163
+ errors_tx
164
+ .send(Errors::FileError(format!("Path `{path}` does not exist")))
165
+ .expect("errors receiver dropped before run completion");
166
+
167
+ continue;
168
+ };
169
+
170
+ if excluded.contains(&canonicalized) {
171
+ continue;
172
+ }
173
+
174
+ queue.push(Box::new(FileDiscoveryJob::new(
175
+ canonicalized,
176
+ Arc::clone(&queue),
177
+ files_tx.clone(),
178
+ errors_tx.clone(),
179
+ Arc::clone(&excluded),
180
+ )));
181
+ }
182
+
183
+ JobQueue::run(&queue);
184
+
185
+ drop(files_tx);
186
+ drop(errors_tx);
187
+
188
+ (files_rx.iter().collect(), errors_rx.iter().collect())
189
+ }
190
+
191
+ #[cfg(test)]
192
+ mod tests {
193
+ use super::*;
194
+ use crate::test_utils::Context;
195
+
196
+ fn collect_document_paths(context: &Context, paths: &[&str]) -> (Vec<String>, Vec<Errors>) {
197
+ collect_document_paths_with_exclusions(context, paths, &HashSet::new())
198
+ }
199
+
200
+ fn collect_document_paths_with_exclusions(
201
+ context: &Context,
202
+ paths: &[&str],
203
+ excluded: &HashSet<PathBuf>,
204
+ ) -> (Vec<String>, Vec<Errors>) {
205
+ let (files, errors) = collect_file_paths(
206
+ paths
207
+ .iter()
208
+ .map(|p| context.absolute_path_to(p).to_string_lossy().into_owned())
209
+ .collect(),
210
+ excluded,
211
+ );
212
+
213
+ let mut files: Vec<String> = files
214
+ .iter()
215
+ .map(|path| context.relative_path_to(path).to_string_lossy().into_owned())
216
+ .collect();
217
+
218
+ files.sort();
219
+
220
+ (files, errors)
221
+ }
222
+
223
+ #[test]
224
+ fn collect_all_documents() {
225
+ let context = Context::new();
226
+ let baz = PathBuf::from("bar").join("baz.rb");
227
+ let qux = PathBuf::from("bar").join("qux.rb");
228
+ let bar = PathBuf::from("foo").join("bar.rb");
229
+ context.touch(&baz);
230
+ context.touch(&qux);
231
+ context.touch(&bar);
232
+
233
+ let (files, errors) = collect_document_paths(&context, &["foo", "bar"]);
234
+
235
+ assert!(errors.is_empty());
236
+
237
+ assert_eq!(
238
+ files,
239
+ [
240
+ baz.to_str().unwrap().to_string(),
241
+ qux.to_str().unwrap().to_string(),
242
+ bar.to_str().unwrap().to_string()
243
+ ]
244
+ );
245
+ }
246
+
247
+ #[test]
248
+ fn collect_some_documents_based_on_paths() {
249
+ let context = Context::new();
250
+ let baz = PathBuf::from("bar").join("baz.rb");
251
+ let qux = PathBuf::from("bar").join("qux.rb");
252
+ let bar = PathBuf::from("foo").join("bar.rb");
253
+
254
+ context.touch(&baz);
255
+ context.touch(&qux);
256
+ context.touch(&bar);
257
+
258
+ let (files, errors) = collect_document_paths(&context, &["bar"]);
259
+
260
+ assert!(errors.is_empty());
261
+
262
+ assert_eq!(
263
+ files,
264
+ [baz.to_str().unwrap().to_string(), qux.to_str().unwrap().to_string()]
265
+ );
266
+ }
267
+
268
+ #[test]
269
+ fn collect_rbs_files() {
270
+ let context = Context::new();
271
+ let ruby_file = PathBuf::from("lib").join("foo.rb");
272
+ let rbs_file = PathBuf::from("sig").join("foo.rbs");
273
+ let txt_file = PathBuf::from("lib").join("notes.txt");
274
+ context.touch(&ruby_file);
275
+ context.touch(&rbs_file);
276
+ context.touch(&txt_file);
277
+
278
+ let (files, errors) = collect_document_paths(&context, &["lib", "sig"]);
279
+
280
+ assert!(errors.is_empty());
281
+
282
+ assert_eq!(
283
+ [
284
+ ruby_file.to_str().unwrap().to_string(),
285
+ rbs_file.to_str().unwrap().to_string(),
286
+ ],
287
+ files.as_slice()
288
+ );
289
+ }
290
+
291
+ #[test]
292
+ fn collect_non_existing_paths() {
293
+ let context = Context::new();
294
+
295
+ let (files, errors) = collect_file_paths(
296
+ vec![
297
+ context
298
+ .absolute_path_to("non_existing_path")
299
+ .to_string_lossy()
300
+ .into_owned(),
301
+ ],
302
+ &HashSet::new(),
303
+ );
304
+
305
+ assert!(files.is_empty());
306
+
307
+ assert_eq!(
308
+ errors,
309
+ [Errors::FileError(format!(
310
+ "Path `{}` does not exist",
311
+ context.absolute_path_to("non_existing_path").display()
312
+ ))]
313
+ );
314
+ }
315
+
316
+ #[test]
317
+ fn collect_files_excludes_directories() {
318
+ let context = Context::new();
319
+ let included = PathBuf::from("included").join("foo.rb");
320
+ let excluded_file = PathBuf::from("excluded").join("bar.rb");
321
+ context.touch(&included);
322
+ context.touch(&excluded_file);
323
+
324
+ let mut excluded = HashSet::new();
325
+ excluded.insert(context.absolute_path_to("excluded"));
326
+
327
+ let (files, errors) = collect_document_paths_with_exclusions(&context, &["included", "excluded"], &excluded);
328
+
329
+ assert!(errors.is_empty());
330
+ assert_eq!(files, [included.to_str().unwrap().to_string()]);
331
+ }
332
+
333
+ #[test]
334
+ fn collect_files_excludes_nested_directories() {
335
+ let context = Context::new();
336
+ let kept = PathBuf::from("root").join("kept.rb");
337
+ let nested = PathBuf::from("root").join("skip").join("nested.rb");
338
+ context.touch(&kept);
339
+ context.touch(&nested);
340
+
341
+ let mut excluded = HashSet::new();
342
+ excluded.insert(context.absolute_path_to("root/skip"));
343
+
344
+ let (files, errors) = collect_document_paths_with_exclusions(&context, &["root"], &excluded);
345
+
346
+ assert!(errors.is_empty());
347
+ assert_eq!(files, [kept.to_str().unwrap().to_string()]);
348
+ }
349
+
350
+ #[cfg(unix)]
351
+ #[test]
352
+ fn collect_files_excludes_symlinked_directories() {
353
+ let context = Context::new();
354
+ let included = PathBuf::from("included").join("foo.rb");
355
+ let excluded_file = PathBuf::from("real_dir").join("bar.rb");
356
+ context.touch(&included);
357
+ context.touch(&excluded_file);
358
+
359
+ // Create a symlink: link -> real_dir
360
+ std::os::unix::fs::symlink(context.absolute_path_to("real_dir"), context.absolute_path_to("link")).unwrap();
361
+
362
+ // Excluding the real directory while requesting to index the symlink should properly exclude the link
363
+ let mut excluded = HashSet::new();
364
+ excluded.insert(context.absolute_path_to("real_dir"));
365
+
366
+ let (files, errors) = collect_document_paths_with_exclusions(&context, &["included", "link"], &excluded);
367
+
368
+ assert!(errors.is_empty());
369
+ assert_eq!(files, [included.to_str().unwrap().to_string()]);
370
+ }
371
+ }
@@ -0,0 +1,160 @@
1
+ use clap::{Parser, ValueEnum};
2
+ use std::{collections::HashSet, mem};
3
+
4
+ use rubydex::{
5
+ indexing, integrity, listing,
6
+ model::graph::Graph,
7
+ resolution::Resolver,
8
+ stats::{
9
+ memory::MemoryStats,
10
+ timer::{Timer, time_it},
11
+ },
12
+ visualization::dot,
13
+ };
14
+
15
+ #[derive(Parser, Debug)]
16
+ #[command(name = "rubydex_cli", about = "A Static Analysis Toolkit for Ruby", version)]
17
+ #[allow(clippy::struct_excessive_bools)]
18
+ struct Args {
19
+ #[arg(value_name = "PATHS", default_value = ".")]
20
+ paths: Vec<String>,
21
+
22
+ #[arg(long = "stop-after", help = "Stop after the given stage")]
23
+ stop_after: Option<StopAfter>,
24
+
25
+ #[arg(long = "visualize")]
26
+ visualize: bool,
27
+
28
+ #[arg(long = "stats", help = "Show detailed performance statistics")]
29
+ stats: bool,
30
+
31
+ #[arg(long = "check-integrity", help = "Check the integrity of the graph after resolution")]
32
+ check_integrity: bool,
33
+
34
+ #[arg(
35
+ long = "report-orphans",
36
+ value_name = "PATH",
37
+ num_args = 0..=1,
38
+ require_equals = true,
39
+ default_missing_value = "/tmp/rubydex-orphan-report.txt",
40
+ help = "Write orphan definitions report to specified file"
41
+ )]
42
+ report_orphans: Option<String>,
43
+ }
44
+
45
+ #[derive(Debug, Clone, ValueEnum)]
46
+ enum StopAfter {
47
+ Listing,
48
+ Indexing,
49
+ Resolution,
50
+ }
51
+
52
+ fn exit(print_stats: bool) {
53
+ if print_stats {
54
+ Timer::print_breakdown();
55
+ MemoryStats::print_memory_usage();
56
+ }
57
+
58
+ std::process::exit(0);
59
+ }
60
+
61
+ fn main() {
62
+ let args = Args::parse();
63
+
64
+ if args.stats {
65
+ Timer::set_global_timer(Timer::new());
66
+ }
67
+
68
+ // Listing
69
+
70
+ let (file_paths, errors) = time_it!(listing, { listing::collect_file_paths(args.paths, &HashSet::new()) });
71
+
72
+ for error in errors {
73
+ eprintln!("{error}");
74
+ }
75
+
76
+ if let Some(StopAfter::Listing) = args.stop_after {
77
+ return exit(args.stats);
78
+ }
79
+
80
+ // Indexing
81
+
82
+ let mut graph = Graph::new();
83
+ let errors = time_it!(indexing, { indexing::index_files(&mut graph, file_paths) });
84
+
85
+ for error in errors {
86
+ eprintln!("{error}");
87
+ }
88
+
89
+ if let Some(StopAfter::Indexing) = args.stop_after {
90
+ return exit(args.stats);
91
+ }
92
+
93
+ // Resolution
94
+
95
+ time_it!(resolution, {
96
+ let mut resolver = Resolver::new(&mut graph);
97
+ resolver.resolve();
98
+ });
99
+
100
+ if let Some(StopAfter::Resolution) = args.stop_after {
101
+ return exit(args.stats);
102
+ }
103
+
104
+ // Integrity check
105
+ if args.check_integrity {
106
+ let errors = time_it!(integrity_check, { integrity::check_integrity(&graph) });
107
+
108
+ if errors.is_empty() {
109
+ println!("Integrity check passed: no issues found");
110
+ } else {
111
+ eprintln!("Integrity check found {} issue(s):", errors.len());
112
+
113
+ for error in &errors {
114
+ eprintln!(" - {error}");
115
+ }
116
+
117
+ std::process::exit(1);
118
+ }
119
+ }
120
+
121
+ // Querying
122
+
123
+ if args.stats {
124
+ time_it!(querying, {
125
+ graph.print_query_statistics();
126
+ });
127
+ }
128
+
129
+ if args.stats {
130
+ Timer::print_breakdown();
131
+ MemoryStats::print_memory_usage();
132
+ }
133
+
134
+ // Orphan report
135
+ if let Some(ref path) = args.report_orphans {
136
+ match std::fs::File::create(path) {
137
+ Ok(mut file) => {
138
+ if let Err(e) = graph.write_orphan_report(&mut file) {
139
+ eprintln!("Failed to write orphan report: {e}");
140
+ } else {
141
+ println!("Orphan report written to {path}");
142
+ }
143
+ }
144
+ Err(e) => eprintln!("Failed to create orphan report file: {e}"),
145
+ }
146
+ }
147
+
148
+ // Generate visualization or print statistics
149
+ if args.visualize {
150
+ println!("{}", dot::generate(&graph));
151
+ } else {
152
+ println!("Indexed {} files", graph.documents().len());
153
+ println!("Found {} names", graph.declarations().len());
154
+ println!("Found {} definitions", graph.definitions().len());
155
+ println!("Found {} URIs", graph.documents().len());
156
+ }
157
+
158
+ // Forget the graph so we don't have to wait for deallocation and let the system reclaim the memory at exit
159
+ mem::forget(graph);
160
+ }
@@ -0,0 +1,83 @@
1
+ use std::sync::LazyLock;
2
+
3
+ use url::Url;
4
+
5
+ use crate::{
6
+ indexing::{self, LanguageId},
7
+ model::{
8
+ declaration::{ClassDeclaration, Declaration, Namespace},
9
+ graph::Graph,
10
+ ids::DeclarationId,
11
+ },
12
+ };
13
+
14
+ pub static KERNEL_ID: LazyLock<DeclarationId> = LazyLock::new(|| DeclarationId::from("Kernel"));
15
+ pub static BASIC_OBJECT_ID: LazyLock<DeclarationId> = LazyLock::new(|| DeclarationId::from("BasicObject"));
16
+ pub static OBJECT_ID: LazyLock<DeclarationId> = LazyLock::new(|| DeclarationId::from("Object"));
17
+ pub static MODULE_ID: LazyLock<DeclarationId> = LazyLock::new(|| DeclarationId::from("Module"));
18
+ pub static CLASS_ID: LazyLock<DeclarationId> = LazyLock::new(|| DeclarationId::from("Class"));
19
+
20
+ /// Adds core classes and modules data to the graph so that resolution can provide correct results even when not
21
+ /// indexing the complete RBS core definitions
22
+ ///
23
+ /// # Panics
24
+ ///
25
+ /// Will panic if the built-in URI is invalid
26
+ pub fn add_built_in_data(graph: &mut Graph) {
27
+ // We need definitions to ensure that ancestor linearization happens naturally through the algorithm. Trying to set
28
+ // ancestors directly on declarations doesn't work because the algorithm erases the ancestors and there are no
29
+ // definitions to inform it of the superclasses and mixins.
30
+ let uri = Url::parse("rubydex:built-in").unwrap();
31
+ let source = r"
32
+ class BasicObject
33
+ end
34
+
35
+ module Kernel
36
+ end
37
+
38
+ class Object < BasicObject
39
+ include Kernel
40
+ end
41
+
42
+ class Module < Object
43
+ end
44
+
45
+ class Class < Module
46
+ end
47
+ ";
48
+ indexing::index_source(graph, uri.as_ref(), source, &LanguageId::Rbs);
49
+
50
+ // Creating declarations eagerly is still necessary because we need to associate correct ownership data no matter in
51
+ // what order we discover classes and modules
52
+ let declarations = graph.declarations_mut();
53
+
54
+ // Built-in declarations that always exist in the Ruby object model
55
+ declarations.insert(
56
+ *BASIC_OBJECT_ID,
57
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
58
+ "BasicObject".to_string(),
59
+ *OBJECT_ID,
60
+ )))),
61
+ );
62
+ declarations.insert(
63
+ *OBJECT_ID,
64
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
65
+ "Object".to_string(),
66
+ *OBJECT_ID,
67
+ )))),
68
+ );
69
+ declarations.insert(
70
+ *MODULE_ID,
71
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
72
+ "Module".to_string(),
73
+ *OBJECT_ID,
74
+ )))),
75
+ );
76
+ declarations.insert(
77
+ *CLASS_ID,
78
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
79
+ "Class".to_string(),
80
+ *OBJECT_ID,
81
+ )))),
82
+ );
83
+ }
@@ -0,0 +1,24 @@
1
+ use crate::offset::Offset;
2
+
3
+ #[derive(Debug, Clone)]
4
+ pub struct Comment {
5
+ offset: Offset,
6
+ string: String,
7
+ }
8
+
9
+ impl Comment {
10
+ #[must_use]
11
+ pub fn new(offset: Offset, string: String) -> Self {
12
+ Self { offset, string }
13
+ }
14
+
15
+ #[must_use]
16
+ pub fn offset(&self) -> &Offset {
17
+ &self.offset
18
+ }
19
+
20
+ #[must_use]
21
+ pub fn string(&self) -> &String {
22
+ &self.string
23
+ }
24
+ }