rubydex 0.4.0-aarch64-linux → 0.4.2-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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -14
  3. data/THIRD_PARTY_LICENSES.html +13 -24
  4. data/ext/rubydex/definition.c +34 -0
  5. data/ext/rubydex/definition.h +3 -0
  6. data/ext/rubydex/diagnostic.c +2 -49
  7. data/ext/rubydex/extconf.rb +4 -2
  8. data/ext/rubydex/graph.c +59 -11
  9. data/lib/rubydex/3.2/rubydex.so +0 -0
  10. data/lib/rubydex/3.3/rubydex.so +0 -0
  11. data/lib/rubydex/3.4/rubydex.so +0 -0
  12. data/lib/rubydex/4.0/rubydex.so +0 -0
  13. data/lib/rubydex/cli/command/dead_code.rb +59 -0
  14. data/lib/rubydex/cli/command/lint/explain.rb +8 -6
  15. data/lib/rubydex/cli/command/lint.rb +10 -4
  16. data/lib/rubydex/librubydex_sys.so +0 -0
  17. data/lib/rubydex/linter/rule_loader.rb +1 -1
  18. data/lib/rubydex/mcp_server/tools/codebase_stats_tool.rb +1 -1
  19. data/lib/rubydex/mcp_server/tools/find_constant_references_tool.rb +4 -4
  20. data/lib/rubydex/mcp_server/tools/find_dead_code_candidates_tool.rb +33 -0
  21. data/lib/rubydex/mcp_server/tools/get_declaration_tool.rb +2 -2
  22. data/lib/rubydex/mcp_server/tools/get_descendants_tool.rb +4 -4
  23. data/lib/rubydex/mcp_server/tools/get_file_declarations_tool.rb +2 -2
  24. data/lib/rubydex/mcp_server/tools/search_declarations_tool.rb +6 -6
  25. data/lib/rubydex/mcp_server.rb +24 -12
  26. data/lib/rubydex/rules/dynamic_ancestor.rb +29 -0
  27. data/lib/rubydex/rules/dynamic_constant_reference.rb +25 -0
  28. data/lib/rubydex/rules/dynamic_singleton_definition.rb +30 -0
  29. data/lib/rubydex/rules/invalid_constant_visibility.rb +28 -0
  30. data/lib/rubydex/rules/invalid_method_visibility.rb +28 -0
  31. data/lib/rubydex/rules/parse_error.rb +25 -0
  32. data/lib/rubydex/rules/parse_warning.rb +28 -0
  33. data/lib/rubydex/rules/top_level_mixin_self.rb +27 -0
  34. data/lib/rubydex/rules/undefined_constant_visibility_target.rb +27 -0
  35. data/lib/rubydex/rules/undefined_method_visibility_target.rb +27 -0
  36. data/lib/rubydex/skill.rb +1 -0
  37. data/lib/rubydex/version.rb +1 -1
  38. data/lib/rubydex.rb +2 -0
  39. data/lib/rubydex_linter/rules/rule_structure.rb +19 -4
  40. data/rbi/rubydex.rbi +32 -130
  41. data/rust/Cargo.lock +17 -11
  42. data/rust/Cargo.toml +3 -0
  43. data/rust/rubydex/Cargo.toml +6 -2
  44. data/rust/rubydex/src/bin/generate_ruby_rules.rs +94 -0
  45. data/rust/rubydex/src/compile_assertions.rs +2 -0
  46. data/rust/rubydex/src/config.rs +1 -1
  47. data/rust/rubydex/src/diagnostic.rs +142 -51
  48. data/rust/rubydex/src/indexing/rbs_indexer.rs +4 -4
  49. data/rust/rubydex/src/indexing/ruby_indexer.rs +26 -18
  50. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +42 -2
  51. data/rust/rubydex/src/indexing.rs +6 -6
  52. data/rust/rubydex/src/listing.rs +12 -12
  53. data/rust/rubydex/src/model/built_in.rs +1 -1
  54. data/rust/rubydex/src/model/comment.rs +4 -4
  55. data/rust/rubydex/src/model/declaration.rs +25 -17
  56. data/rust/rubydex/src/model/document.rs +11 -11
  57. data/rust/rubydex/src/model/graph.rs +3 -3
  58. data/rust/rubydex/src/offset.rs +1 -13
  59. data/rust/rubydex/src/operation/ruby_builder.rs +29 -19
  60. data/rust/rubydex/src/query.rs +193 -2
  61. data/rust/rubydex/src/test_utils/graph_test.rs +2 -2
  62. data/rust/rubydex/src/test_utils/local_graph_test.rs +13 -6
  63. data/rust/rubydex-sys/Cargo.toml +2 -2
  64. data/rust/rubydex-sys/src/definition_api.rs +69 -2
  65. data/rust/rubydex-sys/src/diagnostic_api.rs +0 -35
  66. data/rust/rubydex-sys/src/graph_api.rs +193 -45
  67. data/rust/rubydex-sys/src/name_api.rs +219 -48
  68. metadata +199 -187
