rubydex 0.3.0 → 0.4.1

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 +29 -7
  3. data/THIRD_PARTY_LICENSES.html +238 -2
  4. data/exe/rdx +2 -149
  5. data/ext/rubydex/config.c +140 -0
  6. data/ext/rubydex/config.h +16 -0
  7. data/ext/rubydex/definition.c +34 -0
  8. data/ext/rubydex/definition.h +3 -0
  9. data/ext/rubydex/diagnostic.c +28 -1
  10. data/ext/rubydex/diagnostic.h +2 -0
  11. data/ext/rubydex/extconf.rb +4 -2
  12. data/ext/rubydex/graph.c +71 -55
  13. data/ext/rubydex/graph.h +6 -0
  14. data/ext/rubydex/query.c +398 -16
  15. data/ext/rubydex/rubydex.c +2 -0
  16. data/ext/rubydex/utils.c +11 -4
  17. data/lib/ruby_lsp/rubydex/addon.rb +211 -0
  18. data/lib/rubydex/cli/command/console.rb +55 -0
  19. data/lib/rubydex/cli/command/lint/explain.rb +76 -0
  20. data/lib/rubydex/cli/command/lint.rb +208 -0
  21. data/lib/rubydex/cli/command/mcp.rb +30 -0
  22. data/lib/rubydex/cli/command/query.rb +70 -0
  23. data/lib/rubydex/cli/command/skill.rb +69 -0
  24. data/lib/rubydex/cli/command.rb +168 -0
  25. data/lib/rubydex/cli.rb +93 -0
  26. data/lib/rubydex/config.rb +59 -0
  27. data/lib/rubydex/diagnostic.rb +12 -3
  28. data/lib/rubydex/errors.rb +42 -1
  29. data/lib/rubydex/graph.rb +10 -3
  30. data/lib/rubydex/linter/custom_rule.rb +97 -0
  31. data/lib/rubydex/linter/helpers/path_helpers.rb +78 -0
  32. data/lib/rubydex/linter/helpers/source_access_helpers.rb +31 -0
  33. data/lib/rubydex/linter/rule_loader.rb +36 -0
  34. data/lib/rubydex/linter/rule_test_case.rb +343 -0
  35. data/lib/rubydex/linter/runner.rb +56 -0
  36. data/lib/rubydex/linter.rb +19 -0
  37. data/lib/rubydex/location.rb +3 -0
  38. data/lib/rubydex/mcp_server.rb +1 -2
  39. data/lib/rubydex/related_information.rb +17 -0
  40. data/lib/rubydex/rule.rb +33 -0
  41. data/lib/rubydex/rules/dynamic_ancestor.rb +29 -0
  42. data/lib/rubydex/rules/dynamic_constant_reference.rb +25 -0
  43. data/lib/rubydex/rules/dynamic_singleton_definition.rb +30 -0
  44. data/lib/rubydex/rules/invalid_constant_visibility.rb +28 -0
  45. data/lib/rubydex/rules/invalid_method_visibility.rb +28 -0
  46. data/lib/rubydex/rules/parse_error.rb +25 -0
  47. data/lib/rubydex/rules/parse_warning.rb +28 -0
  48. data/lib/rubydex/rules/top_level_mixin_self.rb +27 -0
  49. data/lib/rubydex/rules/undefined_constant_visibility_target.rb +27 -0
  50. data/lib/rubydex/rules/undefined_method_visibility_target.rb +27 -0
  51. data/lib/rubydex/severity.rb +70 -0
  52. data/lib/rubydex/skill.rb +89 -0
  53. data/lib/rubydex/skill_registry.rb +62 -0
  54. data/lib/rubydex/version.rb +1 -1
  55. data/lib/rubydex.rb +8 -0
  56. data/lib/rubydex_linter/rules/rule_structure.rb +140 -0
  57. data/rbi/rubydex.rbi +464 -21
  58. data/rust/Cargo.lock +2 -2
  59. data/rust/rubydex/Cargo.toml +5 -1
  60. data/rust/rubydex/benches/graph_memory.rs +3 -5
  61. data/rust/rubydex/src/bin/generate_ruby_rules.rs +94 -0
  62. data/rust/rubydex/src/config.rs +538 -157
  63. data/rust/rubydex/src/diagnostic.rs +162 -45
  64. data/rust/rubydex/src/errors.rs +0 -1
  65. data/rust/rubydex/src/indexing/local_graph.rs +6 -5
  66. data/rust/rubydex/src/indexing/rbs_indexer.rs +270 -6
  67. data/rust/rubydex/src/indexing/ruby_indexer.rs +10 -12
  68. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +134 -81
  69. data/rust/rubydex/src/lib.rs +1 -0
  70. data/rust/rubydex/src/listing.rs +26 -1
  71. data/rust/rubydex/src/main.rs +7 -4
  72. data/rust/rubydex/src/model/declaration.rs +302 -219
  73. data/rust/rubydex/src/model/graph.rs +27 -40
  74. data/rust/rubydex/src/model/name.rs +58 -17
  75. data/rust/rubydex/src/operation/ruby_builder.rs +30 -45
  76. data/rust/rubydex/src/path_helpers.rs +77 -0
  77. data/rust/rubydex/src/query/cypher/schema.rs +63 -0
  78. data/rust/rubydex/src/query/cypher/tests.rs +26 -1
  79. data/rust/rubydex/src/query/cypher.rs +8 -11
  80. data/rust/rubydex/src/query.rs +314 -44
  81. data/rust/rubydex/src/resolution.rs +139 -187
  82. data/rust/rubydex/src/resolution_tests.rs +247 -19
  83. data/rust/rubydex/src/test_utils/context.rs +2 -1
  84. data/rust/rubydex/src/test_utils/graph_test.rs +26 -12
  85. data/rust/rubydex/src/test_utils/local_graph_test.rs +19 -0
  86. data/rust/rubydex/tests/cli.rs +4 -4
  87. data/rust/rubydex-sys/src/config_api.rs +205 -0
  88. data/rust/rubydex-sys/src/cypher_api.rs +791 -0
  89. data/rust/rubydex-sys/src/definition_api.rs +68 -1
  90. data/rust/rubydex-sys/src/diagnostic_api.rs +42 -8
  91. data/rust/rubydex-sys/src/graph_api.rs +125 -205
  92. data/rust/rubydex-sys/src/lib.rs +2 -0
  93. data/rust/rubydex-sys/src/name_api.rs +49 -17
  94. data/rust/rubydex-sys/src/utils.rs +37 -0
  95. data/skills/send-private-method/SKILL.md +133 -0
  96. metadata +42 -2
