rubydex 0.2.9 → 0.4.0

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +110 -6
  3. data/THIRD_PARTY_LICENSES.html +271 -2
  4. data/exe/rdx +2 -73
  5. data/ext/rubydex/config.c +140 -0
  6. data/ext/rubydex/config.h +16 -0
  7. data/ext/rubydex/declaration.c +1 -1
  8. data/ext/rubydex/definition.c +32 -4
  9. data/ext/rubydex/diagnostic.c +75 -1
  10. data/ext/rubydex/diagnostic.h +2 -0
  11. data/ext/rubydex/graph.c +27 -48
  12. data/ext/rubydex/graph.h +6 -0
  13. data/ext/rubydex/query.c +487 -0
  14. data/ext/rubydex/query.h +8 -0
  15. data/ext/rubydex/reference.c +60 -0
  16. data/ext/rubydex/rubydex.c +4 -0
  17. data/ext/rubydex/utils.c +23 -4
  18. data/ext/rubydex/utils.h +5 -0
  19. data/lib/ruby_lsp/rubydex/addon.rb +211 -0
  20. data/lib/rubydex/cli/command/console.rb +55 -0
  21. data/lib/rubydex/cli/command/lint/explain.rb +74 -0
  22. data/lib/rubydex/cli/command/lint.rb +202 -0
  23. data/lib/rubydex/cli/command/mcp.rb +30 -0
  24. data/lib/rubydex/cli/command/query.rb +70 -0
  25. data/lib/rubydex/cli/command/skill.rb +69 -0
  26. data/lib/rubydex/cli/command.rb +168 -0
  27. data/lib/rubydex/cli.rb +93 -0
  28. data/lib/rubydex/config.rb +59 -0
  29. data/lib/rubydex/diagnostic.rb +12 -3
  30. data/lib/rubydex/errors.rb +42 -1
  31. data/lib/rubydex/graph.rb +10 -3
  32. data/lib/rubydex/linter/custom_rule.rb +97 -0
  33. data/lib/rubydex/linter/helpers/path_helpers.rb +78 -0
  34. data/lib/rubydex/linter/helpers/source_access_helpers.rb +31 -0
  35. data/lib/rubydex/linter/rule_loader.rb +36 -0
  36. data/lib/rubydex/linter/rule_test_case.rb +343 -0
  37. data/lib/rubydex/linter/runner.rb +56 -0
  38. data/lib/rubydex/linter.rb +19 -0
  39. data/lib/rubydex/location.rb +3 -0
  40. data/lib/rubydex/mcp_server.rb +1 -2
  41. data/lib/rubydex/related_information.rb +17 -0
  42. data/lib/rubydex/rule.rb +33 -0
  43. data/lib/rubydex/severity.rb +70 -0
  44. data/lib/rubydex/skill.rb +88 -0
  45. data/lib/rubydex/skill_registry.rb +62 -0
  46. data/lib/rubydex/version.rb +1 -1
  47. data/lib/rubydex.rb +6 -0
  48. data/lib/rubydex_linter/rules/rule_structure.rb +125 -0
  49. data/rbi/rubydex.rbi +578 -15
  50. data/rust/Cargo.lock +7 -0
  51. data/rust/rubydex/Cargo.toml +1 -0
  52. data/rust/rubydex/benches/graph_memory.rs +20 -4
  53. data/rust/rubydex/src/compile_assertions.rs +15 -0
  54. data/rust/rubydex/src/config.rs +538 -157
  55. data/rust/rubydex/src/diagnostic.rs +66 -40
  56. data/rust/rubydex/src/errors.rs +0 -1
  57. data/rust/rubydex/src/indexing/local_graph.rs +6 -5
  58. data/rust/rubydex/src/indexing/rbs_indexer.rs +284 -8
  59. data/rust/rubydex/src/indexing/ruby_indexer.rs +59 -70
  60. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +195 -86
  61. data/rust/rubydex/src/lib.rs +1 -0
  62. data/rust/rubydex/src/listing.rs +26 -1
  63. data/rust/rubydex/src/main.rs +9 -128
  64. data/rust/rubydex/src/model/declaration.rs +301 -229
  65. data/rust/rubydex/src/model/definitions.rs +27 -26
  66. data/rust/rubydex/src/model/document.rs +43 -7
  67. data/rust/rubydex/src/model/graph.rs +67 -68
  68. data/rust/rubydex/src/model/id.rs +55 -0
  69. data/rust/rubydex/src/model/ids.rs +21 -9
  70. data/rust/rubydex/src/model/name.rs +88 -19
  71. data/rust/rubydex/src/model/references.rs +16 -13
  72. data/rust/rubydex/src/operation/ruby_builder.rs +78 -104
  73. data/rust/rubydex/src/path_helpers.rs +77 -0
  74. data/rust/rubydex/src/query/cypher/schema.rs +853 -0
  75. data/rust/rubydex/src/query/cypher/schema_info.rs +161 -0
  76. data/rust/rubydex/src/query/cypher/tests.rs +253 -0
  77. data/rust/rubydex/src/query/cypher.rs +54 -0
  78. data/rust/rubydex/src/query.rs +125 -43
  79. data/rust/rubydex/src/resolution.rs +368 -395
  80. data/rust/rubydex/src/resolution_tests.rs +504 -78
  81. data/rust/rubydex/src/test_utils/context.rs +2 -1
  82. data/rust/rubydex/src/test_utils/graph_test.rs +26 -12
  83. data/rust/rubydex/src/test_utils/local_graph_test.rs +19 -0
  84. data/rust/rubydex/tests/cli.rs +4 -4
  85. data/rust/rubydex-sys/src/config_api.rs +205 -0
  86. data/rust/rubydex-sys/src/cypher_api.rs +791 -0
  87. data/rust/rubydex-sys/src/declaration_api.rs +6 -3
  88. data/rust/rubydex-sys/src/definition_api.rs +27 -7
  89. data/rust/rubydex-sys/src/diagnostic_api.rs +77 -8
  90. data/rust/rubydex-sys/src/graph_api.rs +31 -68
  91. data/rust/rubydex-sys/src/lib.rs +2 -0
  92. data/rust/rubydex-sys/src/name_api.rs +2 -6
  93. data/rust/rubydex-sys/src/reference_api.rs +58 -12
  94. data/rust/rubydex-sys/src/utils.rs +37 -0
  95. data/skills/send-private-method/SKILL.md +133 -0
  96. metadata +37 -2