@@ -10,9 +10,10 @@ use crate::{name_api, utils};
10
10
  use libc::{c_char, c_void};
11
11
  use rubydex::config::Config;
12
12
  use rubydex::indexing::LanguageId;
13
+ use rubydex::model::definitions::Definition;
13
14
  use rubydex::model::encoding::Encoding;
14
15
  use rubydex::model::graph::Graph;
15
- use rubydex::model::ids::{DeclarationId, NameId, UriId, declaration_id_from_lookup_name};
16
+ use rubydex::model::ids::{DeclarationId, DefinitionId, NameId, UriId, declaration_id_from_lookup_name};
16
17
  use rubydex::model::keywords;
17
18
  use rubydex::model::name::NameRef;
18
19
  use rubydex::model::visibility::Visibility;
@@ -133,6 +134,27 @@ pub unsafe extern "C" fn rdx_graph_declarations_fuzzy_search(
133
134
  DeclarationsIter::new(entries)
134
135
  }
135
136
 
137
+ /// Returns an iterator over all dead code candidates in the graph.
138
+ ///
139
+ /// # Safety
140
+ ///
141
+ /// Expects `pointer` to be a valid graph. The returned iterator must be freed with `rdx_graph_declarations_iter_free`.
142
+ #[unsafe(no_mangle)]
143
+ pub unsafe extern "C" fn rdx_graph_dead_code_candidates(pointer: GraphPointer) -> *mut DeclarationsIter {
144
+ let entries = with_graph(pointer, |graph| {
145
+ query::dead_code_candidates(graph)
146
+ .into_iter()
147
+ .filter_map(|id| {
148
+ let decl = graph.declarations().get(&id)?;
149
+ Some(CDeclaration::from_declaration(id, decl))
150
+ })
151
+ .collect::<Vec<CDeclaration>>()
152
+ .into_boxed_slice()
153
+ });
154
+
155
+ DeclarationsIter::new(entries)
156
+ }
157
+
136
158
  /// # Panics
137
159
  ///
138
160
  /// Will panic if the nesting cannot be transformed into a vector of strings
@@ -151,25 +173,98 @@ pub unsafe extern "C" fn rdx_graph_resolve_constant(
151
173
  let nesting: Vec<String> = unsafe { utils::convert_double_pointer_to_vec(nesting, count).unwrap() };
152
174
  let const_name: String = unsafe { utils::convert_char_ptr_to_string(const_name).unwrap() };
153
175
 
154
- let Some((name_id, names_to_untrack)) = name_api::nesting_stack_to_name_id(graph, &const_name, nesting) else {
176
+ let Some(tracked_name) = name_api::name_from_nesting_names(graph, &const_name, nesting) else {
155
177
  return ptr::null();
156
178
  };
157
179
 
158
- let mut resolver = Resolver::new(graph);
180
+ resolve_constant_name(tracked_name)
181
+ })
182
+ }
183
+
184
+ /// Result of resolving a constant using a namespace definition as its lexical context.
185
+ #[repr(u8)]
186
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
187
+ pub enum CDefinitionConstantResolution {
188
+ Resolved = 0,
189
+ NotFound = 1,
190
+ DefinitionNotFound = 2,
191
+ InvalidDefinitionKind = 3,
192
+ }
193
+
194
+ #[repr(C)]
195
+ #[derive(Debug)]
196
+ pub struct CDefinitionConstantResolutionResult {
197
+ pub status: CDefinitionConstantResolution,
198
+ pub declaration: *const CDeclaration,
199
+ }
159
200
 
