Skip to content

Commit d041f67

Browse files
committed
Support array and inline-def arguments to private_class_method
1 parent 299b305 commit d041f67

4 files changed

Lines changed: 268 additions & 28 deletions

File tree

rust/rubydex/src/indexing/ruby_indexer.rs

Lines changed: 108 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,6 +1277,7 @@ impl<'a> RubyIndexer<'a> {
12771277
}
12781278
}
12791279

1280+
#[allow(clippy::too_many_lines)]
12801281
fn handle_singleton_method_visibility(
12811282
&mut self,
12821283
node: &ruby_prism::CallNode,
@@ -1316,46 +1317,125 @@ impl<'a> RubyIndexer<'a> {
13161317
};
13171318

13181319
for argument in &arguments.arguments() {
1319-
let (name, location) = match argument {
1320-
ruby_prism::Node::SymbolNode { .. } => {
1321-
let symbol = argument.as_symbol_node().unwrap();
1322-
if let Some(value_loc) = symbol.value_loc() {
1323-
(Self::location_to_string(&value_loc), value_loc)
1324-
} else {
1320+
match argument {
1321+
ruby_prism::Node::SymbolNode { .. } | ruby_prism::Node::StringNode { .. } => {
1322+
let Some((name, location)) = Self::extract_visibility_literal(&argument) else {
13251323
continue;
1324+
};
1325+
let str_id = self.local_graph.intern_string(format!("{name}()"));
1326+
let offset = Offset::from_prism_location(&location);
1327+
let definition =
1328+
Definition::SingletonMethodVisibility(Box::new(SingletonMethodVisibilityDefinition::new(
1329+
receiver_name_id,
1330+
str_id,
1331+
visibility,
1332+
self.uri_id,
1333+
offset,
1334+
Box::default(),
1335+
DefinitionFlags::empty(),
1336+
self.current_nesting_definition_id(),
1337+
)));
1338+
let definition_id = self.local_graph.add_definition(definition);
1339+
self.add_member_to_current_owner(definition_id);
1340+
}
1341+
ruby_prism::Node::ArrayNode { .. } => {
1342+
let array = argument.as_array_node().unwrap();
1343+
for element in &array.elements() {
1344+
match element {
1345+
ruby_prism::Node::SymbolNode { .. } | ruby_prism::Node::StringNode { .. } => {
1346+
let Some((name, location)) = Self::extract_visibility_literal(&element) else {
1347+
continue;
1348+
};
1349+
let str_id = self.local_graph.intern_string(format!("{name}()"));
1350+
let offset = Offset::from_prism_location(&location);
1351+
let definition = Definition::SingletonMethodVisibility(Box::new(
1352+
SingletonMethodVisibilityDefinition::new(
1353+
receiver_name_id,
1354+
str_id,
1355+
visibility,
1356+
self.uri_id,
1357+
offset,
1358+
Box::default(),
1359+
DefinitionFlags::empty(),
1360+
self.current_nesting_definition_id(),
1361+
),
1362+
));
1363+
let definition_id = self.local_graph.add_definition(definition);
1364+
self.add_member_to_current_owner(definition_id);
1365+
}
1366+
_ => {
1367+
self.local_graph.add_diagnostic(
1368+
Rule::InvalidSingletonMethodVisibility,
1369+
Offset::from_prism_location(&element.location()),
1370+
format!("`{call_name}` array element must be a Symbol or String"),
1371+
);
1372+
self.visit(&element);
1373+
}
1374+
}
13261375
}
13271376
}
1328-
ruby_prism::Node::StringNode { .. } => {
1329-
let string = argument.as_string_node().unwrap();
1330-
let name = String::from_utf8_lossy(string.unescaped()).to_string();
1331-
(name, argument.location())
1377+
ruby_prism::Node::DefNode { .. } => {
1378+
let def_node = argument.as_def_node().unwrap();
1379+
if def_node.receiver().is_none() {
1380+
self.local_graph.add_diagnostic(
1381+
Rule::InvalidSingletonMethodVisibility,
1382+
Offset::from_prism_location(&argument.location()),
1383+
format!("`{call_name}` requires a singleton method definition"),
1384+
);
1385+
} else {
1386+
let name_loc = def_node.name_loc();
1387+
let name = Self::location_to_string(&name_loc);
1388+
let str_id = self.local_graph.intern_string(format!("{name}()"));
1389+
let offset = Offset::from_prism_location(&name_loc);
1390+
let definition =
1391+
Definition::SingletonMethodVisibility(Box::new(SingletonMethodVisibilityDefinition::new(
1392+
receiver_name_id,
1393+
str_id,
1394+
visibility,
1395+
self.uri_id,
1396+
offset,
1397+
Box::default(),
1398+
DefinitionFlags::empty(),
1399+
self.current_nesting_definition_id(),
1400+
)));
1401+
let definition_id = self.local_graph.add_definition(definition);
1402+
self.add_member_to_current_owner(definition_id);
1403+
}
1404+
self.visit(&argument);
1405+
}
1406+
arg if Self::is_attr_call(&arg) => {
1407+
self.local_graph.add_diagnostic(
1408+
Rule::InvalidSingletonMethodVisibility,
1409+
Offset::from_prism_location(&arg.location()),
1410+
format!("`{call_name}` does not accept `attr_*` arguments"),
1411+
);
1412+
self.visit(&arg);
13321413
}
13331414
_ => {
13341415
self.local_graph.add_diagnostic(
13351416
Rule::InvalidSingletonMethodVisibility,
13361417
Offset::from_prism_location(&argument.location()),
13371418
format!("`{call_name}` called with a non-literal argument"),
13381419
);
1339-
return;
1420+
self.visit(&argument);
13401421
}
1341-
};
1342-
1343-
let str_id = self.local_graph.intern_string(format!("{name}()"));
1344-
let offset = Offset::from_prism_location(&location);
1345-
let definition = Definition::SingletonMethodVisibility(Box::new(SingletonMethodVisibilityDefinition::new(
1346-
receiver_name_id,
1347-
str_id,
1348-
visibility,
1349-
self.uri_id,
1350-
offset,
1351-
Box::default(),
1352-
DefinitionFlags::empty(),
1353-
self.current_nesting_definition_id(),
1354-
)));
1355-
1356-
let definition_id = self.local_graph.add_definition(definition);
1422+
}
1423+
}
1424+
}
13571425

1358-
self.add_member_to_current_owner(definition_id);
1426+
fn extract_visibility_literal<'b>(arg: &ruby_prism::Node<'b>) -> Option<(String, ruby_prism::Location<'b>)> {
1427+
match arg {
1428+
ruby_prism::Node::SymbolNode { .. } => {
1429+
let symbol = arg.as_symbol_node().unwrap();
1430+
let value_loc = symbol.value_loc()?;
1431+
Some((Self::location_to_string(&value_loc), value_loc))
1432+
}
1433+
ruby_prism::Node::StringNode { .. } => {
1434+
let string = arg.as_string_node().unwrap();
1435+
let name = String::from_utf8_lossy(string.unescaped()).to_string();
1436+
Some((name, arg.location()))
1437+
}
1438+
_ => None,
13591439
}
13601440
}
13611441

rust/rubydex/src/indexing/ruby_indexer_tests.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3807,6 +3807,9 @@ mod visibility_tests {
38073807
38083808
module Foo
38093809
private_class_method NOT_INDEXED
3810+
attr_reader :a_attr_target
3811+
private_class_method attr_reader(:bad)
3812+
private_class_method def inline; end
38103813
38113814
def self.qux
38123815
private_class_method :Bar
@@ -3822,9 +3825,90 @@ mod visibility_tests {
38223825
"invalid-singleton-method-visibility: `private_class_method` called at top level (2:1-2:39)",
38233826
"invalid-singleton-method-visibility: Dynamic receiver for `private_class_method` (3:1-3:38)",
38243827
"invalid-singleton-method-visibility: `private_class_method` called with a non-literal argument (6:24-6:35)",
3828+
"invalid-singleton-method-visibility: `private_class_method` does not accept `attr_*` arguments (8:24-8:41)",
3829+
"invalid-singleton-method-visibility: `private_class_method` requires a singleton method definition (9:24-9:39)",
38253830
]
38263831
);
38273832
}
3833+
3834+
#[test]
3835+
fn index_private_class_method_inline_def() {
3836+
let context = index_source(
3837+
r"
3838+
class Foo
3839+
private_class_method def self.inline; end
3840+
end
3841+
",
3842+
);
3843+
3844+
assert_no_local_diagnostics!(&context);
3845+
3846+
assert_definition_at!(&context, "2:33-2:39", SingletonMethodVisibility, |def| {
3847+
assert_string_eq!(&context, def.target(), "inline()");
3848+
assert!(def.receiver().is_none());
3849+
assert_eq!(def.visibility(), &Visibility::Private);
3850+
});
3851+
}
3852+
3853+
#[test]
3854+
fn index_private_class_method_array_form() {
3855+
let context = index_source(
3856+
r#"
3857+
class Foo
3858+
def self.flat; end
3859+
def self.flat2; end
3860+
def self.mixed; end
3861+
3862+
private_class_method [:flat, :flat2]
3863+
public_class_method [:mixed, "flat"]
3864+
end
3865+
"#,
3866+
);
3867+
3868+
assert_no_local_diagnostics!(&context);
3869+
3870+
assert_definition_at!(&context, "6:26-6:30", SingletonMethodVisibility, |def| {
3871+
assert_string_eq!(&context, def.target(), "flat()");
3872+
assert_eq!(def.visibility(), &Visibility::Private);
3873+
});
3874+
assert_definition_at!(&context, "6:33-6:38", SingletonMethodVisibility, |def| {
3875+
assert_string_eq!(&context, def.target(), "flat2()");
3876+
assert_eq!(def.visibility(), &Visibility::Private);
3877+
});
3878+
assert_definition_at!(&context, "7:25-7:30", SingletonMethodVisibility, |def| {
3879+
assert_string_eq!(&context, def.target(), "mixed()");
3880+
assert_eq!(def.visibility(), &Visibility::Public);
3881+
});
3882+
assert_definition_at!(&context, "7:32-7:38", SingletonMethodVisibility, |def| {
3883+
assert_string_eq!(&context, def.target(), "flat()");
3884+
assert_eq!(def.visibility(), &Visibility::Public);
3885+
});
3886+
}
3887+
3888+
#[test]
3889+
fn index_private_class_method_array_form_non_literal_element_diagnostic() {
3890+
let context = index_source(
3891+
r"
3892+
class Foo
3893+
def self.flat; end
3894+
3895+
private_class_method [:flat, SOME_CONST]
3896+
end
3897+
",
3898+
);
3899+
3900+
assert_local_diagnostics_eq!(
3901+
&context,
3902+
vec![
3903+
"invalid-singleton-method-visibility: `private_class_method` array element must be a Symbol or String (4:32-4:42)"
3904+
]
3905+
);
3906+
3907+
assert_definition_at!(&context, "4:26-4:30", SingletonMethodVisibility, |def| {
3908+
assert_string_eq!(&context, def.target(), "flat()");
3909+
assert_eq!(def.visibility(), &Visibility::Private);
3910+
});
3911+
}
38283912
}
38293913

