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
@@ -12,7 +12,7 @@ use crate::model::definitions::{
12
12
  };
13
13
  use crate::model::document::Document;
14
14
  use crate::model::ids::{DefinitionId, NameId, StringId, UriId};
15
- use crate::model::name::{Name, ParentScope};
15
+ use crate::model::name::ParentScope;
16
16
  use crate::model::references::{ConstantReference, MethodRef};
17
17
  use crate::model::visibility::Visibility;
18
18
  use crate::offset::Offset;
@@ -386,21 +386,8 @@ impl<'a> RubyIndexer<'a> {
386
386
  {
387
387
  if let Some(arguments) = node.arguments() {
388
388
  for argument in &arguments.arguments() {
389
- match argument {
390
- ruby_prism::Node::SymbolNode { .. } => {
391
- let symbol = argument.as_symbol_node().unwrap();
392
-
393
- if let Some(value_loc) = symbol.value_loc() {
394
- let name = Self::location_to_string(&value_loc);
395
- f(name, value_loc);
396
- }
397
- }
398
- ruby_prism::Node::StringNode { .. } => {
399
- let string = argument.as_string_node().unwrap();
400
- let name = String::from_utf8_lossy(string.unescaped()).to_string();
401
- f(name, argument.location());
402
- }
403
- _ => {}
389
+ if let Some((name, location)) = Self::extract_literal_name(&argument) {
390
+ f(name, location);
404
391
  }
405
392
  }
406
393
  }
@@ -495,11 +482,9 @@ impl<'a> RubyIndexer<'a> {
495
482
  let offset = Offset::from_prism_location(&location);
496
483
  let name = Self::location_to_string(&location);
497
484
  let string_id = self.local_graph.intern_string(name);
498
- let name_id = self.local_graph.add_name(Name::new(
499
- string_id,
500
- parent_scope_id,
501
- self.current_lexical_scope_name_id(),
502
- ));
485
+ let name_id = self
486
+ .local_graph
487
+ .add_name(string_id, parent_scope_id, self.current_lexical_scope_name_id());
503
488
 
504
489
  if push_final_reference {
505
490
  self.local_graph
@@ -700,7 +685,7 @@ impl<'a> RubyIndexer<'a> {
700
685
  .intern_string(format!("{}:{}<anonymous>", self.uri_id, offset.start()));
701
686
 
702
687
  (
703
- Some(self.local_graph.add_name(Name::new(string_id, ParentScope::None, None))),
688
+ Some(self.local_graph.add_name(string_id, ParentScope::None, None)),
704
689
  offset.clone(),
705
690
  )
706
691
  };
@@ -761,7 +746,7 @@ impl<'a> RubyIndexer<'a> {
761
746
  .intern_string(format!("{}:{}<anonymous>", self.uri_id, offset.start()));
762
747
 
763
748
  (
764
- Some(self.local_graph.add_name(Name::new(string_id, ParentScope::None, None))),
749
+ Some(self.local_graph.add_name(string_id, ParentScope::None, None)),
765
750
  offset.clone(),
766
751
  )
767
752
  };
@@ -982,7 +967,7 @@ impl<'a> RubyIndexer<'a> {
982
967
  }
983
968
 
984
969
  Some((
985
- self.current_lexical_scope_name_id().unwrap(),
970
+ self.current_owner_name_id().unwrap(),
986
971
  Offset::from_prism_location(&arg.location()),
987
972
  ))
988
973
  } else if let Some(name_id) = self.index_constant_reference(&arg, false) {
@@ -1138,7 +1123,7 @@ impl<'a> RubyIndexer<'a> {
1138
1123
  }
1139
1124
  None => {
1140
1125
  let str_id = self.local_graph.intern_string("Object".into());
1141
- Some(self.local_graph.add_name(Name::new(str_id, ParentScope::None, None)))
1126
+ Some(self.local_graph.add_name(str_id, ParentScope::None, None))
1142
1127
  }
1143
1128
  }
1144
1129
  }
@@ -1182,7 +1167,7 @@ impl<'a> RubyIndexer<'a> {
1182
1167
  let string_id = self.local_graph.intern_string(singleton_class_name);
1183
1168
  let new_name_id = self
1184
1169
  .local_graph
1185
- .add_name(Name::new(string_id, ParentScope::Attached(name_id), None));
1170
+ .add_name(string_id, ParentScope::Attached(name_id), None);
1186
1171
 
1187
1172
  let location = receiver.map_or(fallback_location, ruby_prism::Node::location);
1188
1173
  let offset = Offset::from_prism_location(&location);
@@ -1193,10 +1178,11 @@ impl<'a> RubyIndexer<'a> {
1193
1178
 
1194
1179
  fn handle_constant_visibility(&mut self, node: &ruby_prism::CallNode, visibility: Visibility) {
1195
1180
  let receiver = node.receiver();
1181
+ let call_name = String::from_utf8_lossy(node.name().as_slice());
1196
1182
 
1197
- let receiver_name_id = match receiver {
1183
+ let receiver_name_id = match &receiver {
1198
1184
  Some(ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. }) => {
1199
- self.index_constant_reference(&receiver.unwrap(), true)
1185
+ self.index_constant_reference(receiver.as_ref().unwrap(), true)
1200
1186
  }
1201
1187
  Some(ruby_prism::Node::SelfNode { .. }) | None => match self.nesting_stack.last() {
1202
1188
  Some(Nesting::Method(_)) => {
@@ -1205,20 +1191,20 @@ impl<'a> RubyIndexer<'a> {
1205
1191
  }
1206
1192
  None => {
1207
1193
  self.local_graph.add_diagnostic(
1208
- Rule::InvalidPrivateConstant,
1194
+ Rule::InvalidConstantVisibility,
1209
1195
  Offset::from_prism_location(&node.location()),
1210
- "Private constant called at top level".to_string(),
1196
+ format!("`{call_name}` called at top level"),
1211
1197
  );
1212
1198
  self.visit_call_node_parts(node);
1213
1199
  return;
1214
1200
  }
1215
1201
  _ => None,
1216
1202
  },
1217
- _ => {
1203
+ Some(other) => {
1218
1204
  self.local_graph.add_diagnostic(
1219
- Rule::InvalidPrivateConstant,
1220
- Offset::from_prism_location(&node.location()),
1221
- "Dynamic receiver for private constant".to_string(),
1205
+ Rule::InvalidConstantVisibility,
1206
+ Offset::from_prism_location(&other.location()),
1207
+ format!("Dynamic receiver for `{call_name}`"),
1222
1208
  );
1223
1209
  self.visit_call_node_parts(node);
1224
1210
  return;
@@ -1230,29 +1216,14 @@ impl<'a> RubyIndexer<'a> {
1230
1216
  };
1231
1217
 
1232
1218
  for argument in &arguments.arguments() {
1233
- let (name, location) = match argument {
1234
- ruby_prism::Node::SymbolNode { .. } => {
1235
- let symbol = argument.as_symbol_node().unwrap();
1236
- if let Some(value_loc) = symbol.value_loc() {
1237
- (Self::location_to_string(&value_loc), value_loc)
1238
- } else {
1239
- continue;
1240
- }
1241
- }
1242
- ruby_prism::Node::StringNode { .. } => {
1243
- let string = argument.as_string_node().unwrap();
1244
- let name = String::from_utf8_lossy(string.unescaped()).to_string();
1245
- (name, argument.location())
1246
- }
1247
- _ => {
1248
- self.local_graph.add_diagnostic(
1249
- Rule::InvalidPrivateConstant,
1250
- Offset::from_prism_location(&argument.location()),
1251
- "Private constant called with non-symbol argument".to_string(),
1252
- );
1253
- self.visit(&argument);
1254
- continue;
1255
- }
1219
+ let Some((name, location)) = Self::extract_literal_name(&argument) else {
1220
+ self.local_graph.add_diagnostic(
1221
+ Rule::InvalidConstantVisibility,
1222
+ Offset::from_prism_location(&argument.location()),
1223
+ format!("`{call_name}` called with a non-literal argument"),
1224
+ );
1225
+ self.visit(&argument);
1226
+ continue;
1256
1227
  };
1257
1228
 
1258
1229
  let str_id = self.local_graph.intern_string(name);
@@ -1414,6 +1385,22 @@ impl<'a> RubyIndexer<'a> {
1414
1385
  }
1415
1386
  }
1416
1387
 
1388
+ fn extract_literal_name<'b>(arg: &ruby_prism::Node<'b>) -> Option<(String, ruby_prism::Location<'b>)> {
1389
+ match arg {
1390
+ ruby_prism::Node::SymbolNode { .. } => {
1391
+ let symbol = arg.as_symbol_node().unwrap();
1392
+ let value_loc = symbol.value_loc()?;
1393
+ Some((Self::location_to_string(&value_loc), value_loc))
1394
+ }
1395
+ ruby_prism::Node::StringNode { .. } => {
1396
+ let string = arg.as_string_node().unwrap();
1397
+ let name = String::from_utf8_lossy(string.unescaped()).to_string();
1398
+ Some((name, arg.location()))
1399
+ }
1400
+ _ => None,
1401
+ }
1402
+ }
1403
+
1417
1404
  fn is_attr_call(arg: &ruby_prism::Node) -> bool {
1418
1405
  arg.as_call_node().is_some_and(|call| {
1419
1406
  let receiver = call.receiver();
@@ -1452,6 +1439,18 @@ impl<'a> RubyIndexer<'a> {
1452
1439
  ruby_prism::Node::SymbolNode { .. } | ruby_prism::Node::StringNode { .. }
1453
1440
  ) {
1454
1441
  self.create_method_visibility_definition(&arg, visibility, DefinitionFlags::empty());
1442
+ if visibility == Visibility::ModuleFunction {
1443
+ // `module_function` also creates a public singleton method, so we emit a
1444
+ // second def for the singleton side. The two defs stay separate (rather than
1445
+ // shared) so reverse-lookup invalidation can detach `Foo#bar` and
1446
+ // `Foo::<Foo>#bar` independently. The `SINGLETON_METHOD_VISIBILITY` flag
1447
+ // routes this one to the singleton class.
1448
+ self.create_method_visibility_definition(
1449
+ &arg,
1450
+ visibility,
1451
+ DefinitionFlags::SINGLETON_METHOD_VISIBILITY,
1452
+ );
1453
+ }
1455
1454
  } else {
1456
1455
  // Unsupported arg — diagnostic + visit for side effects.
1457
1456
  let arg_offset = Offset::from_prism_location(&arg.location());
@@ -1473,18 +1472,8 @@ impl<'a> RubyIndexer<'a> {
1473
1472
  visibility: Visibility,
1474
1473
  flags: DefinitionFlags,
1475
1474
  ) {
1476
- let (name, location) = match arg {
1477
- ruby_prism::Node::SymbolNode { .. } => {
1478
- let symbol = arg.as_symbol_node().unwrap();
1479
- let Some(value_loc) = symbol.value_loc() else { return };
1480
- (Self::location_to_string(&value_loc), value_loc)
1481
- }
1482
- ruby_prism::Node::StringNode { .. } => {
1483
- let string = arg.as_string_node().unwrap();
1484
- let name = String::from_utf8_lossy(string.unescaped()).to_string();
1485
- (name, arg.location())
1486
- }
1487
- _ => return,
1475
+ let Some((name, location)) = Self::extract_literal_name(arg) else {
1476
+ return;
1488
1477
  };
1489
1478
 
1490
1479
  self.create_method_visibility_definition_from_name(&name, &location, visibility, flags);
@@ -1659,7 +1648,7 @@ impl Visit<'_> for RubyIndexer<'_> {
1659
1648
  let nesting = self.current_lexical_scope_name_id();
1660
1649
  let name_id = self
1661
1650
  .local_graph
1662
- .add_name(Name::new(string_id, ParentScope::Attached(attached_target), nesting));
1651
+ .add_name(string_id, ParentScope::Attached(attached_target), nesting);
1663
1652
 
1664
1653
  let definition = Definition::SingletonClass(Box::new(SingletonClassDefinition::new(
1665
1654
  name_id,