@@ -8,9 +8,8 @@ use crate::{
8
8
  assert_declaration_exists, assert_declaration_kind_eq, assert_declaration_references_count_eq, assert_descendants,
9
9
  assert_diagnostics_eq, assert_instance_variables_eq, assert_members_eq, assert_no_constant_alias_target,
10
10
  assert_no_diagnostics, assert_no_members, assert_owner_eq, assert_singleton_class_eq,
11
- diagnostic::Rule,
11
+ diagnostic::{Rule, Severity},
12
12
  model::{declaration::Ancestors, ids::DeclarationId, name::NameRef},
13
- resolution::Resolver,
14
13
  test_utils::GraphTest,
15
14
  };
16
15
 
@@ -1495,6 +1494,43 @@ mod include_tests {
1495
1494
  assert_descendants!(context, "Bar", ["Baz"]);
1496
1495
  assert_descendants!(context, "Foo", ["Bar", "Baz"]);
1497
1496
  }
1497
+
1498
+ #[test]
1499
+ fn unresolved_include_followed_by_resolved_include_stays_partial() {
1500
+ let mut context = graph_test();
1501
+ context.index_uri("file:///foo.rb", {
1502
+ r"
1503
+ module M
1504
+ end
1505
+
1506
+ class C
1507
+ include Missing
1508
+ include M
1509
+ end
1510
+ "
1511
+ });
1512
+ context.resolve();
1513
+
1514
+ assert_ancestors_eq!(
1515
+ context,
1516
+ "C",
1517
+ ["C", "M", Partial("Missing"), "Object", "Kernel", "BasicObject"]
1518
+ );
1519
+ assert!(
1520
+ matches!(
1521
+ context
1522
+ .graph()
1523
+ .declarations()
1524
+ .get(&DeclarationId::from("C"))
1525
+ .unwrap()
1526
+ .as_namespace()
1527
+ .unwrap()
1528
+ .ancestors(),
1529
+ Ancestors::Partial(_)
1530
+ ),
1531
+ "C should have a partial chain: the `Missing` include never resolves"
1532
+ );
1533
+ }
1498
1534
  }
1499
1535
 
1500
1536
  mod prepend_tests {
@@ -2133,6 +2169,30 @@ mod object_ancestors_tests {
2133
2169
  assert_constant_reference_to!(context, "Kernel::FOUND_ME", "file:///foo.rb:11:3-11:11");
2134
2170
  assert_constant_reference_unresolved!(context, "FOUND_ME", "file:///foo.rb:14:6-14:14");
2135
2171
  }
2172
+
2173
+ #[test]
2174
+ fn object_inherited_constant_inside_module_reordered() {
2175
+ let mut context = graph_test();
2176
+ context.index_uri("file:///foo.rb", {
2177
+ r"
2178
+ module Foo
2179
+ # Accessible via Object inheritance
2180
+ FOUND_ME
2181
+ end
2182
+
2183
+ module Kernel
2184
+ FOUND_ME = true
2185
+ end
2186
+
2187
+ class Object
2188
+ include Kernel
2189
+ end
2190
+ "
2191
+ });
2192
+ context.resolve();
2193
+
2194
+ assert_constant_reference_to!(context, "Kernel::FOUND_ME", "file:///foo.rb:3:3-3:11");
2195
+ }
2136
2196
  }
2137
2197
 
