rubydex 0.1.0.beta3-x86_64-linux → 0.1.0.beta6-x86_64-linux

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: 2de40c7335743a85b7d61d9a513d40b29fcecf62e19cfff8ef2637cd4297b6a8
4
- data.tar.gz: 99bed4d413ab7e1bd97f76965906037d1da29f12af8dba7f19f48d5665c73bc5
3
+ metadata.gz: ba2df1c014d93db599af99939667aa410786bae376f66caba253fa0cdc6cddb5
4
+ data.tar.gz: 2a6b68cf1658cb5f87225cff5a336565541325cd3356a34e96abd77f527789ae
5
5
  SHA512:
6
- metadata.gz: 29ef5b84c94af6eb1608d42acbc7a0c5c115fef6317645b369fca27c1c1714c64a21dba7a1b10f490939fbf547583fbed20db30d26e9ad4f42b9c8079c8b7e6c
7
- data.tar.gz: e4d9dce9acf24ef5c704dab14d89a82143c39e259e8923fc71830abe600b54240094a8e287aa647b62e419fac42db6e1c3f4b59d20b29498d1b5872865083dd5
6
+ metadata.gz: 5e407f9ade85f4febeadc490b3f306229f3197d323b7d404b0a9f7032b13b93eb78e4794037490ee2fa909a440302f16708437350c8ec946c4e64c5430ab3ec6
7
+ data.tar.gz: 595952a7249abe53c78b35196f8a03f5defe24fb997f9f67cb6fca8f22769d5e718d4795c3487d273f3e179eb5d1368a93294fd4fb6a0fde2b95e6ccc82eeb75
data/README.md CHANGED
@@ -11,13 +11,70 @@ of using the Ruby API:
11
11
  ```ruby
12
12
  # Create a new graph representing the current workspace
13
13
  graph = Rubydex::Graph.new
14
+ # Configuring graph LSP encoding
15
+ graph.encoding = "utf16"
14
16
  # Index the entire workspace with all dependencies
15
17
  graph.index_workspace
18
+ # Or index specific file paths
19
+ graph.index_all(["path/to/file.rb"])
16
20
  # Transform the initially collected information into its semantic understanding by running resolution
17
21
  graph.resolve
22
+ # Get all diagnostics acquired during the analysis
23
+ graph.diagnostics
18
24
 
19
- # Access the information as needed
20
- graph["Foo"]
25
+ # Iterating over graph nodes
26
+ graph.declarations
27
+ graph.documents
28
+ graph.constant_references
29
+ graph.method_references
30
+
31
+ # Analyzing require paths
32
+ graph.resolve_require_path("rails/engine", load_paths) # => document pointed by `rails/engine`
33
+ graph.require_paths(load_paths) # => array of all indexed require paths
34
+
35
+ # Querying
36
+ graph["Foo"] # Get declaration by fully qualified name
37
+ graph.search("Foo#b") # Name search
38
+ graph.resolve_constant("Bar", ["Foo", "Baz::Qux"]) # Resolve constant reference based on nesting
39
+
40
+ # Declarations
41
+ declaration = graph["Foo"]
42
+
43
+ # All declarations include
44
+ declaration.name
45
+ declaration.unqualified_name
46
+ declaration.definitions
47
+ declaration.owner
48
+
49
+ # Namespace declarations include
50
+ declaration.member("bar()")
51
+ declaration.member("@ivar")
52
+ declaration.singleton_class
53
+ declaration.ancestors
54
+ declaration.descendants
55
+
56
+ # Documents
57
+ document = graph.documents.first
58
+ document.uri
59
+ document.definitions # => list of definitions discovered in this document
60
+
61
+ # Definitions
62
+ definition = declaration.definitions.first
63
+ definition.location
64
+ definition.comments
65
+ definition.name
66
+ definition.deprecated?
67
+ definition.name_location
68
+
69
+ # Locations
70
+ location = definition.location
71
+ location.path
72
+
73
+ # Diagnostics
74
+ diagnostic = graph.diagnostics.first
75
+ diagnostic.rule
76
+ diagnostic.message
77
+ diagnostic.location
21
78
  ```
22
79
 
23
80
  ## Contributing
data/ext/rubydex/graph.c CHANGED
@@ -9,7 +9,6 @@
9
9
  #include "utils.h"
10
10
 
11
11
  static VALUE cGraph;
12
- static VALUE eIndexingError;
13
12
 
