rubydex 0.2.9 → 0.4.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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +110 -6
  3. data/THIRD_PARTY_LICENSES.html +271 -2
  4. data/exe/rdx +2 -73
  5. data/ext/rubydex/config.c +140 -0
  6. data/ext/rubydex/config.h +16 -0
  7. data/ext/rubydex/declaration.c +1 -1
  8. data/ext/rubydex/definition.c +32 -4
  9. data/ext/rubydex/diagnostic.c +75 -1
  10. data/ext/rubydex/diagnostic.h +2 -0
  11. data/ext/rubydex/graph.c +27 -48
  12. data/ext/rubydex/graph.h +6 -0
  13. data/ext/rubydex/query.c +487 -0
  14. data/ext/rubydex/query.h +8 -0
  15. data/ext/rubydex/reference.c +60 -0
  16. data/ext/rubydex/rubydex.c +4 -0
  17. data/ext/rubydex/utils.c +23 -4
  18. data/ext/rubydex/utils.h +5 -0
  19. data/lib/ruby_lsp/rubydex/addon.rb +211 -0
  20. data/lib/rubydex/cli/command/console.rb +55 -0
  21. data/lib/rubydex/cli/command/lint/explain.rb +74 -0
  22. data/lib/rubydex/cli/command/lint.rb +202 -0
  23. data/lib/rubydex/cli/command/mcp.rb +30 -0
  24. data/lib/rubydex/cli/command/query.rb +70 -0
  25. data/lib/rubydex/cli/command/skill.rb +69 -0
  26. data/lib/rubydex/cli/command.rb +168 -0
  27. data/lib/rubydex/cli.rb +93 -0
  28. data/lib/rubydex/config.rb +59 -0
  29. data/lib/rubydex/diagnostic.rb +12 -3
  30. data/lib/rubydex/errors.rb +42 -1
  31. data/lib/rubydex/graph.rb +10 -3
  32. data/lib/rubydex/linter/custom_rule.rb +97 -0
  33. data/lib/rubydex/linter/helpers/path_helpers.rb +78 -0
  34. data/lib/rubydex/linter/helpers/source_access_helpers.rb +31 -0
  35. data/lib/rubydex/linter/rule_loader.rb +36 -0
  36. data/lib/rubydex/linter/rule_test_case.rb +343 -0
  37. data/lib/rubydex/linter/runner.rb +56 -0
  38. data/lib/rubydex/linter.rb +19 -0
  39. data/lib/rubydex/location.rb +3 -0
  40. data/lib/rubydex/mcp_server.rb +1 -2
  41. data/lib/rubydex/related_information.rb +17 -0
  42. data/lib/rubydex/rule.rb +33 -0
  43. data/lib/rubydex/severity.rb +70 -0
  44. data/lib/rubydex/skill.rb +88 -0
  45. data/lib/rubydex/skill_registry.rb +62 -0
  46. data/lib/rubydex/version.rb +1 -1
  47. data/lib/rubydex.rb +6 -0
  48. data/lib/rubydex_linter/rules/rule_structure.rb +125 -0
  49. data/rbi/rubydex.rbi +578 -15
  50. data/rust/Cargo.lock +7 -0
  51. data/rust/rubydex/Cargo.toml +1 -0
  52. data/rust/rubydex/benches/graph_memory.rs +20 -4
  53. data/rust/rubydex/src/compile_assertions.rs +15 -0
  54. data/rust/rubydex/src/config.rs +538 -157
  55. data/rust/rubydex/src/diagnostic.rs +66 -40
  56. data/rust/rubydex/src/errors.rs +0 -1
  57. data/rust/rubydex/src/indexing/local_graph.rs +6 -5
  58. data/rust/rubydex/src/indexing/rbs_indexer.rs +284 -8
  59. data/rust/rubydex/src/indexing/ruby_indexer.rs +59 -70
  60. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +195 -86
  61. data/rust/rubydex/src/lib.rs +1 -0
  62. data/rust/rubydex/src/listing.rs +26 -1
  63. data/rust/rubydex/src/main.rs +9 -128
  64. data/rust/rubydex/src/model/declaration.rs +301 -229
  65. data/rust/rubydex/src/model/definitions.rs +27 -26
  66. data/rust/rubydex/src/model/document.rs +43 -7
  67. data/rust/rubydex/src/model/graph.rs +67 -68
  68. data/rust/rubydex/src/model/id.rs +55 -0
  69. data/rust/rubydex/src/model/ids.rs +21 -9
  70. data/rust/rubydex/src/model/name.rs +88 -19
  71. data/rust/rubydex/src/model/references.rs +16 -13
  72. data/rust/rubydex/src/operation/ruby_builder.rs +78 -104
  73. data/rust/rubydex/src/path_helpers.rs +77 -0
  74. data/rust/rubydex/src/query/cypher/schema.rs +853 -0
  75. data/rust/rubydex/src/query/cypher/schema_info.rs +161 -0
  76. data/rust/rubydex/src/query/cypher/tests.rs +253 -0
  77. data/rust/rubydex/src/query/cypher.rs +54 -0
  78. data/rust/rubydex/src/query.rs +125 -43
  79. data/rust/rubydex/src/resolution.rs +368 -395
  80. data/rust/rubydex/src/resolution_tests.rs +504 -78
  81. data/rust/rubydex/src/test_utils/context.rs +2 -1
  82. data/rust/rubydex/src/test_utils/graph_test.rs +26 -12
  83. data/rust/rubydex/src/test_utils/local_graph_test.rs +19 -0
  84. data/rust/rubydex/tests/cli.rs +4 -4
  85. data/rust/rubydex-sys/src/config_api.rs +205 -0
  86. data/rust/rubydex-sys/src/cypher_api.rs +791 -0
  87. data/rust/rubydex-sys/src/declaration_api.rs +6 -3
  88. data/rust/rubydex-sys/src/definition_api.rs +27 -7
  89. data/rust/rubydex-sys/src/diagnostic_api.rs +77 -8
  90. data/rust/rubydex-sys/src/graph_api.rs +31 -68
  91. data/rust/rubydex-sys/src/lib.rs +2 -0
  92. data/rust/rubydex-sys/src/name_api.rs +2 -6
  93. data/rust/rubydex-sys/src/reference_api.rs +58 -12
  94. data/rust/rubydex-sys/src/utils.rs +37 -0
  95. data/skills/send-private-method/SKILL.md +133 -0
  96. metadata +37 -2