38303914
mod attr_accessor_tests {

rust/rubydex/src/resolution_tests.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5457,4 +5457,45 @@ mod visibility_resolution_tests {
54575457

54585458
assert_no_diagnostics!(&context);
54595459
}
5460+
5461+
#[test]
5462+
fn retroactive_singleton_method_visibility_inline_def() {
5463+
let mut context = GraphTest::new();
5464+
context.index_uri(
5465+
"file:///foo.rb",
5466+
r"
5467+
class Foo
5468+
private_class_method def self.bar; end
5469+
end
5470+
",
5471+
);
5472+
context.resolve();
5473+
5474+
assert_no_diagnostics!(&context);
5475+
assert_visibility_eq!(context, "Foo::<Foo>#bar()", Visibility::Private);
5476+
}
5477+
5478+
#[test]
5479+
fn retroactive_singleton_method_visibility_array_form() {
5480+
let mut context = GraphTest::new();
5481+
context.index_uri(
5482+
"file:///foo.rb",
5483+
r#"
5484+
class Foo
5485+
def self.a; end
5486+
def self.b; end
5487+
def self.c; end
5488+
5489+
private_class_method [:a, "b"]
5490+
public_class_method [:c]
5491+
end
5492+
"#,
5493+
);
5494+
context.resolve();
5495+
5496+
assert_no_diagnostics!(&context);
5497+
assert_visibility_eq!(context, "Foo::<Foo>#a()", Visibility::Private);
5498+
assert_visibility_eq!(context, "Foo::<Foo>#b()", Visibility::Private);
5499+
assert_visibility_eq!(context, "Foo::<Foo>#c()", Visibility::Public);
5500+
}
54605501
}