14
13
  // Free function for the custom Graph allocator. We always have to call into Rust to free data allocated by it
15
14
  static void graph_free(void *ptr) {
@@ -27,8 +26,8 @@ static VALUE rdxr_graph_alloc(VALUE klass) {
27
26
  return TypedData_Wrap_Struct(klass, &graph_type, graph);
28
27
  }
29
28
 
30
- // Graph#index_all: (Array[String] file_paths) -> nil
31
- // Raises IndexingError if anything failed during indexing
29
+ // Graph#index_all: (Array[String] file_paths) -> Array[String]
30
+ // Returns an array of IO error messages encountered during indexing
32
31
  static VALUE rdxr_graph_index_all(VALUE self, VALUE file_paths) {
33
32
  rdxi_check_array_of_strings(file_paths);
34
33
 
@@ -36,10 +35,12 @@ static VALUE rdxr_graph_index_all(VALUE self, VALUE file_paths) {
36
35
  size_t length = RARRAY_LEN(file_paths);
37
36
  char **converted_file_paths = rdxi_str_array_to_char(file_paths, length);
38
37
 
39
- // Get the underying graph pointer and then invoke the Rust index all implementation
38
+ // Get the underlying graph pointer and then invoke the Rust index all implementation
40
39
  void *graph;
41
40
  TypedData_Get_Struct(self, void *, &graph_type, graph);
42
- const char *error_messages = rdx_index_all(graph, (const char **)converted_file_paths, length);
41
+
42
+ size_t error_count = 0;
43
+ const char *const *errors = rdx_index_all(graph, (const char **)converted_file_paths, length, &error_count);
43
44
 
44
45
  // Free the converted file paths and allow the GC to collect them
45
46
  for (size_t i = 0; i < length; i++) {
@@ -47,12 +48,36 @@ static VALUE rdxr_graph_index_all(VALUE self, VALUE file_paths) {
47
48
  }
48
49
  free(converted_file_paths);
49
50
 
50
- // If indexing errors were returned, turn them into a Ruby string, call Rust to free the CString it allocated and
51
- // return the Ruby string
52
- if (error_messages != NULL) {
53
- VALUE error_string = rb_utf8_str_new_cstr(error_messages);
54
- free_c_string(error_messages);
55
- rb_raise(eIndexingError, "%s", StringValueCStr(error_string));
51
+ if (errors == NULL) {
52
+ return rb_ary_new();
53
+ }
54
+
55
+ VALUE array = rb_ary_new_capa((long)error_count);
56
+ for (size_t i = 0; i < error_count; i++) {
57
+ rb_ary_push(array, rb_utf8_str_new_cstr(errors[i]));
58
+ }
59
+
60
+ free_c_string_array(errors, error_count);
61
+ return array;
62
+ }
63
+
64
+ // Indexes a single source string in memory, dispatching to the appropriate indexer based on language_id
65
+ //
66
+ // Graph#index_source: (String uri, String source, String language_id) -> void
67
+ static VALUE rdxr_graph_index_source(VALUE self, VALUE uri, VALUE source, VALUE language_id) {
68
+ Check_Type(uri, T_STRING);
69
+ Check_Type(source, T_STRING);
70
+ Check_Type(language_id, T_STRING);
71
+
72
+ void *graph;
73
+ TypedData_Get_Struct(self, void *, &graph_type, graph);
74
+
75
+ const char *uri_str = StringValueCStr(uri);
76
+ const char *source_str = StringValueCStr(source);
77
+ const char *language_id_str = StringValueCStr(language_id);
78
+
79
+ if (!rdx_index_source(graph, uri_str, source_str, language_id_str)) {
80
+ rb_raise(rb_eArgError, "unsupported language_id `%s`", language_id_str);
56
81
  }
57
82
 
58
83
  return Qnil;
@@ -272,6 +297,25 @@ static VALUE rdxr_graph_method_references(VALUE self) {
272
297
  return self;
273
298
  }
274
299
 
300
+ // Graph#delete_document: (String uri) -> Document?
301
+ // Deletes a document and all of its definitions from the graph.
302
+ // Returns the removed Document or nil if it doesn't exist.
303
+ static VALUE rdxr_graph_delete_document(VALUE self, VALUE uri) {
304
+ Check_Type(uri, T_STRING);
305
+
306
+ void *graph;
307
+ TypedData_Get_Struct(self, void *, &graph_type, graph);
308
+ const uint64_t *uri_id = rdx_graph_delete_document(graph, StringValueCStr(uri));
309
+
310
+ if (uri_id == NULL) {
311
+ return Qnil;
312
+ }
313
+
314
+ VALUE argv[] = {self, ULL2NUM(*uri_id)};
315
+ free_u64(uri_id);
316
+ return rb_class_new_instance(2, argv, cDocument);
317
+ }
318
+
275
319
  // Graph#resolve: () -> self
276
320
  // Runs the resolver to compute declarations and ownership
277
321
  static VALUE rdxr_graph_resolve(VALUE self) {
@@ -281,7 +325,7 @@ static VALUE rdxr_graph_resolve(VALUE self) {
281
325
  return self;
282
326
  }
283
327
 
284
- // Graph#set_encoding: (String) -> void
328
+ // Graph#encoding=: (String) -> void
285
329
  // Sets the encoding used for transforming byte offsets into LSP code unit line/column positions
286
330
  static VALUE rdxr_graph_set_encoding(VALUE self, VALUE encoding) {
287
331
  Check_Type(encoding, T_STRING);
@@ -358,6 +402,38 @@ static VALUE rdxr_graph_resolve_require_path(VALUE self, VALUE require_path, VAL
358
402
  return rb_class_new_instance(2, argv, cDocument);
359
403
  }
360
404
 
405
+ // Graph#require_paths: (Array[String] load_path) -> Array[String]
406
+ // Returns all require paths for completion.
407
+ static VALUE rdxr_graph_require_paths(VALUE self, VALUE load_path) {
408
+ rdxi_check_array_of_strings(load_path);
409
+
410
+ void *graph;
411
+ TypedData_Get_Struct(self, void *, &graph_type, graph);
412
+
413
+ size_t paths_len = RARRAY_LEN(load_path);
414
+ char **converted_paths = rdxi_str_array_to_char(load_path, paths_len);
415
+
416
+ size_t out_count = 0;
417
+ const char *const *results = rdx_require_paths(graph, (const char **)converted_paths, paths_len, &out_count);
418
+
419
+ for (size_t i = 0; i < paths_len; i++) {
420
+ free(converted_paths[i]);
421
+ }
422
+ free(converted_paths);
423
+
424
+ if (results == NULL) {
425
+ return rb_ary_new();
426
+ }
427
+
428
+ VALUE array = rb_ary_new_capa((long)out_count);
429
+ for (size_t i = 0; i < out_count; i++) {
430
+ rb_ary_push(array, rb_utf8_str_new_cstr(results[i]));
431
+ }
432
+
433
+ free_c_string_array(results, out_count);
434
+ return array;
435
+ }
436
+
361
437
  // Graph#diagnostics -> Array[Rubydex::Diagnostic]
362
438
  static VALUE rdxr_graph_diagnostics(VALUE self) {
363
439
  void *graph;
@@ -392,12 +468,11 @@ static VALUE rdxr_graph_diagnostics(VALUE self) {
392
468
  }
393
469
 
394
470
  void rdxi_initialize_graph(VALUE mRubydex) {
395
- VALUE eRubydexError = rb_const_get(mRubydex, rb_intern("Error"));
396
- eIndexingError = rb_define_class_under(mRubydex, "IndexingError", eRubydexError);
397
-
398
471
  cGraph = rb_define_class_under(mRubydex, "Graph", rb_cObject);
399
472
  rb_define_alloc_func(cGraph, rdxr_graph_alloc);
400
473
  rb_define_method(cGraph, "index_all", rdxr_graph_index_all, 1);
474
+ rb_define_method(cGraph, "index_source", rdxr_graph_index_source, 3);
475
+ rb_define_method(cGraph, "delete_document", rdxr_graph_delete_document, 1);
401
476
  rb_define_method(cGraph, "resolve", rdxr_graph_resolve, 0);
402
477
  rb_define_method(cGraph, "resolve_constant", rdxr_graph_resolve_constant, 2);
403
478
  rb_define_method(cGraph, "declarations", rdxr_graph_declarations, 0);
@@ -407,6 +482,7 @@ void rdxi_initialize_graph(VALUE mRubydex) {
407
482
  rb_define_method(cGraph, "diagnostics", rdxr_graph_diagnostics, 0);
408
483
  rb_define_method(cGraph, "[]", rdxr_graph_aref, 1);
409
484
  rb_define_method(cGraph, "search", rdxr_graph_search, 1);
410
- rb_define_method(cGraph, "set_encoding", rdxr_graph_set_encoding, 1);
485
+ rb_define_method(cGraph, "encoding=", rdxr_graph_set_encoding, 1);
411
486
  rb_define_method(cGraph, "resolve_require_path", rdxr_graph_resolve_require_path, 2);
487
+ rb_define_method(cGraph, "require_paths", rdxr_graph_require_paths, 1);
412
488
  }
Binary file
Binary file
Binary file
Binary file
data/lib/rubydex/graph.rb CHANGED
@@ -5,40 +5,68 @@ module Rubydex
5
5
  #
6
6
  # Note: this class is partially defined in C to integrate with the Rust backend
7
7
  class Graph
8
+ IGNORED_DIRECTORIES = [
9
+ ".bundle",
10
+ ".git",
11
+ ".github",
12
+ "node_modules",
13
+ "tmp",
14
+ ].freeze
15
+
8
16
  #: (?workspace_path: String) -> void
9
17
  def initialize(workspace_path: Dir.pwd)
10
18
  @workspace_path = workspace_path
11
19
  end
12
20
 
13
21
  # Index all files and dependencies of the workspace that exists in `@workspace_path`
14
- #: -> String?
22
+ #: -> Array[String]
15
23
  def index_workspace
16
- paths = Dir.glob("#{@workspace_path}/**/*.rb")
17
- paths.concat(
18
- workspace_dependency_paths.flat_map do |path|
19
- Dir.glob("#{path}/**/*.rb")
20
- end,
21
- )
22
- index_all(paths)
24
+ index_all(workspace_paths)
25
+ end
26
+
27
+ # Returns all workspace paths that should be indexed, excluding directories that we don't need to descend into such
28
+ # as `.git`, `node_modules`. Also includes any top level Ruby files
29
+ #
30
+ #: -> Array[String]
31
+ def workspace_paths
32
+ paths = []
33
+
34
+ Dir.each_child(@workspace_path) do |entry|
35
+ full_path = File.join(@workspace_path, entry)
36
+
37
+ if File.directory?(full_path)
38
+ paths << full_path unless IGNORED_DIRECTORIES.include?(entry)
39
+ elsif File.extname(entry) == ".rb"
40
+ paths << full_path
41
+ end
42
+ end
43
+
44
+ add_workspace_dependency_paths(paths)
45
+ paths.uniq!
46
+ paths
23
47
  end
24
48
 
25
49
  private
26
50
 
27
51
  # Gathers the paths we have to index for all workspace dependencies
28
- def workspace_dependency_paths
52
+ #: (Array[String]) -> void
53
+ def add_workspace_dependency_paths(paths)
29
54
  specs = Bundler.locked_gems&.specs
30
- return [] unless specs
55
+ return unless specs
31
56
 
32
- paths = specs.filter_map do |lazy_spec|
57
+ specs.each do |lazy_spec|
33
58
  spec = Gem::Specification.find_by_name(lazy_spec.name)
34
- spec.require_paths.map { |path| File.join(spec.full_gem_path, path) }
59
+ spec.require_paths.each do |path|
60
+ # For native extensions, RubyGems inserts an absolute require path pointing to
61
+ # `gems/some-gem-1.0.0/extensions`. Those paths don't actually include any Ruby files inside, so we can skip
62
+ # descending them
63
+ next if File.absolute_path?(path)
64
+
65
+ paths << File.join(spec.full_gem_path, path)
66
+ end
35
67
  rescue Gem::MissingSpecError
36
68
  nil
37
69
  end
38
-
39
- paths.flatten!
40
- paths.uniq!
41
- paths
42
70
  end
43
71
  end
44
72
  end
Binary file
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubydex
4
- VERSION = "0.1.0.beta3"
4
+ VERSION = "0.1.0.beta6"
5
5
  end
@@ -0,0 +1,239 @@
1
+ //! Visit the RBS AST and create type definitions.
2
+
3
+ use ruby_rbs::node::{self, ModuleNode, Node, TypeNameNode, Visit};
4
+
5
+ use crate::diagnostic::Rule;
6
+ use crate::indexing::local_graph::LocalGraph;
7
+ use crate::model::comment::Comment;
8
+ use crate::model::definitions::{Definition, DefinitionFlags, ModuleDefinition};
9
+ use crate::model::document::Document;
10
+ use crate::model::ids::{DefinitionId, NameId, UriId};
11
+ use crate::model::name::{Name, ParentScope};
12
+ use crate::offset::Offset;
13
+
14
+ pub struct RBSIndexer<'a> {
15
+ uri_id: UriId,
16
+ local_graph: LocalGraph,
17
+ source: &'a str,
18
+ nesting_stack: Vec<DefinitionId>,
19
+ }
20
+
21
+ impl<'a> RBSIndexer<'a> {
22
+ #[must_use]
23
+ pub fn new(uri: String, source: &'a str) -> Self {
24
+ let uri_id = UriId::from(&uri);
25
+ let local_graph = LocalGraph::new(uri_id, Document::new(uri, source));
26
+
27
+ Self {
28
+ uri_id,
29
+ local_graph,
30
+ source,
31
+ nesting_stack: Vec::new(),
32
+ }
33
+ }
34
+
35
+ #[must_use]
36
+ pub fn local_graph(self) -> LocalGraph {
37
+ self.local_graph
38
+ }
39
+
40
+ pub fn index(&mut self) {
41
+ let Ok(signature) = node::parse(self.source.as_bytes()) else {
42
+ self.local_graph.add_diagnostic(
43
+ Rule::ParseError,
44
+ Offset::new(0, 0),
45
+ "Failed to parse RBS document".to_string(),
46
+ );
47
+ return;
48
+ };
49
+
50
+ self.visit(&signature.as_node());
51
+ }
52
+
53
+ fn bytes_to_string(name: &[u8]) -> String {
54
+ String::from_utf8_lossy(name).into_owned()
55
+ }
56
+
57
+ /// Converts an RBS `TypeNameNode` into a rubydex `NameId`.
58
+ ///
59
+ /// Walks the namespace path (e.g. `Foo::Bar` in `Foo::Bar::Baz`) to build
60
+ /// a `ParentScope` chain, then creates the final `Name` for the leaf segment.
61
+ fn index_type_name(&mut self, type_name: &TypeNameNode, nesting_name_id: Option<NameId>) -> NameId {
62
+ let namespace = type_name.namespace();
63
+
64
+ let mut parent_scope = if namespace.absolute() {
65
+ ParentScope::TopLevel
66
+ } else {
67
+ ParentScope::None
68
+ };
69
+
70
+ for path_node in namespace.path().iter() {
71
+ let Node::Symbol(symbol) = path_node else {
72
+ continue;
73
+ };
74
+ parent_scope = ParentScope::Some(self.intern_name(symbol.name(), parent_scope, nesting_name_id));
75
+ }
76
+
77
+ self.intern_name(type_name.name().name(), parent_scope, nesting_name_id)
78
+ }
79
+
80
+ fn intern_name(&mut self, name_bytes: &[u8], parent_scope: ParentScope, nesting_name_id: Option<NameId>) -> NameId {
81
+ let string_id = self.local_graph.intern_string(Self::bytes_to_string(name_bytes));
82
+ self.local_graph
83
+ .add_name(Name::new(string_id, parent_scope, nesting_name_id))
84
+ }
85
+
86
+ fn parent_lexical_scope_id(&self) -> Option<DefinitionId> {
87
+ self.nesting_stack.last().copied()
88
+ }
89
+
90
+ fn add_member_to_current_lexical_scope(&mut self, owner_id: DefinitionId, member_id: DefinitionId) {
91
+ let owner = self
92
+ .local_graph
93
+ .get_definition_mut(owner_id)
94
+ .expect("owner definition should exist");
95
+
96
+ match owner {
97
+ Definition::Module(module) => module.add_member(member_id),
98
+ Definition::Class(class) => class.add_member(member_id),
99
+ _ => unreachable!("RBS nesting stack only contains modules/classes"),
100
+ }
101
+ }
102
+ }
103
+
104
+ impl Visit for RBSIndexer<'_> {
105
+ fn visit_module_node(&mut self, module_node: &ModuleNode) {
106
+ let lexical_nesting_id = self.parent_lexical_scope_id();
107
+ let nesting_name_id = lexical_nesting_id.map(|id| {
108
+ let owner = self
109
+ .local_graph
110
+ .definitions()
111
+ .get(&id)
112
+ .expect("owner definition should exist");
113
+ *owner.name_id().expect("nesting definition should have a name")
114
+ });
115
+
116
+ let type_name = module_node.name();
117
+ let name_id = self.index_type_name(&type_name, nesting_name_id);
118
+ let offset = Offset::from_rbs_location(&module_node.location());
119
+ let name_offset = Offset::from_rbs_location(&type_name.name().location());
120
+
121
+ let comments: Vec<_> = module_node
122
+ .comment()
123
+ .into_iter()
124
+ .map(|comment| {
125
+ let text = Self::bytes_to_string(comment.string().as_bytes());
126
+ Comment::new(Offset::from_rbs_location(&comment.location()), text)
127
+ })
128
+ .collect();
129
+
130
+ let definition = Definition::Module(Box::new(ModuleDefinition::new(
131
+ name_id,
132
+ self.uri_id,
133
+ offset,
134
+ name_offset,
135
+ comments,
136
+ DefinitionFlags::empty(),
137
+ lexical_nesting_id,
138
+ )));
139
+
140
+ let definition_id = self.local_graph.add_definition(definition);
141
+ if let Some(id) = lexical_nesting_id {
142
+ self.add_member_to_current_lexical_scope(id, definition_id);
143
+ }
144
+
145
+ self.nesting_stack.push(definition_id);
146
+
147
+ for member in module_node.members().iter() {
148
+ self.visit(&member);
149
+ }
150
+
151
+ self.nesting_stack.pop();
152
+ }
153
+ }
154
+
155
+ #[cfg(test)]
156
+ mod tests {
157
+ use crate::test_utils::LocalGraphTest;
158
+ use crate::{
159
+ assert_def_name_eq, assert_def_name_offset_eq, assert_definition_at, assert_local_diagnostics_eq,
160
+ assert_no_local_diagnostics,
161
+ };
162
+
163
+ fn index_source(source: &str) -> LocalGraphTest {
164
+ LocalGraphTest::new_rbs("file:///foo.rbs", source)
165
+ }
166
+
167
+ #[test]
168
+ fn index_source_with_errors() {
169
+ let context = index_source("module");
170
+
171
+ assert_local_diagnostics_eq!(&context, ["parse-error: Failed to parse RBS document (1:1-1:1)"]);
172
+
173
+ assert!(context.graph().definitions().is_empty());
174
+ }
175
+
176
+ #[test]
177
+ fn index_module_node() {
178
+ let context = index_source({
179
+ "
180
+ module Foo
181
+ module Bar
182
+ end
183
+ end
184
+ "
185
+ });
186
+
187
+ assert_no_local_diagnostics!(&context);
188
+ assert_eq!(context.graph().definitions().len(), 2);
189
+
190
+ assert_definition_at!(&context, "1:1-4:4", Module, |def| {
191
+ assert_def_name_eq!(&context, def, "Foo");
192
+ assert_def_name_offset_eq!(&context, def, "1:8-1:11");
193
+ assert_eq!(1, def.members().len());
194
+ assert!(def.lexical_nesting_id().is_none());
195
+ });
196
+
197
+ assert_definition_at!(&context, "2:3-3:6", Module, |def| {
198
+ assert_def_name_eq!(&context, def, "Bar");
199
+ assert_def_name_offset_eq!(&context, def, "2:10-2:13");
200
+
201
+ assert_definition_at!(&context, "1:1-4:4", Module, |parent_nesting| {
202
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
203
+ assert_eq!(parent_nesting.members()[0], def.id());
204
+ });
205
+ });
206
+ }
207
+
208
+ #[test]
209
+ fn index_module_node_with_qualified_name() {
210
+ let context = index_source({
211
+ "
212
+ module Foo
213
+ module Bar::Baz
214
+ end
215
+ end
216
+ "
217
+ });
218
+
219
+ assert_no_local_diagnostics!(&context);
220
+ assert_eq!(context.graph().definitions().len(), 2);
221
+
222
+ assert_definition_at!(&context, "1:1-4:4", Module, |def| {
223
+ assert_def_name_eq!(&context, def, "Foo");
224
+ assert_def_name_offset_eq!(&context, def, "1:8-1:11");
225
+ assert_eq!(1, def.members().len());
226
+ assert!(def.lexical_nesting_id().is_none());
227
+ });
228
+
229
+ assert_definition_at!(&context, "2:3-3:6", Module, |def| {
230
+ assert_def_name_eq!(&context, def, "Bar::Baz");
231
+ assert_def_name_offset_eq!(&context, def, "2:15-2:18");
232
+
233
+ assert_definition_at!(&context, "1:1-4:4", Module, |parent_nesting| {
234
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
235
+ assert_eq!(parent_nesting.members()[0], def.id());
236
+ });
237
+ });
238
+ }
239
+ }