@@ -1,13 +1,10 @@
1
1
  use clap::{Parser, ValueEnum};
2
- use std::{
3
- fs, mem,
4
- path::{Path, PathBuf},
5
- time::{Duration, Instant},
6
- };
2
+ use std::{fs, mem, path::PathBuf};
7
3
 
8
4
  use rubydex::{
5
+ config::Config,
9
6
  dot,
10
- indexing::{self, IndexerBackend, LanguageId, build_local_graph},
7
+ indexing::{self, IndexerBackend},
11
8
  integrity, listing,
12
9
  model::graph::Graph,
13
10
  resolution::Resolver,
@@ -16,7 +13,6 @@ use rubydex::{
16
13
  timer::{Timer, time_it},
17
14
  },
18
15
  };
19
- use url::Url;
20
16
 
21
17
  #[derive(Parser, Debug)]
22
18
  #[command(name = "rubydex_cli", about = "A Static Analysis Toolkit for Ruby", version)]
@@ -61,21 +57,6 @@ struct Args {
61
57
  help = "Write orphan definitions report to specified file"
62
58
  )]
63
59
  report_orphans: Option<String>,
64
-
65
- #[arg(
66
- long = "incremental_cycle",
67
- value_name = "N",
68
- help = "After the initial build, run N incremental resolution cycles and report their timings"
69
- )]
70
- incremental_cycle: Option<usize>,
71
-
72
- #[arg(
73
- long = "incremental_files",
74
- value_name = "N",
75
- default_value_t = 1,
76
- help = "Number of files to re-index per incremental cycle"
77
- )]
78
- incremental_files: usize,
79
60
  }
80
61
 
81
62
  #[derive(Debug, Clone, ValueEnum)]
