rubydex 0.1.0.beta8 → 0.1.0.beta9

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c07115229a5715e4501adce61eefbb4bfe26694f7945736829230d77e97c65a
4
- data.tar.gz: 5e845ff2cc4617790785c6cca4f5f67a0cfb02d02d88721062b39fff75e715d1
3
+ metadata.gz: d9838707935ff868fb005a0d85254747ea6e3a5b5871c0626f5ec6641e0878e2
4
+ data.tar.gz: 87f4157545d244bfb3bb7e869914dc355d1d2b3dd0e181e439ce81ce1073cdc5
5
5
  SHA512:
6
- metadata.gz: 0e5103b95c2123cae03d2f40a1e39ef642aee65b47f99416b034a0f40365bf8535a8829794df393274155bf5e548c44a412457e00fdf8701922e08a9926e3b27
7
- data.tar.gz: 478a4a347f1c95210e69e00c2e609abd6ed1c090bae1a5bf30266ade51efc68eaaa294a3f5371d6890497b5d9a782dc0d42c1009c7440d28a1030753a6a03647
6
+ metadata.gz: 7a69359f6261bb54eb0f576fbb9c31b85eb209c45fdb60d81d5f965abed05ac535409191b8a87376bcabe8278149ef1f87c8097162204d583f1ed2670bea7d5e
7
+ data.tar.gz: 862070c2cd67d83aae2da083987235017610d9637d6b90b49b8270a6ab8d3c4a1914333e30152f025f3e24e317fd3b3d0a74f3f20e64ffcf25752503c40baa7f
Binary file
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubydex
4
- VERSION = "0.1.0.beta8"
4
+ VERSION = "0.1.0.beta9"
5
5
  end
data/rust/Cargo.toml CHANGED
@@ -12,6 +12,11 @@ lto = true
12
12
  opt-level = 3
13
13
  codegen-units = 1
14
14
 
15
+ [profile.profiling]
16
+ inherits = "release"
17
+ debug = true
18
+ strip = false
19
+
15
20
  [workspace.lints.clippy]
16
21
  # See: https://doc.rust-lang.org/clippy/lints.html
17
22
  correctness = "deny"
@@ -3,6 +3,7 @@ use std::collections::hash_map::Entry;
3
3
  use crate::diagnostic::{Diagnostic, Rule};
4
4
  use crate::model::definitions::Definition;
5
5
  use crate::model::document::Document;
6
+ use crate::model::graph::NameDependent;
6
7
  use crate::model::identity_maps::IdentityHashMap;
7
8
  use crate::model::ids::{DefinitionId, NameId, ReferenceId, StringId, UriId};
8
9
  use crate::model::name::{Name, NameRef};
@@ -18,6 +19,7 @@ type LocalGraphParts = (
18
19
  IdentityHashMap<NameId, NameRef>,
19
20
  IdentityHashMap<ReferenceId, ConstantReference>,
20
21
  IdentityHashMap<ReferenceId, MethodRef>,
22
+ IdentityHashMap<NameId, Vec<NameDependent>>,
21
23
  );
22
24
 
23
25
  #[derive(Debug)]
@@ -29,6 +31,7 @@ pub struct LocalGraph {
29
31
  names: IdentityHashMap<NameId, NameRef>,
30
32
  constant_references: IdentityHashMap<ReferenceId, ConstantReference>,
31
33
  method_references: IdentityHashMap<ReferenceId, MethodRef>,
34
+ name_dependents: IdentityHashMap<NameId, Vec<NameDependent>>,
32
35
  }
33
36
 
34
37
  impl LocalGraph {
@@ -42,6 +45,7 @@ impl LocalGraph {
42
45
  names: IdentityHashMap::default(),
43
46
  constant_references: IdentityHashMap::default(),
44
47
  method_references: IdentityHashMap::default(),
48
+ name_dependents: IdentityHashMap::default(),
45
49
  }
46
50
  }
47
51
 