160
- let declaration = match resolver.resolve_constant(name_id) {
161
- Some(id) => {
162
- let decl = graph.declarations().get(&id).unwrap();
163
- Box::into_raw(Box::new(CDeclaration::from_declaration(id, decl))).cast_const()
201
+ /// Resolves a constant using a namespace definition as its lexical context.
202
+ ///
203
+ /// # Panics
204
+ ///
205
+ /// Panics if `const_name` cannot be converted to a string.
206
+ ///
207
+ /// # Safety
208
+ ///
209
+ /// Assumes that `pointer` and `const_name` are valid.
210
+ #[unsafe(no_mangle)]
211
+ pub unsafe extern "C" fn rdx_graph_resolve_constant_from_definition(
212
+ pointer: GraphPointer,
213
+ const_name: *const c_char,
214
+ definition_id: u64,
215
+ ) -> CDefinitionConstantResolutionResult {
216
+ with_mut_graph(pointer, |graph| {
217
+ let const_name: String = unsafe { utils::convert_char_ptr_to_string(const_name).unwrap() };
218
+ let nesting = match class_or_module_definition_name_id(graph, DefinitionId::new(definition_id)) {
219
+ Ok(nesting) => nesting,
220
+ Err(status) => {
221
+ return CDefinitionConstantResolutionResult {
222
+ status,
223
+ declaration: ptr::null(),
224
+ };
164
225
  }
165
- None => ptr::null(),
166
226
  };
167
227
 
168
- for name_id in names_to_untrack {
169
- graph.untrack_name(name_id);
170
- }
228
+ let Some(tracked_name) = name_api::name_from_lexical_nesting(graph, &const_name, Some(nesting)) else {
229
+ return CDefinitionConstantResolutionResult {
230
+ status: CDefinitionConstantResolution::NotFound,
231
+ declaration: ptr::null(),
232
+ };
233
+ };
234
+
235
+ let declaration = resolve_constant_name(tracked_name);
236
+ let status = if declaration.is_null() {
237
+ CDefinitionConstantResolution::NotFound
238
+ } else {
239
+ CDefinitionConstantResolution::Resolved
240
+ };
241
+
242
+ CDefinitionConstantResolutionResult { status, declaration }
243
+ })
244
+ }
171
245
 
172
- declaration
246
+ fn class_or_module_definition_name_id(
247
+ graph: &Graph,
248
+ definition_id: DefinitionId,
249
+ ) -> Result<NameId, CDefinitionConstantResolution> {
250
+ let Some(definition) = graph.definitions().get(&definition_id) else {
251
+ return Err(CDefinitionConstantResolution::DefinitionNotFound);
252
+ };
253
+
254
+ match definition {
255
+ Definition::Class(definition) => Ok(*definition.name_id()),
256
+ Definition::SingletonClass(definition) => Ok(*definition.name_id()),
257
+ Definition::Module(definition) => Ok(*definition.name_id()),
258
+ _ => Err(CDefinitionConstantResolution::InvalidDefinitionKind),
259
+ }
260
+ }
261
+
262
+ fn resolve_constant_name(mut tracked_name: name_api::TrackedName<'_>) -> *const CDeclaration {
263
+ let name_id = tracked_name.name_id();
264
+ let declaration_id = Resolver::new(tracked_name.graph_mut()).resolve_constant(name_id);
265
+ declaration_id.map_or(ptr::null(), |id| {
266
+ let declaration = tracked_name.graph().declarations().get(&id).unwrap();
267
+ Box::into_raw(Box::new(CDeclaration::from_declaration(id, declaration))).cast_const()
173
268
  })
174
269
  }
175
270
 
@@ -644,7 +739,7 @@ pub unsafe extern "C" fn rdx_index_source(
644
739
  };
645
740
 
646
741
  with_mut_graph(pointer, |graph| {
647
- indexing::index_source(graph, &uri_str, source_str, &language);
742
+ indexing::index_source(graph, uri_str.into(), source_str, &language);
648
743
  IndexSourceResult::Success
649
744
  })
650
745
  }
@@ -714,19 +809,19 @@ pub unsafe extern "C" fn rdx_completion_candidates_free(ptr: *mut CompletionCand
714
809
  }
715
810
  }
716
811
 
717
- /// Converts the nesting stack into a `NameId`.
718
- /// The last element of the nesting stack is treated as the self type; if the stack is empty, `"Object"` is used.
812
+ /// Converts the nesting names into a tracked name.
813
+ /// The last nesting name is treated as the self type; if the array is empty, `"Object"` is used.
719
814
  ///
720
- /// Returns `Err` if the nesting array contains invalid UTF-8.
815
+ /// Returns `None` if the nesting array contains invalid UTF-8.
721
816
  ///
722
817
  /// # Safety
723
818
  ///
724
819
  /// `nesting` must point to `nesting_count` valid, null-terminated UTF-8 strings.