@@ -124,10 +105,12 @@ fn main() {
124
105
  let mut graph = Graph::new();
125
106
 
126
107
  if let Some(workspace_path) = workspace_path_for(&args.paths) {
127
- graph.set_workspace_path(workspace_path);
128
- if let Err(error) = graph.load_config(None) {
129
- eprintln!("{error}");
130
- std::process::exit(1);
108
+ match Config::load(&workspace_path) {
109
+ Ok(config) => graph.load_config(&config),
110
+ Err(error) => {
111
+ eprintln!("{error}");
112
+ std::process::exit(1);
113
+ }
131
114
  }
132
115
  }
133
116
 
@@ -149,14 +132,6 @@ fn main() {
149
132
 
150
133
  let backend = IndexerBackend::from(&args.indexer);
151
134
 
152
- // The incremental benchmark re-indexes files after the initial build, so keep a copy of the
153
- // paths before `index_files` consumes them.
154
- let incremental_paths = if args.incremental_cycle.is_some() {
155
- file_paths.clone()
156
- } else {
157
- Vec::new()
158
- };
159
-
160
135
  let errors = time_it!(indexing, { indexing::index_files(&mut graph, file_paths, backend) });
161
136
 
162
137
  for error in errors {
@@ -174,12 +149,6 @@ fn main() {
174
149
  resolver.resolve();
175
150
  });
176
151
 
177
- // Incremental resolution benchmark. Runs before the stop-after check so it can be combined with
178
- // `--stop-after=resolution`.
179
- if let Some(cycles) = args.incremental_cycle {
180
- run_incremental_resolution(&mut graph, &incremental_paths, cycles, args.incremental_files, backend);
181
- }
182
-
183
152
  if let Some(StopAfter::Resolution) = args.stop_after {
184
153
  return exit(args.stats);
185
154
  }
@@ -241,91 +210,3 @@ fn main() {
241
210
  // Forget the graph so we don't have to wait for deallocation and let the system reclaim the memory at exit
242
211
  mem::forget(graph);
243
212
  }
244
-
245
- /// Simulates incremental editing to measure incremental resolution cost. For each cycle it
246
- /// re-indexes a rotating window of `files_per_cycle` files (parsing plus the same invalidation the
247
- /// LSP performs on save) and then re-runs resolution over the resulting pending work, reporting
248
- /// per-cycle and aggregate `resolve()` timings.
249
- ///
250
- /// With `--stats`, the `compute_descendants` breakdown printed in the timing summary reflects the
251
- /// last incremental cycle, since it is recorded on every `resolve()`.
252
- fn run_incremental_resolution(
253
- graph: &mut Graph,
254
- paths: &[PathBuf],
255
- cycles: usize,
256
- files_per_cycle: usize,
257
- backend: IndexerBackend,
258
- ) {
259
- if paths.is_empty() || cycles == 0 || files_per_cycle == 0 {
260
- eprintln!("Skipping incremental resolution: nothing to re-index");
261
- return;
262
- }
263
-
264
- let files_per_cycle = files_per_cycle.min(paths.len());
265
-
266
- let mut reindex_total = Duration::ZERO;
267
- let mut resolve_total = Duration::ZERO;
268
- let mut resolve_min = Duration::MAX;
269
- let mut resolve_max = Duration::ZERO;
270
-
271
- println!();
272
- println!("Incremental resolution ({cycles} cycle(s), {files_per_cycle} file(s)/cycle)");
273
- println!(" Scenario: no-op reindex; files are indexed again without changing their contents.");
274
- println!(" This should be the fastest incremental resolution path.");
275
- println!(" Add scenario-based benchmarks before optimizing incremental resolution.");
276
-
277
- for cycle in 0..cycles {
278
- // Re-index the window of files for this cycle. Rotating the window across cycles samples
279
- // different parts of the codebase rather than measuring the same delta repeatedly.
280
- let reindex_start = Instant::now();
281
- for i in 0..files_per_cycle {
282
- let path = &paths[(cycle * files_per_cycle + i) % paths.len()];
283
- reindex_file(graph, path, backend);
284
- }
285
- reindex_total += reindex_start.elapsed();
286
-
287
- let resolve_start = Instant::now();
288
- Resolver::new(graph).resolve();
289
- let elapsed = resolve_start.elapsed();
290
-
291
- resolve_total += elapsed;
292
- resolve_min = resolve_min.min(elapsed);
293
- resolve_max = resolve_max.max(elapsed);
294
-
295
- println!(
296
- " cycle {:>3}: resolve {:9.3}ms",
297
- cycle + 1,
298
- elapsed.as_secs_f64() * 1000.0
299
- );
300
- }
301
-
302
- let avg = resolve_total / u32::try_from(cycles).expect("cycle count fits in u32");
303
-
304
- println!(
305
- " resolve total {:.3}ms avg {:.3}ms min {:.3}ms max {:.3}ms",
306
- resolve_total.as_secs_f64() * 1000.0,
307
- avg.as_secs_f64() * 1000.0,
308
- resolve_min.as_secs_f64() * 1000.0,
309
- resolve_max.as_secs_f64() * 1000.0,
310
- );
311
- println!(
312
- " reindex+invalidate total {:.3}ms (parse + document merge, excluded from resolve above)",
313
- reindex_total.as_secs_f64() * 1000.0,
314
- );
315
- }
316
-
317
- /// Re-indexes a single file into the graph, running the same invalidation the LSP performs on save.
318
- fn reindex_file(graph: &mut Graph, path: &Path, backend: IndexerBackend) {
319
- let Ok(source) = fs::read_to_string(path) else {
320
- eprintln!("Failed to read file `{}`", path.display());
321
- return;
322
- };
323
- let Ok(url) = Url::from_file_path(path) else {
324
- eprintln!("Couldn't build URI from path `{}`", path.display());
325
- return;
326
- };
327
-
328
- let language = path.extension().map_or(LanguageId::Ruby, LanguageId::from);
329
- let local_graph = build_local_graph(url.to_string(), &source, &language, backend);
330
- graph.consume_document_changes(local_graph);
331
- }