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.
- checksums.yaml +4 -4
- data/README.md +9 -14
- data/THIRD_PARTY_LICENSES.html +13 -24
- data/ext/rubydex/definition.c +34 -0
- data/ext/rubydex/definition.h +3 -0
- data/ext/rubydex/diagnostic.c +2 -49
- data/ext/rubydex/extconf.rb +4 -2
- data/ext/rubydex/graph.c +59 -11
- data/lib/rubydex/3.2/rubydex.so +0 -0
- data/lib/rubydex/3.3/rubydex.so +0 -0
- data/lib/rubydex/3.4/rubydex.so +0 -0
- data/lib/rubydex/4.0/rubydex.so +0 -0
- data/lib/rubydex/cli/command/dead_code.rb +59 -0
- data/lib/rubydex/cli/command/lint/explain.rb +8 -6
- data/lib/rubydex/cli/command/lint.rb +10 -4
- data/lib/rubydex/librubydex_sys.so +0 -0
- data/lib/rubydex/linter/rule_loader.rb +1 -1
- data/lib/rubydex/mcp_server/tools/codebase_stats_tool.rb +1 -1
- data/lib/rubydex/mcp_server/tools/find_constant_references_tool.rb +4 -4
- data/lib/rubydex/mcp_server/tools/find_dead_code_candidates_tool.rb +33 -0
- data/lib/rubydex/mcp_server/tools/get_declaration_tool.rb +2 -2
- data/lib/rubydex/mcp_server/tools/get_descendants_tool.rb +4 -4
- data/lib/rubydex/mcp_server/tools/get_file_declarations_tool.rb +2 -2
- data/lib/rubydex/mcp_server/tools/search_declarations_tool.rb +6 -6
- data/lib/rubydex/mcp_server.rb +24 -12
- data/lib/rubydex/rules/dynamic_ancestor.rb +29 -0
- data/lib/rubydex/rules/dynamic_constant_reference.rb +25 -0
- data/lib/rubydex/rules/dynamic_singleton_definition.rb +30 -0
- data/lib/rubydex/rules/invalid_constant_visibility.rb +28 -0
- data/lib/rubydex/rules/invalid_method_visibility.rb +28 -0
- data/lib/rubydex/rules/parse_error.rb +25 -0
- data/lib/rubydex/rules/parse_warning.rb +28 -0
- data/lib/rubydex/rules/top_level_mixin_self.rb +27 -0
- data/lib/rubydex/rules/undefined_constant_visibility_target.rb +27 -0
- data/lib/rubydex/rules/undefined_method_visibility_target.rb +27 -0
- data/lib/rubydex/skill.rb +1 -0
- data/lib/rubydex/version.rb +1 -1
- data/lib/rubydex.rb +2 -0
- data/lib/rubydex_linter/rules/rule_structure.rb +19 -4
- data/rbi/rubydex.rbi +32 -130
- data/rust/Cargo.lock +17 -11
- data/rust/Cargo.toml +3 -0
- data/rust/rubydex/Cargo.toml +6 -2
- data/rust/rubydex/src/bin/generate_ruby_rules.rs +94 -0
- data/rust/rubydex/src/compile_assertions.rs +2 -0
- data/rust/rubydex/src/config.rs +1 -1
- data/rust/rubydex/src/diagnostic.rs +142 -51
- data/rust/rubydex/src/indexing/rbs_indexer.rs +4 -4
- data/rust/rubydex/src/indexing/ruby_indexer.rs +26 -18
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +42 -2
- data/rust/rubydex/src/indexing.rs +6 -6
- data/rust/rubydex/src/listing.rs +12 -12
- data/rust/rubydex/src/model/built_in.rs +1 -1
- data/rust/rubydex/src/model/comment.rs +4 -4
- data/rust/rubydex/src/model/declaration.rs +25 -17
- data/rust/rubydex/src/model/document.rs +11 -11
- data/rust/rubydex/src/model/graph.rs +3 -3
- data/rust/rubydex/src/offset.rs +1 -13
- data/rust/rubydex/src/operation/ruby_builder.rs +29 -19
- data/rust/rubydex/src/query.rs +193 -2
- data/rust/rubydex/src/test_utils/graph_test.rs +2 -2
- data/rust/rubydex/src/test_utils/local_graph_test.rs +13 -6
- data/rust/rubydex-sys/Cargo.toml +2 -2
- data/rust/rubydex-sys/src/definition_api.rs +69 -2
- data/rust/rubydex-sys/src/diagnostic_api.rs +0 -35
- data/rust/rubydex-sys/src/graph_api.rs +193 -45
- data/rust/rubydex-sys/src/name_api.rs +219 -48
- metadata +199 -187
|
@@ -87,8 +87,8 @@ pub struct RubyOperationBuilder<'a> {
|
|
|
87
87
|
|
|
88
88
|
impl<'a> RubyOperationBuilder<'a> {
|
|
89
89
|
#[must_use]
|
|
90
|
-
pub fn new(uri:
|
|
91
|
-
let uri_id = UriId::from(
|
|
90
|
+
pub fn new(uri: Box<str>, source: &'a str) -> Self {
|
|
91
|
+
let uri_id = UriId::from(&*uri);
|
|
92
92
|
|
|
93
93
|
Self {
|
|
94
94
|
uri_id,
|
|
@@ -112,7 +112,7 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
112
112
|
self.add_diagnostic(
|
|
113
113
|
Rule::ParseError,
|
|
114
114
|
Offset::from_prism_location(&error.location()),
|
|
115
|
-
error.message().to_string(),
|
|
115
|
+
String::from_utf8_lossy(error.message()).to_string(),
|
|
116
116
|
);
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -120,7 +120,7 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
120
120
|
self.add_diagnostic(
|
|
121
121
|
Rule::ParseWarning,
|
|
122
122
|
Offset::from_prism_location(&warning.location()),
|
|
123
|
-
warning.message().to_string(),
|
|
123
|
+
String::from_utf8_lossy(warning.message()).to_string(),
|
|
124
124
|
);
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -489,6 +489,7 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
489
489
|
|
|
490
490
|
// -- Parameters --
|
|
491
491
|
|
|
492
|
+
#[allow(clippy::too_many_lines)]
|
|
492
493
|
fn collect_parameters(&mut self, node: &ruby_prism::DefNode) -> Vec<Parameter> {
|
|
493
494
|
let mut parameters: Vec<Parameter> = Vec::new();
|
|
494
495
|
|
|
@@ -588,7 +589,9 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
588
589
|
}
|
|
589
590
|
}
|
|
590
591
|
|
|
591
|
-
if let Some(block) = parameters_list.block()
|
|
592
|
+
if let Some(block) = parameters_list.block()
|
|
593
|
+
&& let Some(block) = block.as_block_parameter_node()
|
|
594
|
+
{
|
|
592
595
|
let location = block.name_loc().unwrap_or_else(|| block.location());
|
|
593
596
|
let str_id = self.intern_string(Self::location_to_string(&location));
|
|
594
597
|
parameters.push(Parameter::Block(ParameterStruct::new(
|
|
@@ -1036,8 +1039,8 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
1036
1039
|
match arg {
|
|
1037
1040
|
ruby_prism::Node::SymbolNode { .. } => {
|
|
1038
1041
|
let symbol = arg.as_symbol_node().unwrap();
|
|
1039
|
-
let
|
|
1040
|
-
Some((Self::location_to_string(&
|
|
1042
|
+
let content_loc = symbol.content_loc();
|
|
1043
|
+
Some((Self::location_to_string(&content_loc), content_loc))
|
|
1041
1044
|
}
|
|
1042
1045
|
ruby_prism::Node::StringNode { .. } => {
|
|
1043
1046
|
let string = arg.as_string_node().unwrap();
|
|
@@ -1380,7 +1383,7 @@ impl CommentGroup {
|
|
|
1380
1383
|
|
|
1381
1384
|
fn accepts(&self, next: &ruby_prism::Comment, source: &str) -> bool {
|
|
1382
1385
|
let current_end_offset = self.end_offset;
|
|
1383
|
-
let next_line_start_offset = next.location().
|
|
1386
|
+
let next_line_start_offset = next.location().start() as usize;
|
|
1384
1387
|
let between = &source.as_bytes()[current_end_offset..next_line_start_offset];
|
|
1385
1388
|
if !between.iter().all(|&b| b.is_ascii_whitespace()) {
|
|
1386
1389
|
return false;
|
|
@@ -1389,14 +1392,14 @@ impl CommentGroup {
|
|
|
1389
1392
|
}
|
|
1390
1393
|
|
|
1391
1394
|
fn add_comment(&mut self, comment: &ruby_prism::Comment) {
|
|
1392
|
-
self.end_offset = comment.location().
|
|
1395
|
+
self.end_offset = comment.location().end() as usize;
|
|
1393
1396
|
let text = String::from_utf8_lossy(comment.location().as_slice()).to_string();
|
|
1394
1397
|
if text.lines().any(|line| line.starts_with("# @deprecated")) {
|
|
1395
1398
|
self.deprecated = true;
|
|
1396
1399
|
}
|
|
1397
1400
|
self.comments.push(Comment::new(
|
|
1398
1401
|
Offset::from_prism_location(&comment.location()),
|
|
1399
|
-
text.trim().
|
|
1402
|
+
text.trim().into(),
|
|
1400
1403
|
));
|
|
1401
1404
|
}
|
|
1402
1405
|
|
|
@@ -1783,10 +1786,17 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
1783
1786
|
});
|
|
1784
1787
|
};
|
|
1785
1788
|
|
|
1786
|
-
let message_loc = node.
|
|
1787
|
-
|
|
1789
|
+
let message_loc = if node.name().as_slice().is_empty() {
|
|
1790
|
+
// Incomplete calls like `Foo.` have no message. We still index them using the call operator location, so
|
|
1791
|
+
// that the receiver's singleton class gets created, which completion depends on
|
|
1792
|
+
node.call_operator_loc()
|
|
1793
|
+
} else {
|
|
1794
|
+
node.message_loc()
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1797
|
+
let Some(message_loc) = message_loc else {
|
|
1788
1798
|
return;
|
|
1789
|
-
}
|
|
1799
|
+
};
|
|
1790
1800
|
|
|
1791
1801
|
let message = String::from_utf8_lossy(node.name().as_slice()).to_string();
|
|
1792
1802
|
|
|
@@ -2005,11 +2015,11 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
2005
2015
|
self.visit(&receiver);
|
|
2006
2016
|
}
|
|
2007
2017
|
|
|
2008
|
-
self.index_method_reference(message.clone(), &
|
|
2018
|
+
self.index_method_reference(message.clone(), &message_loc, method_receiver);
|
|
2009
2019
|
|
|
2010
2020
|
match message.as_str() {
|
|
2011
2021
|
">" | "<" | ">=" | "<=" => {
|
|
2012
|
-
self.index_method_reference("<=>".to_string(), &
|
|
2022
|
+
self.index_method_reference("<=>".to_string(), &message_loc, method_receiver);
|
|
2013
2023
|
}
|
|
2014
2024
|
_ => {}
|
|
2015
2025
|
}
|
|
@@ -2134,7 +2144,7 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
2134
2144
|
match expression {
|
|
2135
2145
|
ruby_prism::Node::SymbolNode { .. } => {
|
|
2136
2146
|
let symbol = expression.as_symbol_node().unwrap();
|
|
2137
|
-
let name = Self::location_to_string(&symbol.
|
|
2147
|
+
let name = Self::location_to_string(&symbol.content_loc());
|
|
2138
2148
|
self.index_method_reference(name, &node.location(), None);
|
|
2139
2149
|
}
|
|
2140
2150
|
_ => {
|
|
@@ -2146,13 +2156,13 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
2146
2156
|
|
|
2147
2157
|
fn visit_alias_method_node(&mut self, node: &ruby_prism::AliasMethodNode<'_>) {
|
|
2148
2158
|
let mut new_name = if let Some(symbol_node) = node.new_name().as_symbol_node() {
|
|
2149
|
-
Self::location_to_string(&symbol_node.
|
|
2159
|
+
Self::location_to_string(&symbol_node.content_loc())
|
|
2150
2160
|
} else {
|
|
2151
2161
|
Self::location_to_string(&node.new_name().location())
|
|
2152
2162
|
};
|
|
2153
2163
|
|
|
2154
2164
|
let mut old_name = if let Some(symbol_node) = node.old_name().as_symbol_node() {
|
|
2155
|
-
Self::location_to_string(&symbol_node.
|
|
2165
|
+
Self::location_to_string(&symbol_node.content_loc())
|
|
2156
2166
|
} else {
|
|
2157
2167
|
Self::location_to_string(&node.old_name().location())
|
|
2158
2168
|
};
|
|
@@ -2229,7 +2239,7 @@ mod tests {
|
|
|
2229
2239
|
|
|
2230
2240
|
fn build_operations(source: &str) -> OperationBuilderResult {
|
|
2231
2241
|
let source = crate::test_utils::normalize_indentation(source);
|
|
2232
|
-
let builder = RubyOperationBuilder::new("file:///test.rb".
|
|
2242
|
+
let builder = RubyOperationBuilder::new("file:///test.rb".into(), &source);
|
|
2233
2243
|
builder.build()
|
|
2234
2244
|
}
|
|
2235
2245
|
|
data/rust/rubydex/src/query.rs
CHANGED
|
@@ -5,12 +5,12 @@ use std::thread;
|
|
|
5
5
|
|
|
6
6
|
use url::Url;
|
|
7
7
|
|
|
8
|
-
use crate::model::built_in::OBJECT_ID;
|
|
8
|
+
use crate::model::built_in::{BUILT_IN_URI_ID, OBJECT_ID};
|
|
9
9
|
use crate::model::declaration::{Ancestor, Declaration, Namespace};
|
|
10
10
|
use crate::model::definitions::{Definition, Parameter};
|
|
11
11
|
use crate::model::graph::Graph;
|
|
12
12
|
use crate::model::identity_maps::IdentityHashSet;
|
|
13
|
-
use crate::model::ids::{DeclarationId, DefinitionId, NameId, StringId, UriId};
|
|
13
|
+
use crate::model::ids::{ConstantReferenceId, DeclarationId, DefinitionId, NameId, StringId, UriId};
|
|
14
14
|
use crate::model::keywords::{self, Keyword};
|
|
15
15
|
use crate::model::name::NameRef;
|
|
16
16
|
use crate::model::visibility::Visibility;
|
|
@@ -833,6 +833,64 @@ pub fn follow_method_alias(graph: &Graph, alias_id: DefinitionId) -> Result<Decl
|
|
|
833
833
|
}
|
|
834
834
|
}
|
|
835
835
|
|
|
836
|
+
/// Returns `true` when any definition of `declaration` was seeded by rubydex rather than read from indexed source.
|
|
837
|
+
///
|
|
838
|
+
/// Dead code candidates are reported at declaration granularity, so a seeded definition disqualifies the declaration
|
|
839
|
+
/// even when user code also reopens it.
|
|
840
|
+
fn is_built_in(graph: &Graph, declaration: &Declaration) -> bool {
|
|
841
|
+
declaration.definitions().iter().any(|definition_id| {
|
|
842
|
+
graph
|
|
843
|
+
.definitions()
|
|
844
|
+
.get(definition_id)
|
|
845
|
+
.is_some_and(|definition| definition.uri_id() == &*BUILT_IN_URI_ID)
|
|
846
|
+
})
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/// Returns a list of declarations that may be unused in the codebase, which means
|
|
850
|
+
/// that the analysis could not find any references to them. This misses
|
|
851
|
+
/// meta-programming or untyped code, which is why the term candidates is used.
|
|
852
|
+
///
|
|
853
|
+
/// Currently, only supports constants.
|
|
854
|
+
///
|
|
855
|
+
/// # Panics
|
|
856
|
+
///
|
|
857
|
+
/// Will panic if any of the threads panic
|
|
858
|
+
pub fn dead_code_candidates(graph: &Graph) -> Vec<DeclarationId> {
|
|
859
|
+
let num_threads = thread::available_parallelism().map_or(4, std::num::NonZero::get);
|
|
860
|
+
let declarations = graph.declarations();
|
|
861
|
+
|
|
862
|
+
let ids: Vec<DeclarationId> = declarations.keys().copied().collect();
|
|
863
|
+
let chunk_size = ids.len().div_ceil(num_threads);
|
|
864
|
+
|
|
865
|
+
if chunk_size == 0 {
|
|
866
|
+
return Vec::new();
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
thread::scope(|s| {
|
|
870
|
+
let handles: Vec<_> = ids
|
|
871
|
+
.chunks(chunk_size)
|
|
872
|
+
.map(|chunk| {
|
|
873
|
+
s.spawn(|| {
|
|
874
|
+
chunk
|
|
875
|
+
.iter()
|
|
876
|
+
.filter(|id| {
|
|
877
|
+
let decl = declarations.get(id).unwrap();
|
|
878
|
+
decl.constant_references()
|
|
879
|
+
.is_some_and(<[ConstantReferenceId]>::is_empty)
|
|
880
|
+
&& !matches!(decl, Declaration::Namespace(Namespace::SingletonClass(_)))
|
|
881
|
+
&& !decl.has_no_definitions()
|
|
882
|
+
&& !is_built_in(graph, decl)
|
|
883
|
+
})
|
|
884
|
+
.copied()
|
|
885
|
+
.collect::<Vec<_>>()
|
|
886
|
+
})
|
|
887
|
+
})
|
|
888
|
+
.collect();
|
|
889
|
+
|
|
890
|
+
handles.into_iter().flat_map(|h| h.join().unwrap()).collect()
|
|
891
|
+
})
|
|
892
|
+
}
|
|
893
|
+
|
|
836
894
|
#[cfg(test)]
|
|
837
895
|
mod tests {
|
|
838
896
|
use std::str::FromStr;
|
|
@@ -920,6 +978,21 @@ mod tests {
|
|
|
920
978
|
};
|
|
921
979
|
}
|
|
922
980
|
|
|
981
|
+
macro_rules! assert_dead_code_candidates {
|
|
982
|
+
($context:expr, [$($expected:expr),* $(,)?]) => {
|
|
983
|
+
let mut actual: Vec<String> = dead_code_candidates($context.graph())
|
|
984
|
+
.iter()
|
|
985
|
+
.map(|id| $context.graph().declarations().get(id).unwrap().name().to_string())
|
|
986
|
+
.collect();
|
|
987
|
+
actual.sort();
|
|
988
|
+
|
|
989
|
+
let mut expected: Vec<String> = vec![$(String::from($expected)),*];
|
|
990
|
+
expected.sort();
|
|
991
|
+
|
|
992
|
+
assert_eq!(expected, actual);
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
|
|
923
996
|
#[test]
|
|
924
997
|
fn fuzzy_search_returns_partial_matches() {
|
|
925
998
|
let mut context = GraphTest::new();
|
|
@@ -3982,4 +4055,122 @@ mod tests {
|
|
|
3982
4055
|
Err(FindMemberError::DeclarationNotFound),
|
|
3983
4056
|
);
|
|
3984
4057
|
}
|
|
4058
|
+
|
|
4059
|
+
#[test]
|
|
4060
|
+
fn dead_code_candidates_returns_unreferenced_constants() {
|
|
4061
|
+
let mut context = GraphTest::new();
|
|
4062
|
+
context.index_uri(
|
|
4063
|
+
"file:///foo.rb",
|
|
4064
|
+
r"
|
|
4065
|
+
class UnusedClass; end
|
|
4066
|
+
module UnusedModule; end
|
|
4067
|
+
UNUSED_CONSTANT = 1
|
|
4068
|
+
",
|
|
4069
|
+
);
|
|
4070
|
+
context.resolve();
|
|
4071
|
+
|
|
4072
|
+
assert_dead_code_candidates!(context, ["UnusedClass", "UnusedModule", "UNUSED_CONSTANT"]);
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4075
|
+
#[test]
|
|
4076
|
+
fn dead_code_candidates_excludes_referenced_constants() {
|
|
4077
|
+
let mut context = GraphTest::new();
|
|
4078
|
+
context.index_uri(
|
|
4079
|
+
"file:///foo.rb",
|
|
4080
|
+
r"
|
|
4081
|
+
class UsedClass; end
|
|
4082
|
+
USED_CONSTANT = 1
|
|
4083
|
+
",
|
|
4084
|
+
);
|
|
4085
|
+
context.index_uri(
|
|
4086
|
+
"file:///bar.rb",
|
|
4087
|
+
r"
|
|
4088
|
+
UsedClass
|
|
4089
|
+
USED_CONSTANT
|
|
4090
|
+
",
|
|
4091
|
+
);
|
|
4092
|
+
context.resolve();
|
|
4093
|
+
|
|
4094
|
+
assert_dead_code_candidates!(context, []);
|
|
4095
|
+
}
|
|
4096
|
+
|
|
4097
|
+
#[test]
|
|
4098
|
+
fn dead_code_candidates_excludes_methods_and_variables() {
|
|
4099
|
+
let mut context = GraphTest::new();
|
|
4100
|
+
context.index_uri(
|
|
4101
|
+
"file:///foo.rb",
|
|
4102
|
+
r"
|
|
4103
|
+
$global_var = 1
|
|
4104
|
+
|
|
4105
|
+
class Holder
|
|
4106
|
+
@@class_var = 1
|
|
4107
|
+
|
|
4108
|
+
def initialize
|
|
4109
|
+
@instance_var = 1
|
|
4110
|
+
end
|
|
4111
|
+
|
|
4112
|
+
def never_called; end
|
|
4113
|
+
end
|
|
4114
|
+
",
|
|
4115
|
+
);
|
|
4116
|
+
context.resolve();
|
|
4117
|
+
|
|
4118
|
+
assert_dead_code_candidates!(context, ["Holder"]);
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4121
|
+
#[test]
|
|
4122
|
+
fn dead_code_candidates_counts_references_rather_than_reachability() {
|
|
4123
|
+
let mut context = GraphTest::new();
|
|
4124
|
+
context.index_uri(
|
|
4125
|
+
"file:///foo.rb",
|
|
4126
|
+
r"
|
|
4127
|
+
class Target; end
|
|
4128
|
+
AliasName = Target
|
|
4129
|
+
",
|
|
4130
|
+
);
|
|
4131
|
+
context.resolve();
|
|
4132
|
+
|
|
4133
|
+
// `AliasName` is itself dead, but it still references `Target`, giving `Target` a non-zero count.
|
|
4134
|
+
// A single pass only peels the outermost layer of a dead subgraph.
|
|
4135
|
+
assert_dead_code_candidates!(context, ["AliasName"]);
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4138
|
+
#[test]
|
|
4139
|
+
fn dead_code_candidates_excludes_built_ins_and_definitionless_declarations() {
|
|
4140
|
+
let mut context = GraphTest::new();
|
|
4141
|
+
context.index_uri(
|
|
4142
|
+
"file:///foo.rb",
|
|
4143
|
+
r"
|
|
4144
|
+
class Unused
|
|
4145
|
+
def self.class_method; end
|
|
4146
|
+
end
|
|
4147
|
+
|
|
4148
|
+
class Class
|
|
4149
|
+
end
|
|
4150
|
+
",
|
|
4151
|
+
);
|
|
4152
|
+
context.resolve();
|
|
4153
|
+
|
|
4154
|
+
// A reopened built-in remains excluded regardless of definition order.
|
|
4155
|
+
assert_dead_code_candidates!(context, ["Unused"]);
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
#[test]
|
|
4159
|
+
fn dead_code_candidates_excludes_singleton_classes() {
|
|
4160
|
+
let mut context = GraphTest::new();
|
|
4161
|
+
context.index_uri(
|
|
4162
|
+
"file:///foo.rb",
|
|
4163
|
+
r"
|
|
4164
|
+
class Unused; end
|
|
4165
|
+
class Attached; end
|
|
4166
|
+
|
|
4167
|
+
class << Attached
|
|
4168
|
+
def never_called; end
|
|
4169
|
+
end
|
|
4170
|
+
",
|
|
4171
|
+
);
|
|
4172
|
+
context.resolve();
|
|
4173
|
+
|
|
4174
|
+
assert_dead_code_candidates!(context, ["Unused"]);
|
|
4175
|
+
}
|
|
3985
4176
|
}
|
|
@@ -44,14 +44,14 @@ impl GraphTest {
|
|
|
44
44
|
/// Indexes a Ruby source
|
|
45
45
|
pub fn index_uri(&mut self, uri: &str, source: &str) {
|
|
46
46
|
let source = normalize_indentation(source);
|
|
47
|
-
let local_graph = indexing::build_local_graph(uri.
|
|
47
|
+
let local_graph = indexing::build_local_graph(uri.into(), &source, &LanguageId::Ruby, self.backend);
|
|
48
48
|
self.graph.consume_document_changes(local_graph);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/// Indexes an RBS source
|
|
52
52
|
pub fn index_rbs_uri(&mut self, uri: &str, source: &str) {
|
|
53
53
|
let source = normalize_indentation(source);
|
|
54
|
-
indexing::index_source(&mut self.graph, uri, &source, &LanguageId::Rbs);
|
|
54
|
+
indexing::index_source(&mut self.graph, uri.into(), &source, &LanguageId::Rbs);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
pub fn delete_uri(&mut self, uri: &str) {
|
|
@@ -24,22 +24,29 @@ impl LocalGraphTest {
|
|
|
24
24
|
|
|
25
25
|
#[must_use]
|
|
26
26
|
pub fn new_with_backend(uri: &str, source: &str, backend: IndexerBackend) -> Self {
|
|
27
|
-
let uri = uri.to_string();
|
|
28
27
|
let source = normalize_indentation(source);
|
|
29
|
-
let graph = build_local_graph(uri.
|
|
30
|
-
|
|
28
|
+
let graph = build_local_graph(uri.into(), &source, &LanguageId::Ruby, backend);
|
|
29
|
+
|
|
30
|
+
Self {
|
|
31
|
+
uri: uri.to_string(),
|
|
32
|
+
source,
|
|
33
|
+
graph,
|
|
34
|
+
}
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
#[must_use]
|
|
34
38
|
pub fn new_rbs(uri: &str, source: &str) -> Self {
|
|
35
|
-
let uri = uri.to_string();
|
|
36
39
|
let source = normalize_indentation(source);
|
|
37
40
|
|
|
38
|
-
let mut indexer = RBSIndexer::new(uri.
|
|
41
|
+
let mut indexer = RBSIndexer::new(uri.into(), &source);
|
|
39
42
|
indexer.index();
|
|
40
43
|
let graph = indexer.local_graph();
|
|
41
44
|
|
|
42
|
-
Self {
|
|
45
|
+
Self {
|
|
46
|
+
uri: uri.to_string(),
|
|
47
|
+
source,
|
|
48
|
+
graph,
|
|
49
|
+
}
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
#[must_use]
|
data/rust/rubydex-sys/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "rubydex-sys"
|
|
3
|
-
version =
|
|
3
|
+
version.workspace = true
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.89.0"
|
|
6
6
|
license = "MIT"
|
|
@@ -15,7 +15,7 @@ categories = ["development-tools", "external-ffi-bindings"]
|
|
|
15
15
|
crate-type = ["cdylib", "staticlib"]
|
|
16
16
|
|
|
17
17
|
[dependencies]
|
|
18
|
-
rubydex = { version = "0.2
|
|
18
|
+
rubydex = { version = "=0.4.2", path = "../rubydex" }
|
|
19
19
|
libc = "0.2.174"
|
|
20
20
|
url = "2.5.4"
|
|
21
21
|
line-index = "0.1.2"
|
|
@@ -6,7 +6,9 @@ use crate::location_api::{Location, create_location_for_uri_and_offset};
|
|
|
6
6
|
use crate::reference_api::CConstantReference;
|
|
7
7
|
use libc::c_char;
|
|
8
8
|
use rubydex::model::definitions::{Definition, Mixin};
|
|
9
|
-
use rubydex::model::
|
|
9
|
+
use rubydex::model::graph::Graph;
|
|
10
|
+
use rubydex::model::ids::{DefinitionId, NameId};
|
|
11
|
+
use rubydex::model::name::ParentScope;
|
|
10
12
|
use rubydex::query::AliasResolutionError;
|
|
11
13
|
use std::ffi::CString;
|
|
12
14
|
use std::ptr;
|
|
@@ -110,6 +112,71 @@ pub unsafe extern "C" fn rdx_definition_name(pointer: GraphPointer, definition_i
|
|
|
110
112
|
})
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
fn raw_name_for_name(graph: &Graph, name_id: NameId) -> String {
|
|
116
|
+
let mut raw_name = String::new();
|
|
117
|
+
append_raw_name(graph, name_id, &mut raw_name);
|
|
118
|
+
raw_name
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn append_raw_name(graph: &Graph, name_id: NameId, raw_name: &mut String) {
|
|
122
|
+
let name = graph
|
|
123
|
+
.names()
|
|
124
|
+
.get(&name_id)
|
|
125
|
+
.expect("name should exist while building raw_name");
|
|
126
|
+
let simple_name = graph
|
|
127
|
+
.strings()
|
|
128
|
+
.get(name.str())
|
|
129
|
+
.expect("string should exist while building raw_name")
|
|
130
|
+
.as_str();
|
|
131
|
+
|
|
132
|
+
match name.parent_scope() {
|
|
133
|
+
ParentScope::None => {}
|
|
134
|
+
ParentScope::TopLevel => raw_name.push_str("::"),
|
|
135
|
+
ParentScope::Some(parent_id) | ParentScope::Attached(parent_id) => {
|
|
136
|
+
append_raw_name(graph, *parent_id, raw_name);
|
|
137
|
+
raw_name.push_str("::");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
raw_name.push_str(simple_name);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
fn raw_name_id(definition: &Definition) -> Option<&NameId> {
|
|
145
|
+
match definition {
|
|
146
|
+
Definition::Class(_) | Definition::Module(_) | Definition::Constant(_) | Definition::ConstantAlias(_) => {
|
|
147
|
+
definition.name_id()
|
|
148
|
+
}
|
|
149
|
+
_ => None,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Returns the UTF-8 raw name string for a class, module, constant, or constant-alias definition id.
|
|
154
|
+
///
|
|
155
|
+
/// The raw name is reconstructed from the definition's `Name`, preserving explicit parent scopes like `Foo::Bar` and
|
|
156
|
+
/// rooted paths like `::Bar`. Returns null if the definition cannot be found or does not support raw names. Caller must
|
|
157
|
+
/// free a non-null result with `free_c_string`.
|
|
158
|
+
///
|
|
159
|
+
/// # Panics
|
|
160
|
+
///
|
|
161
|
+
/// Panics if the graph contains an invalid name or string reference, or if the name contains an embedded null byte.
|
|
162
|
+
///
|
|
163
|
+
/// # Safety
|
|
164
|
+
///
|
|
165
|
+
/// Assumes pointer is valid.
|
|
166
|
+
///
|
|
167
|
+
#[unsafe(no_mangle)]
|
|
168
|
+
pub unsafe extern "C" fn rdx_definition_raw_name(pointer: GraphPointer, definition_id: u64) -> *const c_char {
|
|
169
|
+
with_graph(pointer, |graph| {
|
|
170
|
+
let def_id = DefinitionId::new(definition_id);
|
|
171
|
+
let Some(name_id) = graph.definitions().get(&def_id).and_then(raw_name_id) else {
|
|
172
|
+
return ptr::null();
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
let name = raw_name_for_name(graph, *name_id);
|
|
176
|
+
CString::new(name).unwrap().into_raw().cast_const()
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
113
180
|
/// Shared iterator over definition (id, kind) pairs
|
|
114
181
|
#[derive(Debug)]
|
|
115
182
|
pub struct DefinitionsIter {
|
|
@@ -181,7 +248,7 @@ pub unsafe extern "C" fn rdx_definition_comments(pointer: GraphPointer, definiti
|
|
|
181
248
|
.comments()
|
|
182
249
|
.iter()
|
|
183
250
|
.map(|c| CommentEntry {
|
|
184
|
-
string: CString::new(c.string()
|
|
251
|
+
string: CString::new(c.string()).unwrap().into_raw().cast_const(),
|
|
185
252
|
location: create_location_for_uri_and_offset(graph, document, c.offset()),
|
|
186
253
|
})
|
|
187
254
|
.collect::<Vec<CommentEntry>>()
|
|
@@ -32,7 +32,6 @@ impl From<Severity> for DiagnosticSeverity {
|
|
|
32
32
|
pub struct CRule {
|
|
33
33
|
pub name: *const c_char,
|
|
34
34
|
pub name_length: usize,
|
|
35
|
-
pub default_severity: DiagnosticSeverity,
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
impl From<Rule> for CRule {
|
|
@@ -42,44 +41,10 @@ impl From<Rule> for CRule {
|
|
|
42
41
|
Self {
|
|
43
42
|
name: name.as_ptr().cast::<c_char>(),
|
|
44
43
|
name_length: name.len(),
|
|
45
|
-
default_severity: DiagnosticSeverity::from(rule.default_severity()),
|
|
46
44
|
}
|
|
47
45
|
}
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
#[repr(C)]
|
|
51
|
-
pub struct CRuleArray {
|
|
52
|
-
pub items: *mut CRule,
|
|
53
|
-
pub len: usize,
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/// Returns every rule the graph can report. Caller must free it with `rdx_rules_free`.
|
|
57
|
-
#[unsafe(no_mangle)]
|
|
58
|
-
pub extern "C" fn rdx_rules() -> CRuleArray {
|
|
59
|
-
let items = Rule::all().iter().copied().map(CRule::from).collect::<Box<[CRule]>>();
|
|
60
|
-
|
|
61
|
-
CRuleArray {
|
|
62
|
-
len: items.len(),
|
|
63
|
-
items: Box::into_raw(items).cast::<CRule>(),
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/// Frees an array previously returned by `rdx_rules`.
|
|
68
|
-
///
|
|
69
|
-
/// # Safety
|
|
70
|
-
///
|
|
71
|
-
/// - `rules` must have been returned by `rdx_rules` and must not be used afterwards.
|
|
72
|
-
#[unsafe(no_mangle)]
|
|
73
|
-
pub unsafe extern "C" fn rdx_rules_free(rules: CRuleArray) {
|
|
74
|
-
if rules.items.is_null() {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
unsafe {
|
|
79
|
-
let _ = Box::from_raw(ptr::slice_from_raw_parts_mut(rules.items, rules.len));
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
48
|
/// C-compatible struct representing a diagnostic entry.
|
|
84
49
|
#[repr(C)]
|
|
85
50
|
pub struct DiagnosticEntry {
|