725
- unsafe fn completion_nesting_name_id(
820
+ unsafe fn completion_nesting_name(
726
821
  graph: &mut Graph,
727
822
  nesting: *const *const c_char,
728
823
  nesting_count: usize,
729
- ) -> Option<(NameId, Vec<NameId>)> {
824
+ ) -> Option<name_api::TrackedName<'_>> {
730
825
  let mut nesting: Vec<String> = unsafe { utils::convert_double_pointer_to_vec(nesting, nesting_count).ok()? };
731
826
 
732
827
  // When serving completion in a bare script, the self (top level) context is Object
@@ -736,7 +831,7 @@ unsafe fn completion_nesting_name_id(
736
831
  nesting.pop().unwrap()
737
832
  };
738
833
 
739
- name_api::nesting_stack_to_name_id(graph, &self_name, nesting)
834
+ name_api::name_from_nesting_names(graph, &self_name, nesting)
740
835
  }
741
836
 
742
837
  /// The result of a completion operation, carrying either a candidate array or an error message.
@@ -765,19 +860,10 @@ impl CompletionResult {
765
860
  }
766
861
 
767
862
  /// Runs completion for the given receiver and returns a structured result with candidates or an error message
768
- fn run_and_finalize_completion(
769
- graph: &mut Graph,
770
- receiver: CompletionReceiver,
771
- names_to_untrack: Vec<NameId>,
772
- ) -> CompletionResult {
863
+ fn run_and_finalize_completion(graph: &Graph, receiver: CompletionReceiver) -> CompletionResult {
773
864
  let candidates = match query::completion_candidates(graph, CompletionContext::new(receiver)) {
774
865
  Ok(candidates) => candidates,
775
- Err(e) => {
776
- for name_id in names_to_untrack {
777
- graph.untrack_name(name_id);
778
- }
779
- return CompletionResult::error(&e.to_string());
780
- }
866
+ Err(e) => return CompletionResult::error(&e.to_string()),
781
867
  };
782
868
 
783
869
  let entries: Vec<CCompletionCandidate> = candidates
@@ -825,10 +911,6 @@ fn run_and_finalize_completion(
825
911
  })
826
912
  .collect();
827
913
 
828
- for name_id in names_to_untrack {
829
- graph.untrack_name(name_id);
830
- }
831
-
832
914
  CompletionResult::success(CompletionCandidateArray::from_vec(entries))
833
915
  }
834
916
 
@@ -849,20 +931,19 @@ pub unsafe extern "C" fn rdx_graph_complete_expression(
849
931
  self_receiver: *const c_char,
850
932
  ) -> CompletionResult {
851
933
  with_mut_graph(pointer, |graph| {
852
- let Some((name_id, names_to_untrack)) = (unsafe { completion_nesting_name_id(graph, nesting, nesting_count) })
853
- else {
934
+ let Some(tracked_name) = (unsafe { completion_nesting_name(graph, nesting, nesting_count) }) else {
854
935
  return CompletionResult::success(ptr::null_mut());
855
936
  };
937
+ let name_id = tracked_name.name_id();
856
938
 
857
939
  let self_decl_id = unsafe { decl_id_from_char_ptr(self_receiver) };
858
940
 
859
941
  run_and_finalize_completion(
860
- graph,
942
+ tracked_name.graph(),
861
943
  CompletionReceiver::Expression {
862
944
  self_decl_id,
863
945
  nesting_name_id: name_id,
864
946
  },
865
- names_to_untrack,
866
947
  )
867
948
  })
868
949
  }
@@ -896,7 +977,6 @@ pub unsafe extern "C" fn rdx_graph_complete_namespace_access(
896
977
  self_decl_id,
897
978
  namespace_decl_id: declaration_id_from_lookup_name(&name_str),
898
979
  },
899
- Vec::new(),
900
980
  )
901
981
  })
902
982
  }
@@ -930,7 +1010,6 @@ pub unsafe extern "C" fn rdx_graph_complete_method_call(
930
1010
  self_decl_id,
931
1011
  receiver_decl_id: declaration_id_from_lookup_name(&name_str),
932
1012
  },
933
- Vec::new(),
934
1013
  )
935
1014
  })
936
1015
  }
@@ -959,22 +1038,20 @@ pub unsafe extern "C" fn rdx_graph_complete_method_argument(
959
1038
  };
960
1039
 
