rubydex 0.2.8 → 0.3.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -3
  3. data/THIRD_PARTY_LICENSES.html +168 -1381
  4. data/exe/rdx +136 -34
  5. data/ext/rubydex/declaration.c +1 -1
  6. data/ext/rubydex/definition.c +32 -4
  7. data/ext/rubydex/extconf.rb +0 -8
  8. data/ext/rubydex/graph.c +29 -18
  9. data/ext/rubydex/query.c +105 -0
  10. data/ext/rubydex/query.h +8 -0
  11. data/ext/rubydex/reference.c +60 -0
  12. data/ext/rubydex/rubydex.c +2 -0
  13. data/ext/rubydex/utils.c +12 -0
  14. data/ext/rubydex/utils.h +5 -0
  15. data/lib/rubydex/mcp_server/protocol.rb +156 -0
  16. data/lib/rubydex/mcp_server/tools/base_tool.rb +109 -0
  17. data/lib/rubydex/mcp_server/tools/codebase_stats_tool.rb +30 -0
  18. data/lib/rubydex/mcp_server/tools/find_constant_references_tool.rb +46 -0
  19. data/lib/rubydex/mcp_server/tools/get_declaration_tool.rb +68 -0
  20. data/lib/rubydex/mcp_server/tools/get_descendants_tool.rb +46 -0
  21. data/lib/rubydex/mcp_server/tools/get_file_declarations_tool.rb +55 -0
  22. data/lib/rubydex/mcp_server/tools/search_declarations_tool.rb +55 -0
  23. data/lib/rubydex/mcp_server.rb +219 -0
  24. data/lib/rubydex/version.rb +1 -1
  25. data/rbi/rubydex.rbi +26 -4
  26. data/rust/Cargo.lock +5 -552
  27. data/rust/Cargo.toml +0 -1
  28. data/rust/rubydex/Cargo.toml +1 -0
  29. data/rust/rubydex/benches/graph_memory.rs +22 -4
  30. data/rust/rubydex/src/compile_assertions.rs +15 -0
  31. data/rust/rubydex/src/config.rs +54 -34
  32. data/rust/rubydex/src/diagnostic.rs +1 -1
  33. data/rust/rubydex/src/indexing/rbs_indexer.rs +14 -2
  34. data/rust/rubydex/src/indexing/ruby_indexer.rs +49 -58
  35. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +72 -16
  36. data/rust/rubydex/src/listing.rs +159 -102
  37. data/rust/rubydex/src/main.rs +3 -125
  38. data/rust/rubydex/src/model/declaration.rs +0 -11
  39. data/rust/rubydex/src/model/definitions.rs +27 -26
  40. data/rust/rubydex/src/model/document.rs +43 -7
  41. data/rust/rubydex/src/model/graph.rs +47 -35
  42. data/rust/rubydex/src/model/id.rs +55 -0
  43. data/rust/rubydex/src/model/ids.rs +21 -9
  44. data/rust/rubydex/src/model/name.rs +35 -7
  45. data/rust/rubydex/src/model/references.rs +16 -13
  46. data/rust/rubydex/src/operation/applier.rs +0 -2
  47. data/rust/rubydex/src/operation/ruby_builder.rs +48 -59
  48. data/rust/rubydex/src/query/cypher/schema.rs +790 -0
  49. data/rust/rubydex/src/query/cypher/schema_info.rs +161 -0
  50. data/rust/rubydex/src/query/cypher/tests.rs +228 -0
  51. data/rust/rubydex/src/query/cypher.rs +57 -0
  52. data/rust/rubydex/src/query.rs +2 -0
  53. data/rust/rubydex/src/resolution.rs +248 -227
  54. data/rust/rubydex/src/resolution_tests.rs +263 -65
  55. data/rust/rubydex-sys/src/declaration_api.rs +6 -3
  56. data/rust/rubydex-sys/src/definition_api.rs +27 -7
  57. data/rust/rubydex-sys/src/graph_api.rs +175 -27
  58. data/rust/rubydex-sys/src/reference_api.rs +58 -12
  59. metadata +17 -10
  60. data/exe/rubydex_mcp +0 -17
  61. data/lib/rubydex/bin/rubydex_mcp.exe +0 -0
  62. data/rust/rubydex-mcp/Cargo.toml +0 -34
  63. data/rust/rubydex-mcp/src/main.rs +0 -48
  64. data/rust/rubydex-mcp/src/server.rs +0 -1148
  65. data/rust/rubydex-mcp/src/tools.rs +0 -49
  66. data/rust/rubydex-mcp/tests/mcp.rs +0 -302
