@@ -5,10 +5,10 @@ use crate::indexing::local_graph::LocalGraph;
55use crate :: model:: comment:: Comment ;
66use crate :: model:: definitions:: {
77 AttrAccessorDefinition , AttrReaderDefinition , AttrWriterDefinition , ClassDefinition , ClassVariableDefinition ,
8- ConstantAliasDefinition , ConstantDefinition , Definition , DefinitionFlags , ExtendDefinition ,
9- GlobalVariableAliasDefinition , GlobalVariableDefinition , IncludeDefinition , InstanceVariableDefinition ,
10- MethodAliasDefinition , MethodDefinition , Mixin , ModuleDefinition , Parameter , ParameterStruct , PrependDefinition ,
11- Receiver , Signatures , SingletonClassDefinition ,
8+ ConstantAliasDefinition , ConstantDefinition , ConstantVisibilityDefinition , Definition , DefinitionFlags ,
9+ ExtendDefinition , GlobalVariableAliasDefinition , GlobalVariableDefinition , IncludeDefinition ,
10+ InstanceVariableDefinition , MethodAliasDefinition , MethodDefinition , Mixin , ModuleDefinition , Parameter ,
11+ ParameterStruct , PrependDefinition , Receiver , Signatures , SingletonClassDefinition ,
1212} ;
1313use crate :: model:: document:: Document ;
1414use crate :: model:: ids:: { DefinitionId , NameId , StringId , UriId } ;
@@ -1164,6 +1164,94 @@ impl<'a> RubyIndexer<'a> {
11641164 . add_constant_reference ( ConstantReference :: new ( new_name_id, self . uri_id , offset) ) ;
11651165 Some ( new_name_id)
11661166 }
1167+
1168+ fn handle_constant_visibility ( & mut self , node : & ruby_prism:: CallNode , visibility : Visibility ) {
1169+ let receiver = node. receiver ( ) ;
1170+
1171+ let receiver_name_id = match receiver {
1172+ Some ( ruby_prism:: Node :: ConstantPathNode { .. } | ruby_prism:: Node :: ConstantReadNode { .. } ) => {
1173+ self . index_constant_reference ( & receiver. unwrap ( ) , true )
1174+ }
1175+ Some ( ruby_prism:: Node :: SelfNode { .. } ) | None => match self . nesting_stack . last ( ) {
1176+ Some ( Nesting :: Method ( _) ) => {
1177+ // Dynamic private constant (called from a method), we ignore it but don't report an error since it's valid Ruby
1178+ // if being called from a singleton method.
1179+
1180+ return ;
1181+ }
1182+ None => {
1183+ self . local_graph . add_diagnostic (
1184+ Rule :: InvalidPrivateConstant ,
1185+ Offset :: from_prism_location ( & node. location ( ) ) ,
1186+ "Private constant called at top level" . to_string ( ) ,
1187+ ) ;
1188+
1189+ return ;
1190+ }
1191+ _ => None ,
1192+ } ,
1193+ _ => {
1194+ self . local_graph . add_diagnostic (
1195+ Rule :: InvalidPrivateConstant ,
1196+ Offset :: from_prism_location ( & node. location ( ) ) ,
1197+ "Dynamic receiver for private constant" . to_string ( ) ,
1198+ ) ;
1199+
1200+ return ;
1201+ }
1202+ } ;
1203+
1204+ let Some ( arguments) = node. arguments ( ) else {
1205+ return ;
1206+ } ;
1207+
1208+ for argument in & arguments. arguments ( ) {
1209+ let ( name, location) = match argument {
1210+ ruby_prism:: Node :: SymbolNode { .. } => {
1211+ let symbol = argument. as_symbol_node ( ) . unwrap ( ) ;
1212+ if let Some ( value_loc) = symbol. value_loc ( ) {
1213+ ( Self :: location_to_string ( & value_loc) , value_loc)
1214+ } else {
1215+ continue ;
1216+ }
1217+ }
1218+ ruby_prism:: Node :: StringNode { .. } => {
1219+ let string = argument. as_string_node ( ) . unwrap ( ) ;
1220+ let name = String :: from_utf8_lossy ( string. unescaped ( ) ) . to_string ( ) ;
1221+ ( name, argument. location ( ) )
1222+ }
1223+ _ => {
1224+ self . local_graph . add_diagnostic (
1225+ Rule :: InvalidPrivateConstant ,
1226+ Offset :: from_prism_location ( & argument. location ( ) ) ,
1227+ "Private constant called with non-symbol argument" . to_string ( ) ,
1228+ ) ;
1229+
1230+ return ;
1231+ }
1232+ } ;
1233+
1234+ let str_id = self . local_graph . intern_string ( name) ;
1235+ let offset = Offset :: from_prism_location ( & location) ;
1236+ let definition = Definition :: ConstantVisibility ( Box :: new ( ConstantVisibilityDefinition :: new (
1237+ self . local_graph . add_name ( Name :: new (
1238+ str_id,
1239+ receiver_name_id. map_or ( ParentScope :: None , ParentScope :: Some ) ,
1240+ self . current_lexical_scope_name_id ( ) ,
1241+ ) ) ,
1242+ visibility,
1243+ self . uri_id ,
1244+ offset,
1245+ Vec :: new ( ) ,
1246+ DefinitionFlags :: empty ( ) ,
1247+ self . current_nesting_definition_id ( ) ,
1248+ ) ) ) ;
1249+
1250+ let definition_id = self . local_graph . add_definition ( definition) ;
1251+
1252+ self . add_member_to_current_owner ( definition_id) ;
1253+ }
1254+ }
11671255}
11681256
11691257struct CommentGroup {
@@ -1845,6 +1933,12 @@ impl Visit<'_> for RubyIndexer<'_> {
18451933
18461934 self . index_method_reference_for_call ( node) ;
18471935 }
1936+ "private_constant" => {
1937+ self . handle_constant_visibility ( node, Visibility :: Private ) ;
1938+ }
1939+ "public_constant" => {
1940+ self . handle_constant_visibility ( node, Visibility :: Public ) ;
1941+ }
18481942 _ => {
18491943 // For method calls that we don't explicitly handle each part, we continue visiting their parts as we
18501944 // may discover something inside
@@ -6469,6 +6563,101 @@ mod tests {
64696563 assert_promotable!( def) ;
64706564 } ) ;
64716565 }
6566+
6567+ #[ test]
6568+ fn index_private_constant_calls ( ) {
6569+ let context = index_source ( {
6570+ r#"
6571+ module Foo
6572+ BAR = 42
6573+ BAZ = 43
6574+ FOO = 44
6575+
6576+ private_constant :BAR, :BAZ
6577+ private_constant "FOO"
6578+
6579+ class Qux
6580+ BAR = 42
6581+ BAZ = 43
6582+
6583+ Foo.public_constant :BAR
6584+ Foo.public_constant "BAZ"
6585+ end
6586+
6587+ self.private_constant :Qux
6588+ end
6589+
6590+ Foo.public_constant :BAR
6591+ "#
6592+ } ) ;
6593+
6594+ assert_no_local_diagnostics ! ( & context) ;
6595+
6596+ assert_definition_at ! ( & context, "6:21-6:24" , ConstantVisibility , |def| {
6597+ assert_def_name_eq!( & context, def, "BAR" ) ;
6598+ assert_eq!( def. visibility( ) , & Visibility :: Private ) ;
6599+ } ) ;
6600+ assert_definition_at ! ( & context, "6:27-6:30" , ConstantVisibility , |def| {
6601+ assert_def_name_eq!( & context, def, "BAZ" ) ;
6602+ assert_eq!( def. visibility( ) , & Visibility :: Private ) ;
6603+ } ) ;
6604+ assert_definition_at ! ( & context, "7:20-7:25" , ConstantVisibility , |def| {
6605+ assert_def_name_eq!( & context, def, "FOO" ) ;
6606+ assert_eq!( def. visibility( ) , & Visibility :: Private ) ;
6607+ } ) ;
6608+ assert_definition_at ! ( & context, "13:26-13:29" , ConstantVisibility , |def| {
6609+ assert_def_name_eq!( & context, def, "Foo::BAR" ) ;
6610+ assert_eq!( def. visibility( ) , & Visibility :: Public ) ;
6611+ } ) ;
6612+ assert_definition_at ! ( & context, "14:25-14:30" , ConstantVisibility , |def| {
6613+ assert_def_name_eq!( & context, def, "Foo::BAZ" ) ;
6614+ assert_eq!( def. visibility( ) , & Visibility :: Public ) ;
6615+ } ) ;
6616+ assert_definition_at ! ( & context, "17:26-17:29" , ConstantVisibility , |def| {
6617+ assert_def_name_eq!( & context, def, "Qux" ) ;
6618+ assert_eq!( def. visibility( ) , & Visibility :: Private ) ;
6619+ } ) ;
6620+ assert_definition_at ! ( & context, "20:22-20:25" , ConstantVisibility , |def| {
6621+ assert_def_name_eq!( & context, def, "Foo::BAR" ) ;
6622+ assert_eq!( def. visibility( ) , & Visibility :: Public ) ;
6623+ } ) ;
6624+ }
6625+
6626+ #[ test]
6627+ fn index_private_constant_calls_diagnostics ( ) {
6628+ let context = index_source ( {
6629+ "
6630+ private_constant :NOT_INDEXED
6631+ self.private_constant :NOT_INDEXED
6632+ foo.private_constant :NOT_INDEXED # not indexed, dynamic receiver
6633+
6634+ module Foo
6635+ private_constant NOT_INDEXED, not_indexed # not indexed, not a symbol
6636+ private_constant # not indexed, no arguments
6637+
6638+ def self.qux
6639+ private_constant :Bar # not indexed, dynamic
6640+ end
6641+
6642+ def foo
6643+ private_constant :Bar # not indexed, dynamic
6644+ end
6645+ end
6646+ "
6647+ } ) ;
6648+
6649+ assert_local_diagnostics_eq ! (
6650+ & context,
6651+ vec![
6652+ "invalid-private-constant: Private constant called at top level (1:1-1:30)" ,
6653+ "invalid-private-constant: Private constant called at top level (2:1-2:35)" ,
6654+ "invalid-private-constant: Dynamic receiver for private constant (3:1-3:34)" ,
6655+ "invalid-private-constant: Private constant called with non-symbol argument (6:20-6:31)" ,
6656+ ]
6657+ ) ;
6658+
6659+ assert_eq ! ( context. graph( ) . definitions( ) . len( ) , 3 ) ; // Foo, Foo::Qux, Foo#foo
6660+ }
64726661}
64736662
64746663#[ cfg( test) ]
0 commit comments