@@ -70,6 +74,13 @@ impl LocalGraph {
70
74
  pub fn add_definition(&mut self, definition: Definition) -> DefinitionId {
71
75
  let definition_id = definition.id();
72
76
 
77
+ if let Some(name_id) = definition.name_id() {
78
+ self.name_dependents
79
+ .entry(*name_id)
80
+ .or_default()
81
+ .push(NameDependent::Definition(definition_id));
82
+ }
83
+
73
84
  if self.definitions.insert(definition_id, definition).is_some() {
74
85
  debug_assert!(false, "DefinitionId collision in local graph");
75
86
  }
@@ -117,6 +128,18 @@ impl LocalGraph {
117
128
  entry.get_mut().increment_ref_count(1);
118
129
  }
119
130
  Entry::Vacant(entry) => {
131
+ if let Some(&parent_scope) = name.parent_scope().as_ref() {
132
+ self.name_dependents
133
+ .entry(parent_scope)
134
+ .or_default()
135
+ .push(NameDependent::ChildName(name_id));
136
+ }
137
+ if let Some(&nesting_id) = name.nesting().as_ref() {
138
+ self.name_dependents
139
+ .entry(nesting_id)
140
+ .or_default()
141
+ .push(NameDependent::NestedName(name_id));
142
+ }
120
143
  entry.insert(NameRef::Unresolved(Box::new(name)));
121
144
  }
122
145
  }
@@ -133,6 +156,10 @@ impl LocalGraph {
133
156
 
134
157
  pub fn add_constant_reference(&mut self, reference: ConstantReference) -> ReferenceId {
135
158
  let reference_id = reference.id();
159
+ self.name_dependents
160
+ .entry(*reference.name_id())
161
+ .or_default()
162
+ .push(NameDependent::Reference(reference_id));
136
163
 
137
164
  if self.constant_references.insert(reference_id, reference).is_some() {
138
165
  debug_assert!(false, "ReferenceId collision in local graph");
@@ -172,6 +199,13 @@ impl LocalGraph {
172
199
  self.document.add_diagnostic(diagnostic);
173
200
  }
174
201
 
202
+ // Name dependents
203
+
204
+ #[must_use]
205
+ pub fn name_dependents(&self) -> &IdentityHashMap<NameId, Vec<NameDependent>> {
206
+ &self.name_dependents
207
+ }
208
+
175
209
  // Into parts
176
210
 
177
211
  #[must_use]
@@ -184,6 +218,7 @@ impl LocalGraph {
184
218
  self.names,
185
219
  self.constant_references,
186
220
  self.method_references,
221
+ self.name_dependents,
187
222
  )
188
223
  }
189
224
  }
@@ -1,8 +1,8 @@
1
1
  //! Visit the RBS AST and create type definitions.
2
2
 
3
3
  use ruby_rbs::node::{
4
- self, ClassNode, CommentNode, ConstantNode, ExtendNode, GlobalNode, IncludeNode, ModuleNode, Node, NodeList,
5
- PrependNode, TypeNameNode, Visit,
4
+ self, AliasKind, ClassNode, CommentNode, ConstantNode, ExtendNode, GlobalNode, IncludeNode, ModuleNode, Node,
5
+ NodeList, PrependNode, TypeNameNode, Visit,
6
6
  };
7
7
 
8
8
  use crate::diagnostic::Rule;
@@ -10,7 +10,7 @@ use crate::indexing::local_graph::LocalGraph;
10
10
  use crate::model::comment::Comment;
11
11
  use crate::model::definitions::{
12
12
  ClassDefinition, ConstantDefinition, Definition, DefinitionFlags, ExtendDefinition, GlobalVariableDefinition,
13
- IncludeDefinition, Mixin, ModuleDefinition, PrependDefinition,
13
+ IncludeDefinition, MethodAliasDefinition, Mixin, ModuleDefinition, PrependDefinition, Receiver,
14
14
  };
15
15
  use crate::model::document::Document;
16
16
  use crate::model::ids::{DefinitionId, NameId, ReferenceId, UriId};
@@ -147,14 +147,52 @@ impl<'a> RBSIndexer<'a> {
147
147
  }
148
148
  }
149
149
 
150
- fn collect_comments(comment_node: Option<CommentNode>) -> Vec<Comment> {
151
- comment_node
152
- .into_iter()
153
- .map(|comment| {
154
- let text = Self::bytes_to_string(comment.string().as_bytes());
155
- Comment::new(Offset::from_rbs_location(&comment.location()), text)
156
- })
157
- .collect()
150
+ #[allow(clippy::cast_possible_truncation)]
151
+ fn collect_comments(&self, comment_node: Option<CommentNode>) -> Box<[Comment]> {
152
+ let Some(comment_node) = comment_node else {
153
+ return Box::new([]);
154
+ };
155
+
156
+ let location = comment_node.location();
157
+ let start = location.start().cast_unsigned() as usize;
158
+ let end = location.end().cast_unsigned() as usize;
159
+
160
+ let comment_block = &self.source[start..end];
161
+ let lines: Vec<&str> = comment_block.split('\n').collect();
162
+
163
+ let mut comments = Vec::with_capacity(lines.len());
164
+ let mut current_offset = start as u32;
165
+
166
+ for (i, line) in lines.iter().enumerate() {
167
+ let mut size = 1;
168
+ let mut line = *line;
169
+
170
+ if line.ends_with('\r') {
171
+ line = line.trim_end_matches('\r');
172
+ size += 1;
173
+ }
174
+
175
+ let line_indent = if i == 0 {
176
+ 0
177
+ } else {
178
+ line.len() - line.trim_start().len()
179
+ };
180
+
181
+ // Skip past indentation to the comment text
182
+ current_offset += line_indent as u32;
183
+
184
+ let line_text = line[line_indent..].to_string();
185
+ let line_bytes = line_text.len() as u32;
186
+ let offset = Offset::new(current_offset, current_offset + line_bytes);
187
+ comments.push(Comment::new(offset, line_text));
188
+
189
+ // Advance past current line text + \r (if present) + \n for the next line
190
+ if i < lines.len() - 1 {
191
+ current_offset += line_bytes + size;
192
+ }
193
+ }
194
+
195
+ comments.into_boxed_slice()
158
196
  }
159
197
 
160
198
  fn register_definition(
@@ -201,7 +239,7 @@ impl Visit for RBSIndexer<'_> {
201
239
  let offset = Offset::from_rbs_location(&class_node.location());
202
240
  let name_offset = Offset::from_rbs_location(&type_name.name().location());
203
241
 
204
- let comments = Self::collect_comments(class_node.comment());
242
+ let comments = self.collect_comments(class_node.comment());
205
243
 
206
244
  let superclass_ref = class_node.super_class().as_ref().map(|super_node| {
207
245
  let type_name = super_node.name();
@@ -241,7 +279,7 @@ impl Visit for RBSIndexer<'_> {
241
279
  let offset = Offset::from_rbs_location(&module_node.location());
242
280
  let name_offset = Offset::from_rbs_location(&type_name.name().location());
243
281
 
244
- let comments = Self::collect_comments(module_node.comment());
282
+ let comments = self.collect_comments(module_node.comment());
245
283
 
246
284
  let definition = Definition::Module(Box::new(ModuleDefinition::new(
247
285
  name_id,
@@ -271,7 +309,7 @@ impl Visit for RBSIndexer<'_> {
271
309
  let name_id = self.index_type_name(&type_name, nesting_name_id);
272
310
  let offset = Offset::from_rbs_location(&constant_node.location());
273
311
 
274
- let comments = Self::collect_comments(constant_node.comment());
312
+ let comments = self.collect_comments(constant_node.comment());
275
313
 
276
314
  let definition = Definition::Constant(Box::new(ConstantDefinition::new(
277
315
  name_id,
@@ -293,7 +331,7 @@ impl Visit for RBSIndexer<'_> {
293
331
  .intern_string(Self::bytes_to_string(global_node.name().name()));
294
332
  let offset = Offset::from_rbs_location(&global_node.location());
295
333
 
296
- let comments = Self::collect_comments(global_node.comment());
334
+ let comments = self.collect_comments(global_node.comment());
297
335
 
298
336
  let definition = Definition::GlobalVariable(Box::new(GlobalVariableDefinition::new(
299
337
  str_id,
@@ -324,6 +362,37 @@ impl Visit for RBSIndexer<'_> {
324
362
  Mixin::Extend(ExtendDefinition::new(ref_id))
325
363
  });
326
364
  }
365
+
366
+ fn visit_alias_node(&mut self, alias_node: &node::AliasNode) {
367
+ let lexical_nesting_id = self.parent_lexical_scope_id();
368
+
369
+ let receiver = match alias_node.kind() {
370
+ AliasKind::Instance => None,
371
+ AliasKind::Singleton => lexical_nesting_id.map(Receiver::SelfReceiver),
372
+ };
373
+
374
+ let new_name = Self::bytes_to_string(alias_node.new_name().name());
375
+ let old_name = Self::bytes_to_string(alias_node.old_name().name());
376
+
377
+ let new_name_str_id = self.local_graph.intern_string(format!("{new_name}()"));
378
+ let old_name_str_id = self.local_graph.intern_string(format!("{old_name}()"));
379
+
380
+ let offset = Offset::from_rbs_location(&alias_node.location());
381
+ let comments = self.collect_comments(alias_node.comment());
382
+
383
+ let definition = Definition::MethodAlias(Box::new(MethodAliasDefinition::new(
384
+ new_name_str_id,
385
+ old_name_str_id,
386
+ self.uri_id,
387
+ offset,
388
+ comments,
389
+ Self::flags(&alias_node.annotations()),
390
+ lexical_nesting_id,
391
+ receiver,
392
+ )));
393
+
394
+ self.register_definition(definition, lexical_nesting_id);
395
+ }
327
396
  }
328
397
 
329
398
  #[cfg(test)]
@@ -331,11 +400,12 @@ mod tests {
331
400
  use ruby_rbs::node::{self, Node, NodeList};
332
401
 
333
402
  use crate::indexing::rbs_indexer::RBSIndexer;
334
- use crate::model::definitions::DefinitionFlags;
403
+ use crate::model::definitions::{Definition, DefinitionFlags};
335
404
  use crate::test_utils::LocalGraphTest;
336
405
  use crate::{
337
406
  assert_def_comments_eq, assert_def_mixins_eq, assert_def_name_eq, assert_def_name_offset_eq, assert_def_str_eq,
338
- assert_def_superclass_ref_eq, assert_definition_at, assert_local_diagnostics_eq, assert_no_local_diagnostics,
407
+ assert_def_superclass_ref_eq, assert_definition_at, assert_local_diagnostics_eq, assert_method_has_receiver,
408
+ assert_no_local_diagnostics, assert_string_eq,
339
409
  };
340
410
 
341
411
  fn index_source(source: &str) -> LocalGraphTest {
@@ -526,7 +596,7 @@ mod tests {
526
596
 
527
597
  assert_definition_at!(&context, "2:1-2:12", Constant, |def| {
528
598
  assert_def_name_eq!(&context, def, "FOO");
529
- assert_def_comments_eq!(&context, def, ["Some documentation\n"]);
599
+ assert_def_comments_eq!(&context, def, ["# Some documentation"]);
530
600
  });
531
601
  }
532
602
 
@@ -551,7 +621,7 @@ mod tests {
551
621
 
552
622
  assert_definition_at!(&context, "4:1-4:14", GlobalVariable, |def| {
553
623
  assert_def_str_eq!(&context, def, "$bar");
554
- assert_def_comments_eq!(&context, def, ["A global variable\n"]);
624
+ assert_def_comments_eq!(&context, def, ["# A global variable"]);
555
625
  });
556
626
  }
557
627
 
@@ -714,4 +784,155 @@ mod tests {
714
784
  assert!(def.flags().contains(DefinitionFlags::DEPRECATED));
715
785
  });
716
786
  }
787
+
788
+ #[test]
789
+ fn index_alias_node() {
790
+ let context = index_source({
791
+ "
792
+ class Foo
793
+ # Some documentation
794
+ alias bar baz
795
+ end
796
+ "
797
+ });
798
+
799
+ assert_no_local_diagnostics!(&context);
800
+
801
+ assert_definition_at!(&context, "1:1-4:4", Class, |class_def| {
802
+ assert_eq!(1, class_def.members().len());
803
+
804
+ assert_definition_at!(&context, "3:3-3:16", MethodAlias, |def| {
805
+ assert_string_eq!(&context, def.new_name_str_id(), "bar()");
806
+ assert_string_eq!(&context, def.old_name_str_id(), "baz()");
807
+ assert_def_comments_eq!(&context, def, ["# Some documentation"]);
808
+ assert_eq!(class_def.id(), def.lexical_nesting_id().unwrap());
809
+ });
810
+ });
811
+ }
812
+
813
+ #[test]
814
+ fn index_alias_node_with_deprecation() {
815
+ let context = index_source({
816
+ "
817
+ class Foo
818
+ %a{deprecated}
819
+ alias bar baz
820
+ end
821
+ "
822
+ });
823
+
824
+ assert_no_local_diagnostics!(&context);
825
+
826
+ assert_definition_at!(&context, "3:3-3:16", MethodAlias, |def| {
827
+ assert!(def.flags().contains(DefinitionFlags::DEPRECATED));
828
+ });
829
+ }
830
+
831
+ #[test]
832
+ fn index_alias_node_singleton() {
833
+ let context = index_source({
834
+ "
835
+ class Foo
836
+ alias self.bar self.baz
837
+ end
838
+ "
839
+ });
840
+
841
+ assert_no_local_diagnostics!(&context);
842
+ assert_eq!(context.graph().definitions().len(), 2);
843
+
844
+ assert_definition_at!(&context, "2:3-2:26", MethodAlias, |def| {
845
+ assert_string_eq!(&context, def.new_name_str_id(), "bar()");
846
+ assert_string_eq!(&context, def.old_name_str_id(), "baz()");
847
+ assert_method_has_receiver!(&context, def, "Foo");
848
+ });
849
+ }
850
+
851
+ #[test]
852
+ fn mixed_singleton_instance_alias_is_not_indexed() {
853
+ // Mixed aliases (`alias self.x y` and `alias x self.y`) are not valid RBS.
854
+ // Verify that no alias definitions are produced for these inputs.
855
+ for source in [
856
+ "
857
+ class Foo
858
+ alias self.bar baz
859
+ end
860
+ ",
861
+ "
862
+ class Foo
863
+ alias bar self.baz
864
+ end
865
+ ",
866
+ ] {
867
+ let context = index_source(source);
868
+ let has_alias = context
869
+ .graph()
870
+ .definitions()
871
+ .values()
872
+ .any(|d| matches!(d, Definition::MethodAlias(_)));
873
+ assert!(!has_alias, "Expected no alias definitions for: {source}");
874
+ }
875
+ }
876
+
877
+ #[test]
878
+ fn split_multiline_comments() {
879
+ let context = index_source({
880
+ "
881
+ # First line
882
+ # Second line
883
+ # Third line
884
+ class Foo
885
+ # A comment for Bar
886
+ # Another line for Bar
887
+ # One more line for Bar
888
+ module Bar
889
+ end
890
+ end
891
+
892
+ # splits strings at the \\n char
893
+ BAZ: Integer
894
+ "
895
+ });
896
+
897
+ assert_no_local_diagnostics!(&context);
898
+
899
+ assert_definition_at!(&context, "4:1-10:4", Class, |def| {
900
+ assert_def_name_eq!(&context, def, "Foo");
901
+ assert_def_comments_eq!(&context, def, ["# First line", "# Second line", "# Third line"]);
902
+ });
903
+
904
+ assert_definition_at!(&context, "8:3-9:6", Module, |def| {
905
+ assert_def_name_eq!(&context, def, "Bar");
906
+ assert_def_comments_eq!(
907
+ &context,
908
+ def,
909
+ [
910
+ "# A comment for Bar",
911
+ "# Another line for Bar",
912
+ "# One more line for Bar"
913
+ ]
914
+ );
915
+ });
916
+
917
+ assert_definition_at!(&context, "13:1-13:13", Constant, |def| {
918
+ assert_def_name_eq!(&context, def, "BAZ");
919
+ assert_def_comments_eq!(&context, def, ["# splits strings at the \\n char"]);
920
+ });
921
+ }
922
+
923
+ #[test]
924
+ fn split_multiline_comments_crlf() {
925
+ // Build the indexer directly to bypass normalize_indentation, which strips \r
926
+ let source = "# First line\r\n# Second line\r\nclass Foo\r\nend\r\n";
927
+ let mut indexer = RBSIndexer::new("file:///foo.rbs".to_string(), source);
928
+ indexer.index();
929
+ let context = LocalGraphTest::from_local_graph("file:///foo.rbs", indexer.local_graph());
930
+
931
+ assert_no_local_diagnostics!(&context);
932
+
933
+ assert_definition_at!(&context, "3:1-4:4", Class, |def| {
934
+ assert_def_name_eq!(&context, def, "Foo");
935
+ assert_def_comments_eq!(&context, def, ["# First line", "# Second line"]);
936
+ });
937
+ }
717
938
  }