@@ -611,20 +611,8 @@ impl<'a> RubyOperationBuilder<'a> {
611
611
  {
612
612
  if let Some(arguments) = node.arguments() {
613
613
  for argument in &arguments.arguments() {
614
- match argument {
615
- ruby_prism::Node::SymbolNode { .. } => {
616
- let symbol = argument.as_symbol_node().unwrap();
617
- if let Some(value_loc) = symbol.value_loc() {
618
- let name = Self::location_to_string(&value_loc);
619
- f(name, value_loc);
620
- }
621
- }
622
- ruby_prism::Node::StringNode { .. } => {
623
- let string = argument.as_string_node().unwrap();
624
- let name = String::from_utf8_lossy(string.unescaped()).to_string();
625
- f(name, argument.location());
626
- }
627
- _ => {}
614
+ if let Some((name, location)) = Self::extract_literal_name(&argument) {
615
+ f(name, location);
628
616
  }
629
617
  }
630
618
  }
@@ -899,28 +887,29 @@ impl<'a> RubyOperationBuilder<'a> {
899
887
 
900
888
  fn handle_constant_visibility(&mut self, node: &ruby_prism::CallNode, visibility: Visibility) {
901
889
  let receiver = node.receiver();
890
+ let call_name = String::from_utf8_lossy(node.name().as_slice());
902
891
 
903
- let receiver_name_id = match receiver {
892
+ let receiver_name_id = match &receiver {
904
893
  Some(ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. }) => {
905
- self.index_constant_reference(&receiver.unwrap(), true)
894
+ self.index_constant_reference(receiver.as_ref().unwrap(), true)
906
895
  }
907
896
  Some(ruby_prism::Node::SelfNode { .. }) | None => match self.nesting_stack.last() {
908
897
  Some(Nesting::Method { .. }) => return,
909
898
  None => {
910
899
  self.add_diagnostic(
911
- Rule::InvalidPrivateConstant,
900
+ Rule::InvalidConstantVisibility,
912
901
  Offset::from_prism_location(&node.location()),
913
- "Private constant called at top level".to_string(),
902
+ format!("`{call_name}` called at top level"),
914
903
  );
915
904
  return;
916
905
  }
917
906
  _ => None,
918
907
  },
919
- _ => {
908
+ Some(other) => {
920
909
  self.add_diagnostic(
921
- Rule::InvalidPrivateConstant,
922
- Offset::from_prism_location(&node.location()),
923
- "Dynamic receiver for private constant".to_string(),
910
+ Rule::InvalidConstantVisibility,
911
+ Offset::from_prism_location(&other.location()),
912
+ format!("Dynamic receiver for `{call_name}`"),
924
913
  );
925
914
  return;
926
915
  }
@@ -931,28 +920,13 @@ impl<'a> RubyOperationBuilder<'a> {
931
920
  };
932
921
 
933
922
  for argument in &arguments.arguments() {
934
- let (name, location) = match argument {
935
- ruby_prism::Node::SymbolNode { .. } => {
936
- let symbol = argument.as_symbol_node().unwrap();
937
- if let Some(value_loc) = symbol.value_loc() {
938
- (Self::location_to_string(&value_loc), value_loc)
939
- } else {
940
- continue;
941
- }
942
- }
943
- ruby_prism::Node::StringNode { .. } => {
944
- let string = argument.as_string_node().unwrap();
945
- let name = String::from_utf8_lossy(string.unescaped()).to_string();
946
- (name, argument.location())
947
- }
948
- _ => {
949
- self.add_diagnostic(
950
- Rule::InvalidPrivateConstant,
951
- Offset::from_prism_location(&argument.location()),
952
- "Private constant called with non-symbol argument".to_string(),
953
- );
954
- continue;
955
- }
923
+ let Some((name, location)) = Self::extract_literal_name(&argument) else {
924
+ self.add_diagnostic(
925
+ Rule::InvalidConstantVisibility,
926
+ Offset::from_prism_location(&argument.location()),
927
+ format!("`{call_name}` called with a non-literal argument"),
928
+ );
929
+ continue;
956
930
  };
957
931
 
958
932
  let str_id = self.intern_string(name);
@@ -1067,6 +1041,22 @@ impl<'a> RubyOperationBuilder<'a> {
1067
1041
  Some(())
1068
1042
  }
1069
1043
 
1044
+ fn extract_literal_name<'b>(arg: &ruby_prism::Node<'b>) -> Option<(String, ruby_prism::Location<'b>)> {
1045
+ match arg {
1046
+ ruby_prism::Node::SymbolNode { .. } => {
1047
+ let symbol = arg.as_symbol_node().unwrap();
1048
+ let value_loc = symbol.value_loc()?;
1049
+ Some((Self::location_to_string(&value_loc), value_loc))
1050
+ }
1051
+ ruby_prism::Node::StringNode { .. } => {
1052
+ let string = arg.as_string_node().unwrap();
1053
+ let name = String::from_utf8_lossy(string.unescaped()).to_string();
1054
+ Some((name, arg.location()))
1055
+ }
1056
+ _ => None,
1057
+ }
1058
+ }
1059
+
1070
1060
  fn is_attr_call(arg: &ruby_prism::Node) -> bool {
1071
1061
  arg.as_call_node().is_some_and(|call| {
1072
1062
  let receiver = call.receiver();
@@ -1116,6 +1106,18 @@ impl<'a> RubyOperationBuilder<'a> {
1116
1106
  ruby_prism::Node::SymbolNode { .. } | ruby_prism::Node::StringNode { .. }
1117
1107
  ) {
1118
1108
  self.create_method_visibility_operation(&arg, visibility, DefinitionFlags::empty());
1109
+ if visibility == Visibility::ModuleFunction {
1110
+ // `module_function` also creates a public singleton method, so we emit a
1111
+ // second def for the singleton side. The two defs stay separate (rather than
1112
+ // shared) so reverse-lookup invalidation can detach `Foo#bar` and
1113
+ // `Foo::<Foo>#bar` independently. The `SINGLETON_METHOD_VISIBILITY` flag
1114
+ // routes this one to the singleton class.
1115
+ self.create_method_visibility_operation(
1116
+ &arg,
1117
+ visibility,
1118
+ DefinitionFlags::SINGLETON_METHOD_VISIBILITY,
1119
+ );
1120
+ }
1119
1121
  } else {
1120
1122
  let arg_offset = Offset::from_prism_location(&arg.location());
1121
1123
  let message = if Self::is_attr_call(&arg) {
@@ -1135,21 +1137,8 @@ impl<'a> RubyOperationBuilder<'a> {
1135
1137
  visibility: Visibility,
1136
1138
  flags: DefinitionFlags,
1137
1139
  ) {
1138
- let (name, location) = match arg {
1139
- ruby_prism::Node::SymbolNode { .. } => {
1140
- let symbol = arg.as_symbol_node().unwrap();
1141
- if let Some(value_loc) = symbol.value_loc() {
1142
- (Self::location_to_string(&value_loc), value_loc)
1143
- } else {
1144
- return;
1145
- }
1146
- }
1147
- ruby_prism::Node::StringNode { .. } => {
1148
- let string = arg.as_string_node().unwrap();
1149
- let name = String::from_utf8_lossy(string.unescaped()).to_string();
1150
- (name, arg.location())
1151
- }
1152
- _ => return,
1140
+ let Some((name, location)) = Self::extract_literal_name(arg) else {
1141
+ return;
1153
1142
  };
1154
1143
 
1155
1144
  let str_id = self.intern_string(format!("{name}()"));