2138
2198
  mod singleton_ancestors_tests {
@@ -3034,7 +3094,7 @@ mod variable_tests {
3034
3094
 
3035
3095
  assert_diagnostics_eq!(
3036
3096
  &context,
3037
- ["dynamic-singleton-definition: Dynamic receiver for singleton method definition (2:3-4:6)",]
3097
+ ["DynamicSingletonDefinition: Dynamic receiver for singleton method definition (2:3-4:6)",]
3038
3098
  );
3039
3099
 
3040
3100
  // Instance variable in method with unresolved receiver should not create a declaration
@@ -3097,13 +3157,14 @@ mod declaration_creation_tests {
3097
3157
  #[test]
3098
3158
  fn resolution_for_ambiguous_namespace_definitions() {
3099
3159
  // Like many examples of Ruby code that is ambiguous to static analysis, this example is ambiguous due to
3100
- // require order. If `foo.rb` is loaded first, then `Bar` doesn't exist, Ruby crashes and we should emit an
3101
- // error or warning for a non existing constant.
3160
+ // require order. If `a_qualified.rb` is loaded first, then `Bar` doesn't exist, Ruby crashes and we should
3161
+ // emit an error or warning for a non existing constant.
3102
3162
  //
3103
- // If `bar.rb` is loaded first, then `Bar` resolves to top level `Bar` and `Bar::Baz` is defined, completely
3104
- // escaping the `Foo` nesting.
3163
+ // If `z_parent.rb` is loaded first, then `Bar` resolves to top level `Bar` and `Bar::Baz` is defined,
3164
+ // completely escaping the `Foo` nesting. The URIs intentionally sort in the opposite order: name complexity
3165
+ // must take precedence over URI so the top level `Bar` is resolved before the qualified `Bar::Baz` definition.
3105
3166
  let mut context = graph_test();
3106
- context.index_uri("file:///foo.rb", {
3167
+ context.index_uri("file:///a_qualified.rb", {
3107
3168
  r"
3108
3169
  module Foo
3109
3170
  class Bar::Baz
@@ -3111,7 +3172,7 @@ mod declaration_creation_tests {
3111
3172
  end
3112
3173
  "
3113
3174
  });
3114
- context.index_uri("file:///bar.rb", {
3175
+ context.index_uri("file:///z_parent.rb", {
3115
3176
  r"
3116
3177
  module Bar
3117
3178
  end
@@ -3123,62 +3184,11 @@ mod declaration_creation_tests {
3123
3184
 
3124
3185
  assert_no_members!(context, "Foo");
3125
3186
  assert_owner_eq!(context, "Foo", "Object");
3187
+ assert_declaration_does_not_exist!(context, "Foo::Bar");
3126
3188
 
3127
3189
  assert_members_eq!(context, "Bar", ["Baz"]);
3128
3190
  assert_owner_eq!(context, "Bar", "Object");
3129
3191
  }
3130
-
3131
- #[test]
3132
- fn expected_name_depth_order() {
3133
- let mut context = graph_test();
3134
- context.index_uri("file:///foo.rb", {
3135
- r"
3136
- module Foo
3137
- module Bar
3138
- module Baz
3139
- end
3140
-
3141
- module ::Top
3142
- class AfterTop
3143
- end
3144
- end
3145
- end
3146
-
3147
- module Qux::Zip
3148
- module Zap
3149
- class Zop::Boop
3150
- end
3151
- end
3152
- end
3153
- end
3154
- "
3155
- });
3156
-
3157
- let depths = Resolver::compute_name_depths(context.graph().names());
3158
- let mut names = context
3159
- .graph()
3160
- .names()
3161
- .iter()
3162
- .filter(|(_, n)| {
3163
- !["Kernel", "BasicObject", "Object", "Module", "Class"]
3164
- .contains(&context.graph().strings().get(n.str()).unwrap().as_str())
3165
- })
3166
- .collect::<Vec<_>>();
3167
- assert_eq!(10, names.len());
3168
-
3169
- names.sort_by_key(|(id, _)| depths.get(id).unwrap());
3170
-
3171
- assert_eq!(
3172
- [
3173
- "Top", "Foo", "Bar", "Qux", "AfterTop", "Baz", "Zip", "Zap", "Zop", "Boop"
3174
- ],
3175
- names
3176
- .iter()
3177
- .map(|(_, n)| context.graph().strings().get(n.str()).unwrap().as_str())
3178
- .collect::<Vec<_>>()
3179
- .as_slice()
3180
- );
3181
- }
3182
3192
  }
3183
3193
 
3184
3194
  mod singleton_class_tests {
@@ -3973,6 +3983,41 @@ mod todo_tests {
3973
3983
  assert_members_eq!(context, "A::B::D", vec!["d_method()"]);
3974
3984
  }
3975
3985
 
3986
+ #[test]
3987
+ fn have_linearized_ancestors() {
3988
+ // Todos are namespaces, so they must be the first entry of their own ancestor chain. Member lookups walk that
3989
+ // chain, so an empty one makes every member of the Todo unreachable
3990
+ let mut context = graph_test();
3991
+ context.index_uri("file:///a.rb", {
3992
+ r"
3993
+ class A::B::C
3994
+ def foo; end
3995
+ end
3996
+ "
3997
+ });
3998
+ context.index_uri("file:///d.rb", "class A::B::D < A::B::C; end");
3999
+ context.resolve();
4000
+
4001
+ assert_declaration_kind_eq!(context, "A", "<TODO>");
4002
+ assert_declaration_kind_eq!(context, "A::B", "<TODO>");
4003
+ assert_ancestors_eq!(context, "A", ["A"]);
4004
+ assert_ancestors_eq!(context, "A::B", ["A::B"]);
4005
+
4006
+ // Every member of a Todo is only reachable because the Todo lists itself in its own chain
4007
+ assert_members_eq!(context, "A", ["B"]);
4008
+ assert_members_eq!(context, "A::B", ["C", "D"]);
4009
+
4010
+ // Classes nested in a Todo still inherit from each other. Both the superclass path `A::B::C` and `D`'s own
4011
+ // owner chain go through the Todos
4012
+ assert_ancestors_eq!(
4013
+ context,
4014
+ "A::B::D",
4015
+ ["A::B::D", "A::B::C", "Object", "Kernel", "BasicObject"]
4016
+ );
4017
+ assert_owner_eq!(context, "A::B::D", "A::B");
4018
+ assert_descendants!(context, "A::B::C", ["A::B::D"]);
4019
+ }
4020
+
3976
4021
  #[test]
3977
4022
  fn promoted_incrementally() {
3978
4023
  // Index class A::B::C first (creates Todos), then provide real definitions.
@@ -4535,6 +4580,7 @@ mod promotability_tests {
4535
4580
 
4536
4581
  context.resolve();
4537
4582
  assert_no_diagnostics!(&context);
4583
+ assert_declaration_kind_eq!(context, "Qux", "<TODO>");
4538
4584
  assert_declaration_exists!(context, "Qux::<Qux>");
4539
4585
  }
4540
4586
 
@@ -4570,9 +4616,8 @@ mod promotability_tests {
4570
4616
 
4571
4617
  #[test]
4572
4618
  fn promoted_constant_has_correct_ancestors() {
4573
- // When a promotable constant is auto-promoted via singleton class access, we conservatively
4574
- // promote to a module (not a class) since we don't know what the call returns.
4575
- // Modules don't inherit from Object.
4619
+ // When a promotable constant is auto-promoted via singleton class access, its kind remains unknown.
4620
+ // Todos do not inherit from Object.
4576
4621
  let mut context = graph_test();
4577
4622
  context.index_uri("file:///foo.rb", {
4578
4623
  r"
@@ -4583,6 +4628,7 @@ mod promotability_tests {
4583
4628
 
4584
4629
  context.resolve();
4585
4630
  assert_no_diagnostics!(&context);
4631
+ assert_declaration_kind_eq!(context, "Foo", "<TODO>");
4586
4632
  assert_ancestors_eq!(context, "Foo", ["Foo"]);
4587
4633
  }
4588
4634
 
@@ -4633,8 +4679,8 @@ mod promotability_tests {
4633
4679
  });
4634
4680
 
4635
4681
  context.resolve();
4636
- assert_declaration_kind_eq!(context, "Foo", "Module");
4637
- assert_declaration_kind_eq!(context, "Foo::Bar", "Module");
4682
+ assert_declaration_kind_eq!(context, "Foo", "<TODO>");
4683
+ assert_declaration_kind_eq!(context, "Foo::Bar", "<TODO>");
4638
4684
  assert_declaration_kind_eq!(context, "Foo::Bar::Baz", "Constant");
4639
4685
  }
4640
4686
 
@@ -4652,7 +4698,7 @@ mod promotability_tests {
4652
4698
  });
4653
4699
 
4654
4700
  context.resolve();
4655
- assert_declaration_kind_eq!(context, "Foo", "Module");
4701
+ assert_declaration_kind_eq!(context, "Foo", "<TODO>");
4656
4702
  assert_declaration_exists!(context, "Foo::<Foo>#bar()");
4657
4703
  }
4658
4704
 
@@ -4675,6 +4721,131 @@ mod promotability_tests {
4675
4721
  assert_declaration_does_not_exist!(context, "Foo::<Foo>#bar()");
4676
4722
  }
4677
4723
 
4724
+ #[test]
4725
+ fn rbs_constant_is_promotable_for_singleton_call() {
4726
+ let mut context = graph_test();
4727
+ context.index_rbs_uri("file:///env.rbs", "ENV: untyped");
4728
+ context.index_uri("file:///env.rb", {
4729
+ r#"
4730
+ ENV.fetch("FEATURE")
4731
+ "#
4732
+ });
4733
+ context.resolve();
4734
+
4735
+ assert_no_diagnostics!(&context);
4736
+ assert_declaration_exists!(context, "ENV::<ENV>");
4737
+
4738
+ let fetch = context
4739
+ .graph()
4740
+ .method_references()
4741
+ .values()
4742
+ .find(|method| *method.str() == "fetch".into())
4743
+ .expect("ENV.fetch should be indexed");
4744
+ let receiver_name_id = fetch.receiver().expect("ENV.fetch should have a receiver");
4745
+ let NameRef::Resolved(receiver) = context.graph().names().get(&receiver_name_id).unwrap() else {
4746
+ panic!("ENV.fetch receiver should resolve");
4747
+ };
4748
+ assert_eq!(*receiver.declaration_id(), DeclarationId::from("ENV::<ENV>"));
4749
+ }
4750
+
4751
+ #[test]
4752
+ fn singleton_on_todo_is_preserved_and_relinearized_when_promoted_to_class() {
4753
+ let mut context = graph_test();
4754
+ context.index_uri("file:///placeholder.rb", "class Foo::Placeholder; end");
4755
+ context.resolve();
4756
+
4757
+ assert_declaration_kind_eq!(context, "Foo", "<TODO>");
4758
+
4759
+ context.index_uri("file:///value.rb", "Foo.call");
4760
+ context.resolve();
4761
+
4762
+ assert_singleton_class_eq!(context, "Foo", "Foo::<Foo>");
4763
+
4764
+ context.index_uri("file:///class.rb", "class Foo; end");
4765
+ context.resolve();
4766
+
4767
+ assert_declaration_kind_eq!(context, "Foo", "Class");
4768
+ assert_singleton_class_eq!(context, "Foo", "Foo::<Foo>");
4769
+ assert_ancestors_eq!(
4770
+ context,
4771
+ "Foo::<Foo>",
4772
+ [
4773
+ "Foo::<Foo>",
4774
+ "Object::<Object>",
4775
+ "BasicObject::<BasicObject>",
4776
+ "Class",
4777
+ "Module",
4778
+ "Object",
4779
+ "Kernel",
4780
+ "BasicObject"
4781
+ ]
4782
+ );
4783
+ }
4784
+
4785
+ #[test]
4786
+ fn singleton_on_todo_is_preserved_when_promoted_to_module() {
4787
+ let mut context = graph_test();
4788
+ context.index_uri("file:///placeholder.rb", "class Bar::Placeholder; end");
4789
+ context.resolve();
4790
+
4791
+ assert_declaration_kind_eq!(context, "Bar", "<TODO>");
4792
+
4793
+ context.index_uri("file:///value.rb", "Bar.call");
4794
+ context.resolve();
4795
+
4796
+ assert_singleton_class_eq!(context, "Bar", "Bar::<Bar>");
4797
+
4798
+ context.index_uri("file:///module.rb", "module Bar; end");
4799
+ context.resolve();
4800
+
4801
+ assert_declaration_kind_eq!(context, "Bar", "Module");
4802
+ assert_singleton_class_eq!(context, "Bar", "Bar::<Bar>");
4803
+ assert_ancestors_eq!(
4804
+ context,
4805
+ "Bar::<Bar>",
4806
+ ["Bar::<Bar>", "Module", "Object", "Kernel", "BasicObject"]
4807
+ );
4808
+ }
4809
+
4810
+ #[test]
4811
+ fn higher_order_singleton_on_todo_is_relinearized_when_promoted_to_class() {
4812
+ let mut context = graph_test();
4813
+ context.index_uri("file:///placeholder.rb", "class Foo::Placeholder; end");
4814
+ context.resolve();
4815
+
4816
+ context.index_uri("file:///value.rb", {
4817
+ r"
4818
+ class << Foo
4819
+ class << self
4820
+ end
4821
+ end
4822
+ "
4823
+ });
4824
+ context.resolve();
4825
+
4826
+ context.index_uri("file:///class.rb", "class Foo; end");
4827
+ context.resolve();
4828
+
4829
+ assert_ancestors_eq!(
4830
+ context,
4831
+ "Foo::<Foo>::<<Foo>>",
4832
+ [
4833
+ "Foo::<Foo>::<<Foo>>",
4834
+ "Object::<Object>::<<Object>>",
4835
+ "BasicObject::<BasicObject>::<<BasicObject>>",
4836
+ "Class::<Class>",
4837
+ "Module::<Module>",
4838
+ "Object::<Object>",
4839
+ "BasicObject::<BasicObject>",
4840
+ "Class",
4841
+ "Module",
4842
+ "Object",
4843
+ "Kernel",
4844
+ "BasicObject"
4845
+ ]
4846
+ );
4847
+ }
4848
+
4678
4849
  #[test]
4679
4850
  fn ivar_defined_inside_of_undefined_alias_namespace() {
4680
4851
  let mut context = graph_test();
@@ -4882,6 +5053,67 @@ mod rbs_tests {
4882
5053
  assert_declaration_exists!(context, "Foo::Bar::Baz");
4883
5054
  }
4884
5055
 
5056
+ #[test]
5057
+ fn rbs_qualified_mixin_resolves_through_parent_scope() {
5058
+ let mut context = graph_test();
5059
+ context.index_rbs_uri("file:///minitest.rbs", {
5060
+ r"
5061
+ module Minitest
5062
+ module Guard
5063
+ end
5064
+
5065
+ module Assertions
5066
+ end
5067
+
5068
+ class Runnable
5069
+ end
5070
+ end
5071
+ "
5072
+ });
5073
+ context.index_rbs_uri("file:///minitest/test.rbs", {
5074
+ r"
5075
+ class Minitest::Test < ::Minitest::Runnable
5076
+ include Minitest::Assertions
5077
+ include Minitest::Guard
5078
+ extend Minitest::Guard
5079
+ end
5080
+ "
5081
+ });
5082
+ context.resolve();
5083
+
5084
+ assert_no_diagnostics!(&context);
5085
+
5086
+ assert_ancestors_eq!(
5087
+ context,
5088
+ "Minitest::Test",
5089
+ [
5090
+ "Minitest::Test",
5091
+ "Minitest::Guard",
5092
+ "Minitest::Assertions",
5093
+ "Minitest::Runnable",
5094
+ "Object",
5095
+ "Kernel",
5096
+ "BasicObject"
5097
+ ]
5098
+ );
5099
+ assert_ancestors_eq!(
5100
+ context,
5101
+ "Minitest::Test::<Test>",
5102
+ [
5103
+ "Minitest::Test::<Test>",
5104
+ "Minitest::Guard",
5105
+ "Minitest::Runnable::<Runnable>",
5106
+ "Object::<Object>",
5107
+ "BasicObject::<BasicObject>",
5108
+ "Class",
5109
+ "Module",
5110
+ "Object",
5111
+ "Kernel",
5112
+ "BasicObject"
5113
+ ]
5114
+ );
5115
+ }
5116
+
4885
5117
  #[test]
4886
5118
  fn rbs_qualified_name_inside_nested_module() {
4887
5119
  let mut context = graph_test();
@@ -4986,6 +5218,38 @@ mod rbs_tests {
4986
5218
  );
4987
5219
  }
4988
5220
 
5221
+ #[test]
5222
+ fn rbs_attributes_create_method_declarations_without_instance_variable_declarations() {
5223
+ let mut context = graph_test();
5224
+ context.index_rbs_uri("file:///attributes.rbs", {
5225
+ r"
5226
+ class Foo
5227
+ attr_reader reader: String
5228
+ attr_writer writer (@writer): Integer
5229
+ attr_accessor accessor(): bool
5230
+ private attr_accessor self.class_value (@class_value): Symbol
5231
+ end
5232
+ "
5233
+ });
5234
+ context.resolve();
5235
+
5236
+ assert_no_diagnostics!(&context);
5237
+ for method in [
5238
+ "Foo#reader()",
5239
+ "Foo#writer=()",
5240
+ "Foo#accessor()",
5241
+ "Foo#accessor=()",
5242
+ "Foo::<Foo>#class_value()",
5243
+ "Foo::<Foo>#class_value=()",
5244
+ ] {
5245
+ assert_declaration_exists!(context, method);
5246
+ assert_declaration_kind_eq!(context, method, "Method");
5247
+ }
5248
+ for instance_variable in ["Foo#@reader", "Foo#@writer", "Foo::<Foo>#@class_value"] {
5249
+ assert_declaration_does_not_exist!(context, instance_variable);
5250
+ }
5251
+ }
5252
+
4989
5253
  #[test]
4990
5254
  fn rbs_mixin_resolution() {
4991
5255
  let mut context = graph_test();
@@ -5225,8 +5489,9 @@ mod visibility_resolution_tests {
5225
5489
  assert_diagnostics_eq!(
5226
5490
  context,
5227
5491
  &[
5228
- "undefined-method-visibility-target: undefined method `Foo#nonexistent()` for visibility change (2:12-2:23)"
5229
- ]
5492
+ "UndefinedMethodVisibilityTarget: undefined method `Foo#nonexistent()` for visibility change (2:12-2:23)"
5493
+ ],
5494
+ severity: Severity::Warning
5230
5495
  );
5231
5496
  }
5232
5497
 
@@ -5360,9 +5625,10 @@ mod visibility_resolution_tests {
5360
5625
  assert_diagnostics_eq!(
5361
5626
  context,
5362
5627
  &[
5363
- "undefined-constant-visibility-target: undefined constant `NOPE_ONE` for visibility change in `Foo` (2:21-2:29)",
5364
- "undefined-constant-visibility-target: undefined constant `NOPE_TWO` for visibility change in `Foo` (2:32-2:40)",
5365
- ]
5628
+ "UndefinedConstantVisibilityTarget: undefined constant `NOPE_ONE` for visibility change in `Foo` (2:21-2:29)",
5629
+ "UndefinedConstantVisibilityTarget: undefined constant `NOPE_TWO` for visibility change in `Foo` (2:32-2:40)",
5630
+ ],
5631
+ severity: Severity::Warning
5366
5632
  );
5367
5633
  }
5368
5634
 
@@ -5386,7 +5652,7 @@ mod visibility_resolution_tests {
5386
5652
  assert_diagnostics_eq!(
5387
5653
  context,
5388
5654
  &[
5389
- "undefined-constant-visibility-target: undefined constant `CONST` for visibility change in `Child` (6:21-6:26)"
5655
+ "UndefinedConstantVisibilityTarget: undefined constant `CONST` for visibility change in `Child` (6:21-6:26)"
5390
5656
  ]
5391
5657
  );
5392
5658
  assert_visibility_eq!(context, "Parent::CONST", Visibility::Public);
@@ -5528,7 +5794,7 @@ mod visibility_resolution_tests {
5528
5794
  assert_diagnostics_eq!(
5529
5795
  context,
5530
5796
  &[
5531
- "undefined-method-visibility-target: undefined method `Foo::<Foo>#nonexistent()` for visibility change (2:25-2:36)"
5797
+ "UndefinedMethodVisibilityTarget: undefined method `Foo::<Foo>#nonexistent()` for visibility change (2:25-2:36)"
5532
5798
  ]
5533
5799
  );
5534
5800
  }
@@ -5556,7 +5822,7 @@ mod visibility_resolution_tests {
5556
5822
  assert_diagnostics_eq!(
5557
5823
  context,
5558
5824
  &[
5559
- "undefined-method-visibility-target: undefined method `Foo::<Foo>#missing()` for visibility change (2:25-2:32)"
5825
+ "UndefinedMethodVisibilityTarget: undefined method `Foo::<Foo>#missing()` for visibility change (2:25-2:32)"
5560
5826
  ]
5561
5827
  );
5562
5828
 
@@ -5582,7 +5848,7 @@ mod visibility_resolution_tests {
5582
5848
  assert_diagnostics_eq!(
5583
5849
  context,
5584
5850
  &[
5585
- "undefined-method-visibility-target: undefined method `Foo::<Foo>#missing()` for visibility change (2:25-2:32)"
5851
+ "UndefinedMethodVisibilityTarget: undefined method `Foo::<Foo>#missing()` for visibility change (2:25-2:32)"
5586
5852
  ]
5587
5853
  );
5588
5854
 
@@ -5603,7 +5869,7 @@ mod visibility_resolution_tests {
5603
5869
 
5604
5870
  #[test]
5605
5871
  fn retroactive_singleton_method_visibility_inline_def() {
5606
- let mut context = GraphTest::new();
5872
+ let mut context = graph_test();
5607
5873
  context.index_uri(
5608
5874
  "file:///foo.rb",
5609
5875
  r"
@@ -5620,7 +5886,7 @@ mod visibility_resolution_tests {
5620
5886
 
5621
5887
  #[test]
5622
5888
  fn retroactive_singleton_method_visibility_array_form() {
5623
- let mut context = GraphTest::new();
5889
+ let mut context = graph_test();
5624
5890
  context.index_uri(
5625
5891
  "file:///foo.rb",
5626
5892
  r#"
@@ -5641,4 +5907,164 @@ mod visibility_resolution_tests {
5641
5907
  assert_visibility_eq!(context, "Foo::<Foo>#b()", Visibility::Private);
5642
5908
  assert_visibility_eq!(context, "Foo::<Foo>#c()", Visibility::Public);
5643
5909
  }
5910
+
5911
+ #[test]
5912
+ fn inline_module_function() {
5913
+ let mut context = graph_test();
5914
+ context.index_uri(
5915
+ "file:///foo.rb",
5916
+ r"
5917
+ module Foo
5918
+ module_function def bar; end
5919
+ end
5920
+ ",
5921
+ );
5922
+ context.resolve();
5923
+
5924
+ assert_no_diagnostics!(&context);
5925
+ assert_visibility_eq!(context, "Foo#bar()", Visibility::Private);
5926
+ assert_visibility_eq!(context, "Foo::<Foo>#bar()", Visibility::Public);
5927
+ }
5928
+
5929
+ #[test]
5930
+ fn inline_module_function_singleton_companion_visibility_can_be_changed() {
5931
+ let mut context = graph_test();
5932
+ context.index_uri(
5933
+ "file:///foo.rb",
5934
+ r"
5935
+ module Foo
5936
+ module_function def bar; end
5937
+
5938
+ class << self
5939
+ private :bar
5940
+ end
5941
+ end
5942
+ ",
5943
+ );
5944
+ context.resolve();
5945
+
5946
+ assert_no_diagnostics!(&context);
5947
+ assert_visibility_eq!(context, "Foo#bar()", Visibility::Private);
5948
+ assert_visibility_eq!(context, "Foo::<Foo>#bar()", Visibility::Private);
5949
+ }
5950
+
5951
+ #[test]
5952
+ fn retroactive_module_function() {
5953
+ let mut context = graph_test();
5954
+ context.index_uri(
5955
+ "file:///foo.rb",
5956
+ r"
5957
+ module Foo
5958
+ def bar; end
5959
+ module_function :bar
5960
+ end
5961
+ ",
5962
+ );
5963
+ context.resolve();
5964
+
5965
+ assert_no_diagnostics!(&context);
5966
+ assert_visibility_eq!(context, "Foo#bar()", Visibility::Private);
5967
+ assert_visibility_eq!(context, "Foo::<Foo>#bar()", Visibility::Public);
5968
+
5969
+ // Instance: Method def + MethodVisibility def
5970
+ assert_declaration_definitions_count_eq!(context, "Foo#bar()", 2);
5971
+ // Singleton companion: only the MethodVisibility def is attached
5972
+ assert_declaration_definitions_count_eq!(context, "Foo::<Foo>#bar()", 1);
5973
+ }
5974
+
5975
+ #[test]
5976
+ fn retroactive_module_function_undefined_target_emits_single_diagnostic() {
5977
+ let mut context = graph_test();
5978
+ context.index_uri(
5979
+ "file:///foo.rb",
5980
+ r"
5981
+ module Foo
5982
+ module_function :missing
5983
+ end
5984
+ ",
5985
+ );
5986
+ context.resolve();
5987
+
5988
+ // The singleton-side def stays silent; only the instance-side def emits.
5989
+ assert_diagnostics_eq!(
5990
+ context,
5991
+ &["UndefinedMethodVisibilityTarget: undefined method `Foo#missing()` for visibility change (2:20-2:27)"]
5992
+ );
5993
+ assert_declaration_does_not_exist!(context, "Foo::<Foo>#missing()");
5994
+ assert_declaration_does_not_exist!(context, "Foo::<Foo>");
5995
+ }
5996
+
5997
+ #[test]
5998
+ fn retroactive_module_function_singleton_companion_cleared_when_removed_multi_file() {
5999
+ let mut context = graph_test();
6000
+ context.index_uri(
6001
+ "file:///a.rb",
6002
+ r"
6003
+ module Foo
6004
+ def bar; end
6005
+ module_function :bar
6006
+ end
6007
+ ",
6008
+ );
6009
+ context.index_uri(
6010
+ "file:///b.rb",
6011
+ r"
6012
+ module Foo
6013
+ def baz; end
6014
+ end
6015
+ ",
6016
+ );
6017
+ context.resolve();
6018
+
6019
+ assert_visibility_eq!(context, "Foo::<Foo>#bar()", Visibility::Public);
6020
+
6021
+ // Re-index a.rb without `module_function :bar`. Foo still has b.rb's Module def, so the
6022
+ // namespace stays alive and no cascade through Foo's singleton class runs. The singleton
6023
+ // companion must still be cleaned up via reverse-lookup invalidation of its own def.
6024
+ context.index_uri(
6025
+ "file:///a.rb",
6026
+ r"
6027
+ module Foo
6028
+ def bar; end
6029
+ end
6030
+ ",
6031
+ );
6032
+ context.resolve();
6033
+
6034
+ assert_no_diagnostics!(&context);
6035
+ assert_visibility_eq!(context, "Foo#bar()", Visibility::Public);
6036
+ assert_declaration_does_not_exist!(context, "Foo::<Foo>#bar()");
6037
+ }
6038
+
6039
+ #[test]
6040
+ fn retroactive_module_function_singleton_companion_cleared_when_removed() {
6041
+ let mut context = graph_test();
6042
+ context.index_uri(
6043
+ "file:///foo.rb",
6044
+ r"
6045
+ module Foo
6046
+ def bar; end
6047
+ module_function :bar
6048
+ end
6049
+ ",
6050
+ );
6051
+ context.resolve();
6052
+
6053
+ assert_visibility_eq!(context, "Foo::<Foo>#bar()", Visibility::Public);
6054
+
6055
+ context.index_uri(
6056
+ "file:///foo.rb",
6057
+ r"
6058
+ module Foo
6059
+ def bar; end
6060
+ end
6061
+ ",
6062
+ );
6063
+ context.resolve();
6064
+
6065
+ assert_no_diagnostics!(&context);
6066
+ assert_visibility_eq!(context, "Foo#bar()", Visibility::Public);
6067
+ assert_declaration_does_not_exist!(context, "Foo::<Foo>#bar()");
6068
+ assert_declaration_does_not_exist!(context, "Foo::<Foo>");
6069
+ }
5644
6070
  }