test/declaration_test.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,41 @@ def self.bar; end
841841
end
842842
end
843843

844+
def test_class_method_visibility_inline_def
845+
with_context do |context|
846+
context.write!("file1.rb", <<~RUBY)
847+
class Foo
848+
private_class_method def self.bar; end
849+
end
850+
RUBY
851+
852+
graph = Rubydex::Graph.new
853+
graph.index_all(context.glob("**/*.rb"))
854+
graph.resolve
855+
856+
assert_equal(:private, graph["Foo::<Foo>#bar()"].visibility)
857+
end
858+
end
859+
860+
def test_class_method_visibility_array_form
861+
with_context do |context|
862+
context.write!("file1.rb", <<~RUBY)
863+
class Foo
864+
def self.a; end
865+
def self.b; end
866+
private_class_method [:a, "b"]
867+
end
868+
RUBY
869+
870+
graph = Rubydex::Graph.new
871+
graph.index_all(context.glob("**/*.rb"))
872+
graph.resolve
873+
874+
assert_equal(:private, graph["Foo::<Foo>#a()"].visibility)
875+
assert_equal(:private, graph["Foo::<Foo>#b()"].visibility)
876+
end
877+
end
878+
844879
def test_constant_alias_visibility
845880
with_context do |context|
846881
context.write!("file1.rb", <<~RUBY)

0 commit comments

Comments
 (0)