Skip to content

Commit 63f366a

Browse files
authored
Merge pull request #931 from Shopify/ar-constant-visibility-diagnostics
Distinguish public_constant from private_constant in diagnostics
2 parents cd0a169 + 7d3020c commit 63f366a

4 files changed

Lines changed: 72 additions & 33 deletions

File tree

rust/rubydex/src/diagnostic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ rules! {
103103
DynamicSingletonDefinition;
104104
DynamicAncestor;
105105
TopLevelMixinSelf;
106-
InvalidPrivateConstant;
106+
InvalidConstantVisibility;
107107
InvalidMethodVisibility;
108108

109109
// Resolution

rust/rubydex/src/indexing/ruby_indexer.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,10 +1180,11 @@ impl<'a> RubyIndexer<'a> {
11801180

11811181
fn handle_constant_visibility(&mut self, node: &ruby_prism::CallNode, visibility: Visibility) {
11821182
let receiver = node.receiver();
1183+
let call_name = String::from_utf8_lossy(node.name().as_slice());
11831184

1184-
let receiver_name_id = match receiver {
1185+
let receiver_name_id = match &receiver {
11851186
Some(ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. }) => {
1186-
self.index_constant_reference(&receiver.unwrap(), true)
1187+
self.index_constant_reference(receiver.as_ref().unwrap(), true)
11871188
}
11881189
Some(ruby_prism::Node::SelfNode { .. }) | None => match self.nesting_stack.last() {
11891190
Some(Nesting::Method(_)) => {
@@ -1192,20 +1193,20 @@ impl<'a> RubyIndexer<'a> {
11921193
}
11931194
None => {
11941195
self.local_graph.add_diagnostic(
1195-
Rule::InvalidPrivateConstant,
1196+
Rule::InvalidConstantVisibility,
11961197
Offset::from_prism_location(&node.location()),
1197-
"Private constant called at top level".to_string(),
1198+
format!("`{call_name}` called at top level"),
11981199
);
11991200
self.visit_call_node_parts(node);
12001201
return;
12011202
}
12021203
_ => None,
12031204
},
1204-
_ => {
1205+
Some(other) => {
12051206
self.local_graph.add_diagnostic(
1206-
Rule::InvalidPrivateConstant,
1207-
Offset::from_prism_location(&node.location()),
1208-
"Dynamic receiver for private constant".to_string(),
1207+
Rule::InvalidConstantVisibility,
1208+
Offset::from_prism_location(&other.location()),
1209+
format!("Dynamic receiver for `{call_name}`"),
12091210
);
12101211
self.visit_call_node_parts(node);
12111212
return;
@@ -1219,9 +1220,9 @@ impl<'a> RubyIndexer<'a> {
12191220
for argument in &arguments.arguments() {
12201221
let Some((name, location)) = Self::extract_literal_name(&argument) else {
12211222
self.local_graph.add_diagnostic(
1222-
Rule::InvalidPrivateConstant,
1223+
Rule::InvalidConstantVisibility,
12231224
Offset::from_prism_location(&argument.location()),
1224-
"Private constant called with non-symbol argument".to_string(),
1225+
format!("`{call_name}` called with a non-literal argument"),
12251226
);
12261227
self.visit(&argument);
12271228
continue;

rust/rubydex/src/indexing/ruby_indexer_tests.rs

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1750,20 +1750,20 @@ mod visibility_tests {
17501750
fn index_private_constant_calls_diagnostics() {
17511751
let context = index_source({
17521752
"
1753-
private_constant :NOT_INDEXED
1754-
self.private_constant :NOT_INDEXED
1755-
foo.private_constant :NOT_INDEXED # not indexed, dynamic receiver
1753+
private_constant :BAR # not indexed, called at top level
1754+
self.private_constant :BAR # not indexed, self receiver at top level
1755+
foo.private_constant :BAR # not indexed, dynamic receiver
17561756
17571757
module Foo
1758-
private_constant NOT_INDEXED, not_indexed # not indexed, not a symbol
1758+
private_constant SomeConst, some_method # not indexed, non-literal arguments
17591759
private_constant # not indexed, no arguments
17601760
17611761
def self.qux
1762-
private_constant :Bar # not indexed, dynamic
1762+
private_constant :BAR # not indexed, inside a method
17631763
end
17641764
17651765
def foo
1766-
private_constant :Bar # not indexed, dynamic
1766+
private_constant :BAR # not indexed, inside a method
17671767
end
17681768
end
17691769
"
@@ -1772,15 +1772,52 @@ mod visibility_tests {
17721772
assert_local_diagnostics_eq!(
17731773
&context,
17741774
vec![
1775-
"invalid-private-constant: Private constant called at top level (1:1-1:30)",
1776-
"invalid-private-constant: Private constant called at top level (2:1-2:35)",
1777-
"invalid-private-constant: Dynamic receiver for private constant (3:1-3:34)",
1778-
"invalid-private-constant: Private constant called with non-symbol argument (6:20-6:31)",
1779-
"invalid-private-constant: Private constant called with non-symbol argument (6:33-6:44)",
1775+
"invalid-constant-visibility: `private_constant` called at top level (1:1-1:22)",
1776+
"invalid-constant-visibility: `private_constant` called at top level (2:1-2:27)",
1777+
"invalid-constant-visibility: Dynamic receiver for `private_constant` (3:1-3:4)",
1778+
"invalid-constant-visibility: `private_constant` called with a non-literal argument (6:20-6:29)",
1779+
"invalid-constant-visibility: `private_constant` called with a non-literal argument (6:31-6:42)",
17801780
]
17811781
);
17821782

1783-
assert_eq!(context.graph().definitions().len(), 3); // Foo, Foo::Qux, Foo#foo
1783+
assert_eq!(context.graph().definitions().len(), 3); // Foo, Foo.qux, Foo#foo
1784+
}
1785+
1786+
#[test]
1787+
fn index_public_constant_calls_diagnostics() {
1788+
let context = index_source({
1789+
"
1790+
public_constant :BAR # not indexed, called at top level
1791+
self.public_constant :BAR # not indexed, self receiver at top level
1792+
foo.public_constant :BAR # not indexed, dynamic receiver
1793+
1794+
module Foo
1795+
public_constant SomeConst, some_method # not indexed, non-literal arguments
1796+
public_constant # not indexed, no arguments
1797+
1798+
def self.qux
1799+
public_constant :BAR # not indexed, inside a method
1800+
end
1801+
1802+
def foo
1803+
public_constant :BAR # not indexed, inside a method
1804+
end
1805+
end
1806+
"
1807+
});
1808+
1809+
assert_local_diagnostics_eq!(
1810+
&context,
1811+
vec![
1812+
"invalid-constant-visibility: `public_constant` called at top level (1:1-1:21)",
1813+
"invalid-constant-visibility: `public_constant` called at top level (2:1-2:26)",
1814+
"invalid-constant-visibility: Dynamic receiver for `public_constant` (3:1-3:4)",
1815+
"invalid-constant-visibility: `public_constant` called with a non-literal argument (6:19-6:28)",
1816+
"invalid-constant-visibility: `public_constant` called with a non-literal argument (6:30-6:41)",
1817+
]
1818+
);
1819+
1820+
assert_eq!(context.graph().definitions().len(), 3); // Foo, Foo.qux, Foo#foo
17841821
}
17851822

17861823
#[test]

rust/rubydex/src/operation/ruby_builder.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -887,28 +887,29 @@ impl<'a> RubyOperationBuilder<'a> {
887887

888888
fn handle_constant_visibility(&mut self, node: &ruby_prism::CallNode, visibility: Visibility) {
889889
let receiver = node.receiver();
890+
let call_name = String::from_utf8_lossy(node.name().as_slice());
890891

891-
let receiver_name_id = match receiver {
892+
let receiver_name_id = match &receiver {
892893
Some(ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. }) => {
893-
self.index_constant_reference(&receiver.unwrap(), true)
894+
self.index_constant_reference(receiver.as_ref().unwrap(), true)
894895
}
895896
Some(ruby_prism::Node::SelfNode { .. }) | None => match self.nesting_stack.last() {
896897
Some(Nesting::Method { .. }) => return,
897898
None => {
898899
self.add_diagnostic(
899-
Rule::InvalidPrivateConstant,
900+
Rule::InvalidConstantVisibility,
900901
Offset::from_prism_location(&node.location()),
901-
"Private constant called at top level".to_string(),
902+
format!("`{call_name}` called at top level"),
902903
);
903904
return;
904905
}
905906
_ => None,
906907
},
907-
_ => {
908+
Some(other) => {
908909
self.add_diagnostic(
909-
Rule::InvalidPrivateConstant,
910-
Offset::from_prism_location(&node.location()),
911-
"Dynamic receiver for private constant".to_string(),
910+
Rule::InvalidConstantVisibility,
911+
Offset::from_prism_location(&other.location()),
912+
format!("Dynamic receiver for `{call_name}`"),
912913
);
913914
return;
914915
}
@@ -921,9 +922,9 @@ impl<'a> RubyOperationBuilder<'a> {
921922
for argument in &arguments.arguments() {
922923
let Some((name, location)) = Self::extract_literal_name(&argument) else {
923924
self.add_diagnostic(
924-
Rule::InvalidPrivateConstant,
925+
Rule::InvalidConstantVisibility,
925926
Offset::from_prism_location(&argument.location()),
926-
"Private constant called with non-symbol argument".to_string(),
927+
format!("`{call_name}` called with a non-literal argument"),
927928
);
928929
continue;
929930
};

0 commit comments

Comments
 (0)