@@ -8,7 +8,7 @@ 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
13
  test_utils::GraphTest,
14
14
  };
@@ -1494,6 +1494,43 @@ mod include_tests {
1494
1494
  assert_descendants!(context, "Bar", ["Baz"]);
1495
1495
  assert_descendants!(context, "Foo", ["Bar", "Baz"]);
1496
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
+ }
1497
1534
  }
1498
1535
 
1499
1536
  mod prepend_tests {
@@ -2132,6 +2169,30 @@ mod object_ancestors_tests {
2132
2169
  assert_constant_reference_to!(context, "Kernel::FOUND_ME", "file:///foo.rb:11:3-11:11");
2133
2170
  assert_constant_reference_unresolved!(context, "FOUND_ME", "file:///foo.rb:14:6-14:14");
2134
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
+ }
2135
2196
  }
2136
2197
 
2137
2198
  mod singleton_ancestors_tests {
@@ -3033,7 +3094,7 @@ mod variable_tests {
3033
3094
 
3034
3095
  assert_diagnostics_eq!(
3035
3096
  &context,
3036
- ["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)",]
3037
3098
  );
3038
3099
 
3039
3100
  // Instance variable in method with unresolved receiver should not create a declaration
@@ -3922,6 +3983,41 @@ mod todo_tests {
3922
3983
  assert_members_eq!(context, "A::B::D", vec!["d_method()"]);
3923
3984
  }
3924
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
+
3925
4021
  #[test]
3926
4022
  fn promoted_incrementally() {
3927
4023
  // Index class A::B::C first (creates Todos), then provide real definitions.
@@ -4652,6 +4748,104 @@ mod promotability_tests {
4652
4748
  assert_eq!(*receiver.declaration_id(), DeclarationId::from("ENV::<ENV>"));
4653
4749
  }
4654
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
+
4655
4849
  #[test]
4656
4850
  fn ivar_defined_inside_of_undefined_alias_namespace() {
4657
4851
  let mut context = graph_test();
@@ -5024,6 +5218,38 @@ mod rbs_tests {
5024
5218
  );
5025
5219
  }
5026
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
+
5027
5253
  #[test]
5028
5254
  fn rbs_mixin_resolution() {
5029
5255
  let mut context = graph_test();
@@ -5263,8 +5489,9 @@ mod visibility_resolution_tests {
5263
5489
  assert_diagnostics_eq!(
5264
5490
  context,
5265
5491
  &[
5266
- "undefined-method-visibility-target: undefined method `Foo#nonexistent()` for visibility change (2:12-2:23)"
5267
- ]
5492
+ "UndefinedMethodVisibilityTarget: undefined method `Foo#nonexistent()` for visibility change (2:12-2:23)"
5493
+ ],
5494
+ severity: Severity::Warning
5268
5495
  );
5269
5496
  }
5270
5497
 
@@ -5398,9 +5625,10 @@ mod visibility_resolution_tests {
5398
5625
  assert_diagnostics_eq!(
5399
5626
  context,
5400
5627
  &[
5401
- "undefined-constant-visibility-target: undefined constant `NOPE_ONE` for visibility change in `Foo` (2:21-2:29)",
5402
- "undefined-constant-visibility-target: undefined constant `NOPE_TWO` for visibility change in `Foo` (2:32-2:40)",
5403
- ]
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
5404
5632
  );
5405
5633
  }
5406
5634
 
@@ -5424,7 +5652,7 @@ mod visibility_resolution_tests {
5424
5652
  assert_diagnostics_eq!(
5425
5653
  context,
5426
5654
  &[
5427
- "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)"
5428
5656
  ]
5429
5657
  );
5430
5658
  assert_visibility_eq!(context, "Parent::CONST", Visibility::Public);
@@ -5566,7 +5794,7 @@ mod visibility_resolution_tests {
5566
5794
  assert_diagnostics_eq!(
5567
5795
  context,
5568
5796
  &[
5569
- "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)"
5570
5798
  ]
5571
5799
  );
5572
5800
  }
@@ -5594,7 +5822,7 @@ mod visibility_resolution_tests {
5594
5822
  assert_diagnostics_eq!(
5595
5823
  context,
5596
5824
  &[
5597
- "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)"
5598
5826
  ]
5599
5827
  );
5600
5828
 
@@ -5620,7 +5848,7 @@ mod visibility_resolution_tests {
5620
5848
  assert_diagnostics_eq!(
5621
5849
  context,
5622
5850
  &[
5623
- "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)"
5624
5852
  ]
5625
5853
  );
5626
5854
 
@@ -5641,7 +5869,7 @@ mod visibility_resolution_tests {
5641
5869
 
5642
5870
  #[test]
5643
5871
  fn retroactive_singleton_method_visibility_inline_def() {
5644
- let mut context = GraphTest::new();
5872
+ let mut context = graph_test();
5645
5873
  context.index_uri(
5646
5874
  "file:///foo.rb",
5647
5875
  r"
@@ -5658,7 +5886,7 @@ mod visibility_resolution_tests {
5658
5886
 
5659
5887
  #[test]
5660
5888
  fn retroactive_singleton_method_visibility_array_form() {
5661
- let mut context = GraphTest::new();
5889
+ let mut context = graph_test();
5662
5890
  context.index_uri(
5663
5891
  "file:///foo.rb",
5664
5892
  r#"
@@ -5682,7 +5910,7 @@ mod visibility_resolution_tests {
5682
5910
 
5683
5911
  #[test]
5684
5912
  fn inline_module_function() {
5685
- let mut context = GraphTest::new();
5913
+ let mut context = graph_test();
5686
5914
  context.index_uri(
5687
5915
  "file:///foo.rb",
5688
5916
  r"
@@ -5722,7 +5950,7 @@ mod visibility_resolution_tests {
5722
5950
 
5723
5951
  #[test]
5724
5952
  fn retroactive_module_function() {
5725
- let mut context = GraphTest::new();
5953
+ let mut context = graph_test();
5726
5954
  context.index_uri(
5727
5955
  "file:///foo.rb",
5728
5956
  r"
@@ -5746,7 +5974,7 @@ mod visibility_resolution_tests {
5746
5974
 
5747
5975
  #[test]
5748
5976
  fn retroactive_module_function_undefined_target_emits_single_diagnostic() {
5749
- let mut context = GraphTest::new();
5977
+ let mut context = graph_test();
5750
5978
  context.index_uri(
5751
5979
  "file:///foo.rb",
5752
5980
  r"
@@ -5760,7 +5988,7 @@ mod visibility_resolution_tests {
5760
5988
  // The singleton-side def stays silent; only the instance-side def emits.
5761
5989
  assert_diagnostics_eq!(
5762
5990
  context,
5763
- &["undefined-method-visibility-target: undefined method `Foo#missing()` for visibility change (2:20-2:27)"]
5991
+ &["UndefinedMethodVisibilityTarget: undefined method `Foo#missing()` for visibility change (2:20-2:27)"]
5764
5992
  );
5765
5993
  assert_declaration_does_not_exist!(context, "Foo::<Foo>#missing()");
5766
5994
  assert_declaration_does_not_exist!(context, "Foo::<Foo>");
@@ -5768,7 +5996,7 @@ mod visibility_resolution_tests {
5768
5996
 
5769
5997
  #[test]
5770
5998
  fn retroactive_module_function_singleton_companion_cleared_when_removed_multi_file() {
5771
- let mut context = GraphTest::new();
5999
+ let mut context = graph_test();
5772
6000
  context.index_uri(
5773
6001
  "file:///a.rb",
5774
6002
  r"
@@ -5810,7 +6038,7 @@ mod visibility_resolution_tests {
5810
6038
 
5811
6039
  #[test]
5812
6040
  fn retroactive_module_function_singleton_companion_cleared_when_removed() {
5813
- let mut context = GraphTest::new();
6041
+ let mut context = graph_test();
5814
6042
  context.index_uri(
5815
6043
  "file:///foo.rb",
5816
6044
  r"
@@ -1,4 +1,5 @@
1
1
  use super::normalize_indentation;
2
+ use crate::path_helpers;
2
3
  use std::fs;
3
4
  use std::path::{Path, PathBuf};
4
5
  use tempfile::TempDir;
@@ -37,7 +38,7 @@ impl Context {
37
38
  #[must_use]
38
39
  pub fn new() -> Self {
39
40
  let root = tempfile::tempdir().expect("failed to create temp dir");
40
- let absolute_path = fs::canonicalize(root.path()).unwrap();
41
+ let absolute_path = path_helpers::resolved(root.path()).unwrap();
41
42
  Self {
42
43
  _root: root,
43
44
  absolute_path,
@@ -540,18 +540,13 @@ macro_rules! assert_descendants {
540
540
  .declarations()
541
541
  .get(&$crate::model::ids::DeclarationId::from($parent))
542
542
  .unwrap();
543
- let actual = match parent {
544
- $crate::model::declaration::Declaration::Namespace($crate::model::declaration::Namespace::Class(class)) => {
545
- class.descendants().iter().cloned().collect::<Vec<_>>()
546
- }
547
- $crate::model::declaration::Declaration::Namespace($crate::model::declaration::Namespace::Module(
548
- module,
549
- )) => module.descendants().iter().cloned().collect::<Vec<_>>(),
550
- $crate::model::declaration::Declaration::Namespace(
551
- $crate::model::declaration::Namespace::SingletonClass(singleton),
552
- ) => singleton.descendants().iter().cloned().collect::<Vec<_>>(),
553
- _ => panic!("Tried to get descendants for a declaration that isn't a namespace"),
554
- };
543
+ let actual = parent
544
+ .as_namespace()
545
+ .expect("Tried to get descendants for a declaration that isn't a namespace")
546
+ .descendants()
547
+ .iter()
548
+ .cloned()
549
+ .collect::<Vec<_>>();
555
550
 
556
551
  for descendant in &$descendants {
557
552
  let descendant_id = $crate::model::ids::DeclarationId::from(*descendant);
@@ -667,6 +662,25 @@ macro_rules! assert_instance_variables_eq {
667
662
  #[cfg(test)]
668
663
  #[macro_export]
669
664
  macro_rules! assert_diagnostics_eq {
665
+ ($context:expr, $expected_diagnostics:expr, severity: $expected_severity:expr) => {{
666
+ let context = &$context;
667
+ $crate::assert_diagnostics_eq!(context, $expected_diagnostics);
668
+
669
+ let expected_severity = $expected_severity;
670
+ let actual_severities = context
671
+ .graph()
672
+ .all_diagnostics()
673
+ .iter()
674
+ .map(|diagnostic| diagnostic.rule().default_severity())
675
+ .collect::<Vec<_>>();
676
+ assert_eq!(
677
+ vec![expected_severity; actual_severities.len()],
678
+ actual_severities,
679
+ "diagnostic severities mismatch: expected all `{:?}`, got `{:?}`",
680
+ expected_severity,
681
+ actual_severities
682
+ );
683
+ }};
670
684
  ($context:expr, $expected_diagnostics:expr) => {{
671
685
  assert_eq!($expected_diagnostics, $context.format_diagnostics(&[]).as_slice());
672
686
  }};
@@ -557,6 +557,25 @@ macro_rules! assert_method_has_receiver {
557
557
  #[cfg(test)]
558
558
  #[macro_export]
559
559
  macro_rules! assert_local_diagnostics_eq {
560
+ ($context:expr, $expected_diagnostics:expr, severity: $expected_severity:expr) => {{
561
+ let context = &$context;
562
+ $crate::assert_local_diagnostics_eq!(context, $expected_diagnostics);
563
+
564
+ let expected_severity = $expected_severity;
565
+ let actual_severities = context
566
+ .graph()
567
+ .diagnostics()
568
+ .iter()
569
+ .map(|diagnostic| diagnostic.rule().default_severity())
570
+ .collect::<Vec<_>>();
571
+ assert_eq!(
572
+ vec![expected_severity; actual_severities.len()],
573
+ actual_severities,
574
+ "diagnostic severities mismatch: expected all `{:?}`, got `{:?}`",
575
+ expected_severity,
576
+ actual_severities
577
+ );
578
+ }};
560
579
  ($context:expr, $expected_diagnostics:expr) => {{
561
580
  let mut diagnostics = $context.graph().diagnostics().iter().collect::<Vec<_>>();
562
581
  diagnostics.sort_by_key(|d| d.offset());
@@ -60,7 +60,7 @@ fn single_directory_argument_is_workspace_root_for_config() {
60
60
  with_context(|context| {
61
61
  context.write("included.rb", "class Included\nend\n");
62
62
  context.write("excluded/skipped.rb", "class Skipped\nend\n");
63
- context.write("rubydex.toml", "exclude = [\"excluded\"]\n");
63
+ context.write("rubydex.toml", "[graph]\nexclude = [\"excluded\"]\n");
64
64
 
65
65
  rdx(&[context.absolute_path().to_str().unwrap()])
66
66
  .success()
@@ -74,7 +74,7 @@ fn first_directory_argument_is_workspace_root_for_config() {
74
74
  with_context(|context| {
75
75
  context.write("included/kept.rb", "class Included\nend\n");
76
76
  context.write("excluded/skipped.rb", "class Skipped\nend\n");
77
- context.write("rubydex.toml", "exclude = [\"excluded\"]\n");
77
+ context.write("rubydex.toml", "[graph]\nexclude = [\"excluded\"]\n");
78
78
 
79
79
  rdx(&[
80
80
  context.absolute_path().to_str().unwrap(),
@@ -92,7 +92,7 @@ fn single_file_argument_is_not_used_as_workspace_root() {
92
92
  with_context(|context| {
93
93
  context.write("included.rb", "class Included\nend\n");
94
94
  context.write("excluded/skipped.rb", "class Skipped\nend\n");
95
- context.write("rubydex.toml", "exclude = [\"excluded\"]\n");
95
+ context.write("rubydex.toml", "[graph]\nexclude = [\"excluded\"]\n");
96
96
 
97
97
  rdx(&[context.absolute_path_to("excluded/skipped.rb").to_str().unwrap()])
98
98
  .success()
@@ -106,7 +106,7 @@ fn first_file_argument_is_not_used_as_workspace_root() {
106
106
  with_context(|context| {
107
107
  context.write("included.rb", "class Included\nend\n");
108
108
  context.write("excluded/skipped.rb", "class Skipped\nend\n");
109
- context.write("rubydex.toml", "exclude = [\"excluded\"]\n");
109
+ context.write("rubydex.toml", "[graph]\nexclude = [\"excluded\"]\n");
110
110
 
111
111
  rdx(&[
112
112
  context.absolute_path_to("included.rb").to_str().unwrap(),