961
1040
  with_mut_graph(pointer, |graph| {
962
- let Some((nesting_name_id, names_to_untrack)) =
963
- (unsafe { completion_nesting_name_id(graph, nesting, nesting_count) })
964
- else {
1041
+ let Some(tracked_name) = (unsafe { completion_nesting_name(graph, nesting, nesting_count) }) else {
965
1042
  return CompletionResult::success(ptr::null_mut());
966
1043
  };
1044
+ let nesting_name_id = tracked_name.name_id();
967
1045
 
968
1046
  let self_decl_id = unsafe { decl_id_from_char_ptr(self_receiver) };
969
1047
 
970
1048
  run_and_finalize_completion(
971
- graph,
1049
+ tracked_name.graph(),
972
1050
  CompletionReceiver::MethodArgument {
973
1051
  self_decl_id,
974
1052
  nesting_name_id,
975
1053
  method_decl_id: declaration_id_from_lookup_name(&name_str),
976
1054
  },
977
- names_to_untrack,
978
1055
  )
979
1056
  })
980
1057
  }
@@ -1095,6 +1172,7 @@ pub unsafe extern "C" fn rdx_keyword_free(ptr: *const CKeyword) {
1095
1172
 
1096
1173
  #[cfg(test)]
1097
1174
  mod tests {
1175
+ use crate::declaration_api;
1098
1176
  use rubydex::indexing::ruby_indexer::RubyIndexer;
1099
1177
 
1100
1178
  use super::*;
@@ -1145,7 +1223,9 @@ mod tests {
1145
1223
  nesting_ptrs.as_ptr(),
1146
1224
  nesting_ptrs.len(),
1147
1225
  );
1226
+ assert!(!decl.is_null());
1148
1227
  assert_eq!((*decl).id(), *DeclarationId::from("Foo::BAR"));
1228
+ declaration_api::free_c_declaration(decl);
1149
1229
  };
1150
1230
 
1151
1231
  let graph = unsafe { Box::from_raw(graph_ptr.cast::<RwLock<Graph>>()) };
@@ -1167,4 +1247,72 @@ mod tests {
1167
1247
  .ref_count()
1168
1248
  );
1169
1249
  }
1250
+
1251
+ #[test]
1252
+ fn names_are_untracked_after_completion_error() {
1253
+ let mut indexer = RubyIndexer::new(
1254
+ "file:///foo.rb".into(),
1255
+ "
1256
+ class Foo
1257
+ end
1258
+ ",
1259
+ );
1260
+ indexer.index();
1261
+
1262
+ let mut graph = Graph::new();
1263
+ graph.consume_document_changes(indexer.local_graph());
1264
+ let mut resolver = Resolver::new(&mut graph);
1265
+ resolver.resolve();
1266
+
1267
+ let (foo_name_id, foo_string_id, name_ref_count, string_ref_count) = graph
1268
+ .names()
1269
+ .iter()
1270
+ .find_map(|(name_id, name)| {
1271
+ let string_id = *name.str();
1272
+ if graph.strings().get(&string_id).unwrap().as_str() == "Foo"
1273
+ && name.parent_scope().is_none()
1274
+ && name.nesting().is_none()
1275
+ {
1276
+ Some((
1277
+ *name_id,
1278
+ string_id,
1279
+ name.ref_count(),
1280
+ graph.strings().get(&string_id).unwrap().ref_count(),
1281
+ ))
1282
+ } else {
1283
+ None
1284
+ }
1285
+ })
1286
+ .unwrap();
1287
+ let name_count = graph.names().len();
1288
+ let string_count = graph.strings().len();
1289
+
1290
+ let graph_ptr = Box::into_raw(Box::new(RwLock::new(graph))) as GraphPointer;
1291
+ let nesting_strings = [CString::new("Foo").unwrap()];
1292
+ let nesting_ptrs: Vec<*const c_char> = nesting_strings.iter().map(|s| s.as_ptr()).collect();
1293
+ let self_receiver = CString::new("Nonexistent").unwrap();
1294
+
1295
+ let result = unsafe {
1296
+ rdx_graph_complete_expression(
1297
+ graph_ptr,
1298
+ nesting_ptrs.as_ptr(),
1299
+ nesting_ptrs.len(),
1300
+ self_receiver.as_ptr(),
1301
+ )
1302
+ };
1303
+ assert!(result.candidates.is_null());
1304
+ assert!(!result.error.is_null());
1305
+ utils::free_c_string(result.error);
1306
+
1307
+ let graph = unsafe { Box::from_raw(graph_ptr.cast::<RwLock<Graph>>()) };
1308
+ let graph = graph.read().unwrap();
1309
+
1310
+ assert_eq!(name_count, graph.names().len());
1311
+ assert_eq!(string_count, graph.strings().len());
1312
+ assert_eq!(name_ref_count, graph.names().get(&foo_name_id).unwrap().ref_count());
1313
+ assert_eq!(
1314
+ string_ref_count,
1315
+ graph.strings().get(&foo_string_id).unwrap().ref_count()
1316
+ );
1317
+ }
1170
1318
  }