rubydex 0.2.9 → 0.3.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.
- checksums.yaml +4 -4
- data/README.md +69 -3
- data/THIRD_PARTY_LICENSES.html +34 -1
- data/exe/rdx +131 -55
- data/ext/rubydex/declaration.c +1 -1
- data/ext/rubydex/definition.c +32 -4
- data/ext/rubydex/graph.c +14 -3
- data/ext/rubydex/query.c +105 -0
- data/ext/rubydex/query.h +8 -0
- data/ext/rubydex/reference.c +60 -0
- data/ext/rubydex/rubydex.c +2 -0
- data/ext/rubydex/utils.c +12 -0
- data/ext/rubydex/utils.h +5 -0
- data/lib/rubydex/version.rb +1 -1
- data/rbi/rubydex.rbi +22 -0
- data/rust/Cargo.lock +7 -0
- data/rust/rubydex/Cargo.toml +1 -0
- data/rust/rubydex/benches/graph_memory.rs +22 -4
- data/rust/rubydex/src/compile_assertions.rs +15 -0
- data/rust/rubydex/src/diagnostic.rs +1 -1
- data/rust/rubydex/src/indexing/rbs_indexer.rs +14 -2
- data/rust/rubydex/src/indexing/ruby_indexer.rs +49 -58
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +72 -16
- data/rust/rubydex/src/main.rs +2 -124
- data/rust/rubydex/src/model/declaration.rs +0 -11
- data/rust/rubydex/src/model/definitions.rs +27 -26
- data/rust/rubydex/src/model/document.rs +43 -7
- data/rust/rubydex/src/model/graph.rs +40 -28
- data/rust/rubydex/src/model/id.rs +55 -0
- data/rust/rubydex/src/model/ids.rs +21 -9
- data/rust/rubydex/src/model/name.rs +35 -7
- data/rust/rubydex/src/model/references.rs +16 -13
- data/rust/rubydex/src/operation/ruby_builder.rs +48 -59
- data/rust/rubydex/src/query/cypher/schema.rs +790 -0
- data/rust/rubydex/src/query/cypher/schema_info.rs +161 -0
- data/rust/rubydex/src/query/cypher/tests.rs +228 -0
- data/rust/rubydex/src/query/cypher.rs +57 -0
- data/rust/rubydex/src/query.rs +2 -0
- data/rust/rubydex/src/resolution.rs +248 -227
- data/rust/rubydex/src/resolution_tests.rs +263 -65
- data/rust/rubydex-sys/src/declaration_api.rs +6 -3
- data/rust/rubydex-sys/src/definition_api.rs +27 -7
- data/rust/rubydex-sys/src/graph_api.rs +158 -14
- data/rust/rubydex-sys/src/reference_api.rs +58 -12
- metadata +8 -2
data/ext/rubydex/reference.c
CHANGED
|
@@ -17,6 +17,8 @@ VALUE cUnresolvedConstantReference;
|
|
|
17
17
|
VALUE cResolvedConstantReference;
|
|
18
18
|
VALUE cMethodReference;
|
|
19
19
|
|
|
20
|
+
static VALUE cDocument;
|
|
21
|
+
|
|
20
22
|
/*
|
|
21
23
|
* call-seq:
|
|
22
24
|
* name -> String
|
|
@@ -28,6 +30,9 @@ static VALUE rdxr_constant_reference_name(VALUE self) {
|
|
|
28
30
|
void *graph = rdxi_graph_from_handle(self, &data);
|
|
29
31
|
|
|
30
32
|
const char *name = rdx_constant_reference_name(graph, data->id);
|
|
33
|
+
if (name == NULL) {
|
|
34
|
+
rb_raise(rb_eRuntimeError, "Constant reference must exist for a valid id");
|
|
35
|
+
}
|
|
31
36
|
return rdxi_owned_c_string_to_ruby(name);
|
|
32
37
|
}
|
|
33
38
|
|
|
@@ -42,11 +47,35 @@ static VALUE rdxr_constant_reference_location(VALUE self) {
|
|
|
42
47
|
void *graph = rdxi_graph_from_handle(self, &data);
|
|
43
48
|
|
|
44
49
|
Location *loc = rdx_constant_reference_location(graph, data->id);
|
|
50
|
+
if (loc == NULL) {
|
|
51
|
+
rb_raise(rb_eRuntimeError, "Constant reference must exist for a valid id");
|
|
52
|
+
}
|
|
45
53
|
VALUE location = rdxi_build_location_value(loc);
|
|
46
54
|
rdx_location_free(loc);
|
|
47
55
|
return location;
|
|
48
56
|
}
|
|
49
57
|
|
|
58
|
+
/*
|
|
59
|
+
* call-seq:
|
|
60
|
+
* document -> Rubydex::Document
|
|
61
|
+
*
|
|
62
|
+
* Returns the document this constant reference belongs to.
|
|
63
|
+
*/
|
|
64
|
+
static VALUE rdxr_constant_reference_document(VALUE self) {
|
|
65
|
+
HandleData *data;
|
|
66
|
+
void *graph = rdxi_graph_from_handle(self, &data);
|
|
67
|
+
|
|
68
|
+
const uint64_t *uri_id = rdx_constant_reference_document(graph, data->id);
|
|
69
|
+
if (uri_id == NULL) {
|
|
70
|
+
rb_raise(rb_eRuntimeError, "Constant reference not found");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
VALUE argv[] = {data->graph_obj, ULL2NUM(*uri_id)};
|
|
74
|
+
free_u64(uri_id);
|
|
75
|
+
|
|
76
|
+
return rb_class_new_instance(2, argv, cDocument);
|
|
77
|
+
}
|
|
78
|
+
|
|
50
79
|
/*
|
|
51
80
|
* call-seq:
|
|
52
81
|
* name -> String
|
|
@@ -58,6 +87,9 @@ static VALUE rdxr_method_reference_name(VALUE self) {
|
|
|
58
87
|
void *graph = rdxi_graph_from_handle(self, &data);
|
|
59
88
|
|
|
60
89
|
const char *name = rdx_method_reference_name(graph, data->id);
|
|
90
|
+
if (name == NULL) {
|
|
91
|
+
rb_raise(rb_eRuntimeError, "Method reference must exist for a valid id");
|
|
92
|
+
}
|
|
61
93
|
return rdxi_owned_c_string_to_ruby(name);
|
|
62
94
|
}
|
|
63
95
|
|
|
@@ -72,6 +104,9 @@ static VALUE rdxr_method_reference_location(VALUE self) {
|
|
|
72
104
|
void *graph = rdxi_graph_from_handle(self, &data);
|
|
73
105
|
|
|
74
106
|
Location *loc = rdx_method_reference_location(graph, data->id);
|
|
107
|
+
if (loc == NULL) {
|
|
108
|
+
rb_raise(rb_eRuntimeError, "Method reference must exist for a valid id");
|
|
109
|
+
}
|
|
75
110
|
VALUE location = rdxi_build_location_value(loc);
|
|
76
111
|
rdx_location_free(loc);
|
|
77
112
|
return location;
|
|
@@ -100,6 +135,27 @@ static VALUE rdxr_method_reference_receiver(VALUE self) {
|
|
|
100
135
|
return rb_class_new_instance(2, argv, decl_class);
|
|
101
136
|
}
|
|
102
137
|
|
|
138
|
+
/*
|
|
139
|
+
* call-seq:
|
|
140
|
+
* document -> Rubydex::Document
|
|
141
|
+
*
|
|
142
|
+
* Returns the document this method reference belongs to.
|
|
143
|
+
*/
|
|
144
|
+
static VALUE rdxr_method_reference_document(VALUE self) {
|
|
145
|
+
HandleData *data;
|
|
146
|
+
void *graph = rdxi_graph_from_handle(self, &data);
|
|
147
|
+
|
|
148
|
+
const uint64_t *uri_id = rdx_method_reference_document(graph, data->id);
|
|
149
|
+
if (uri_id == NULL) {
|
|
150
|
+
rb_raise(rb_eRuntimeError, "Method reference not found");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
VALUE argv[] = {data->graph_obj, ULL2NUM(*uri_id)};
|
|
154
|
+
free_u64(uri_id);
|
|
155
|
+
|
|
156
|
+
return rb_class_new_instance(2, argv, cDocument);
|
|
157
|
+
}
|
|
158
|
+
|
|
103
159
|
/*
|
|
104
160
|
* call-seq:
|
|
105
161
|
* declaration -> Rubydex::Declaration
|
|
@@ -123,6 +179,8 @@ static VALUE rdxr_resolved_constant_reference_declaration(VALUE self) {
|
|
|
123
179
|
}
|
|
124
180
|
|
|
125
181
|
void rdxi_initialize_reference(VALUE mRubydex) {
|
|
182
|
+
cDocument = rb_const_get(mRubydex, rb_intern("Document"));
|
|
183
|
+
|
|
126
184
|
cReference = rb_define_class_under(mRubydex, "Reference", rb_cObject);
|
|
127
185
|
rb_define_alloc_func(cReference, rdxr_handle_alloc);
|
|
128
186
|
rb_define_method(cReference, "initialize", rdxr_handle_initialize, 2);
|
|
@@ -132,6 +190,7 @@ void rdxi_initialize_reference(VALUE mRubydex) {
|
|
|
132
190
|
rb_define_alloc_func(cConstantReference, rdxr_handle_alloc);
|
|
133
191
|
rb_define_method(cConstantReference, "initialize", rdxr_handle_initialize, 2);
|
|
134
192
|
rb_define_method(cConstantReference, "location", rdxr_constant_reference_location, 0);
|
|
193
|
+
rb_define_method(cConstantReference, "document", rdxr_constant_reference_document, 0);
|
|
135
194
|
rb_funcall(rb_singleton_class(cConstantReference), rb_intern("private"), 1, ID2SYM(rb_intern("new")));
|
|
136
195
|
|
|
137
196
|
cUnresolvedConstantReference = rb_define_class_under(mRubydex, "UnresolvedConstantReference", cConstantReference);
|
|
@@ -148,4 +207,5 @@ void rdxi_initialize_reference(VALUE mRubydex) {
|
|
|
148
207
|
rb_define_method(cMethodReference, "name", rdxr_method_reference_name, 0);
|
|
149
208
|
rb_define_method(cMethodReference, "location", rdxr_method_reference_location, 0);
|
|
150
209
|
rb_define_method(cMethodReference, "receiver", rdxr_method_reference_receiver, 0);
|
|
210
|
+
rb_define_method(cMethodReference, "document", rdxr_method_reference_document, 0);
|
|
151
211
|
}
|
data/ext/rubydex/rubydex.c
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include "document.h"
|
|
5
5
|
#include "graph.h"
|
|
6
6
|
#include "location.h"
|
|
7
|
+
#include "query.h"
|
|
7
8
|
#include "reference.h"
|
|
8
9
|
#include "signature.h"
|
|
9
10
|
|
|
@@ -19,6 +20,7 @@ void Init_rubydex(void) {
|
|
|
19
20
|
*/
|
|
20
21
|
mRubydex = rb_define_module("Rubydex");
|
|
21
22
|
rdxi_initialize_graph(mRubydex);
|
|
23
|
+
rdxi_initialize_query(mRubydex);
|
|
22
24
|
rdxi_initialize_declaration(mRubydex);
|
|
23
25
|
rdxi_initialize_document(mRubydex);
|
|
24
26
|
rdxi_initialize_definition(mRubydex);
|
data/ext/rubydex/utils.c
CHANGED
|
@@ -52,6 +52,18 @@ VALUE rdxi_owned_c_string_to_ruby(const char *string) {
|
|
|
52
52
|
return value;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// Coerce an optional String, Symbol, or nil to a C string, defaulting to `default_value` for nil.
|
|
56
|
+
const char *rdxi_symbol_or_string_cstr(VALUE value, const char *default_value) {
|
|
57
|
+
if (NIL_P(value)) {
|
|
58
|
+
return default_value;
|
|
59
|
+
}
|
|
60
|
+
if (RB_TYPE_P(value, T_SYMBOL)) {
|
|
61
|
+
value = rb_sym2str(value);
|
|
62
|
+
}
|
|
63
|
+
Check_Type(value, T_STRING);
|
|
64
|
+
return StringValueCStr(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
55
67
|
// Yield body for iterating over declarations
|
|
56
68
|
VALUE rdxi_declarations_yield(VALUE args) {
|
|
57
69
|
VALUE self = rb_ary_entry(args, 0);
|
data/ext/rubydex/utils.h
CHANGED
|
@@ -17,6 +17,11 @@ void rdxi_check_array_of_strings(VALUE array);
|
|
|
17
17
|
// Returns nil when the Rust side returned NULL.
|
|
18
18
|
VALUE rdxi_owned_c_string_to_ruby(const char *string);
|
|
19
19
|
|
|
20
|
+
// Coerce an optional String, Symbol, or nil to a C string, returning `default_value` when the value
|
|
21
|
+
// is nil. A non-nil value must be a String or Symbol, otherwise a `TypeError` is raised. The
|
|
22
|
+
// returned pointer is owned by the Ruby VALUE and is only valid while it stays live.
|
|
23
|
+
const char *rdxi_symbol_or_string_cstr(VALUE value, const char *default_value);
|
|
24
|
+
|
|
20
25
|
// Yield body for iterating over declarations
|
|
21
26
|
VALUE rdxi_declarations_yield(VALUE args);
|
|
22
27
|
|
data/lib/rubydex/version.rb
CHANGED
data/rbi/rubydex.rbi
CHANGED
|
@@ -20,6 +20,9 @@ class Rubydex::ConstantReference < Rubydex::Reference
|
|
|
20
20
|
sig { returns(Rubydex::Location) }
|
|
21
21
|
def location; end
|
|
22
22
|
|
|
23
|
+
sig { returns(Rubydex::Document) }
|
|
24
|
+
def document; end
|
|
25
|
+
|
|
23
26
|
class << self
|
|
24
27
|
private
|
|
25
28
|
|
|
@@ -158,6 +161,9 @@ class Rubydex::Definition
|
|
|
158
161
|
sig { returns(T::Array[Rubydex::Definition]) }
|
|
159
162
|
def lexical_nesting; end
|
|
160
163
|
|
|
164
|
+
sig { returns(Rubydex::Document) }
|
|
165
|
+
def document; end
|
|
166
|
+
|
|
161
167
|
class << self
|
|
162
168
|
private
|
|
163
169
|
|
|
@@ -278,6 +284,19 @@ end
|
|
|
278
284
|
|
|
279
285
|
class Rubydex::IntegrityFailure < Rubydex::Failure; end
|
|
280
286
|
|
|
287
|
+
class Rubydex::Query
|
|
288
|
+
class << self
|
|
289
|
+
sig { params(query: String).returns(Rubydex::Query) }
|
|
290
|
+
def parse(query); end
|
|
291
|
+
|
|
292
|
+
sig { params(format: T.any(String, Symbol)).returns(String) }
|
|
293
|
+
def schema(format = :table); end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
sig { params(graph: Rubydex::Graph, format: T.any(String, Symbol)).returns(String) }
|
|
297
|
+
def render(graph, format = :table); end
|
|
298
|
+
end
|
|
299
|
+
|
|
281
300
|
class Rubydex::Graph
|
|
282
301
|
sig { params(workspace_path: T.nilable(String)).void }
|
|
283
302
|
def initialize(workspace_path: nil); end
|
|
@@ -502,6 +521,9 @@ class Rubydex::MethodReference < Rubydex::Reference
|
|
|
502
521
|
|
|
503
522
|
sig { returns(T.nilable(Rubydex::Declaration)) }
|
|
504
523
|
def receiver; end
|
|
524
|
+
|
|
525
|
+
sig { returns(Rubydex::Document) }
|
|
526
|
+
def document; end
|
|
505
527
|
end
|
|
506
528
|
|
|
507
529
|
class Rubydex::Reference
|
data/rust/Cargo.lock
CHANGED
|
@@ -260,6 +260,12 @@ version = "0.8.21"
|
|
|
260
260
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
261
261
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
|
262
262
|
|
|
263
|
+
[[package]]
|
|
264
|
+
name = "cypher-parser"
|
|
265
|
+
version = "0.2.0"
|
|
266
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
267
|
+
checksum = "fed0d1e561d51e651bdf70f8439da293fd0f1fe34d8431059061eadaefc7abb1"
|
|
268
|
+
|
|
263
269
|
[[package]]
|
|
264
270
|
name = "difflib"
|
|
265
271
|
version = "0.4.0"
|
|
@@ -763,6 +769,7 @@ dependencies = [
|
|
|
763
769
|
"crossbeam-channel",
|
|
764
770
|
"crossbeam-deque",
|
|
765
771
|
"crossbeam-utils",
|
|
772
|
+
"cypher-parser",
|
|
766
773
|
"glob",
|
|
767
774
|
"libc",
|
|
768
775
|
"line-index",
|
data/rust/rubydex/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#[cfg(all(feature = "jemalloc", not(target_os = "windows")))]
|
|
2
2
|
mod imp {
|
|
3
|
-
use std::
|
|
3
|
+
use std::time::Instant;
|
|
4
4
|
|
|
5
5
|
use rubydex::{
|
|
6
6
|
indexing::{self, IndexerBackend},
|
|
@@ -18,13 +18,31 @@ mod imp {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
pub fn run() {
|
|
21
|
-
let
|
|
22
|
-
|
|
23
|
-
let
|
|
21
|
+
let mut args = std::env::args().skip(1);
|
|
22
|
+
|
|
23
|
+
let workspace = args
|
|
24
|
+
.next()
|
|
25
|
+
.expect("incorrect usage of cargo bench --bench graph_memory. Please use `utils/bench-graph-memory` instead of invoking this benchmark directly.");
|
|
26
|
+
let workspace_path = std::fs::canonicalize(&workspace).expect("the workspace path must exist");
|
|
27
|
+
assert!(workspace_path.is_dir(), "the workspace path must be a directory");
|
|
24
28
|
|
|
25
29
|
let mut graph = Graph::new();
|
|
30
|
+
graph.set_workspace_path(workspace_path);
|
|
31
|
+
|
|
32
|
+
if let Err(error) = graph.load_config(None) {
|
|
33
|
+
eprintln!("{error}");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let time = Instant::now();
|
|
37
|
+
|
|
38
|
+
let (file_paths, _) = listing::collect_file_paths(args.collect(), &graph.excluded_patterns());
|
|
39
|
+
println!("Listing {:.2}s", time.elapsed().as_secs_f64());
|
|
40
|
+
|
|
26
41
|
let _ = indexing::index_files(&mut graph, file_paths, IndexerBackend::RubyIndexer);
|
|
42
|
+
println!("Indexing {:.2}s", time.elapsed().as_secs_f64());
|
|
43
|
+
|
|
27
44
|
Resolver::new(&mut graph).resolve();
|
|
45
|
+
println!("Resolution {:.2}s", time.elapsed().as_secs_f64());
|
|
28
46
|
|
|
29
47
|
// Compare the total memory used in the allocator before and after dropping the graph
|
|
30
48
|
let before_drop = allocated_bytes();
|
|
@@ -11,3 +11,18 @@ macro_rules! assert_mem_size {
|
|
|
11
11
|
const _: [(); $size] = [(); std::mem::size_of::<$struct>()];
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
/// Asserts at compile time that a type is `Send + Sync`.
|
|
16
|
+
///
|
|
17
|
+
/// The `Graph` is shared across Ractors/threads behind a `RwLock`, so it must stay
|
|
18
|
+
/// `Send + Sync`. This assertion fails the build if a field that breaks either trait
|
|
19
|
+
/// is ever added.
|
|
20
|
+
#[macro_export]
|
|
21
|
+
macro_rules! assert_send_sync {
|
|
22
|
+
($struct:ident) => {
|
|
23
|
+
const _: fn() = || {
|
|
24
|
+
fn assert_send_sync<T: ?Sized + Send + Sync>() {}
|
|
25
|
+
assert_send_sync::<$struct>();
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -80,7 +80,16 @@ impl<'a> RBSIndexer<'a> {
|
|
|
80
80
|
let Node::Symbol(symbol) = path_node else {
|
|
81
81
|
continue;
|
|
82
82
|
};
|
|
83
|
-
|
|
83
|
+
let name_id = self.intern_name(&symbol, parent_scope, nesting_name_id);
|
|
84
|
+
|
|
85
|
+
// Emit a constant reference for each parent-scope segment so it gets its own resolution
|
|
86
|
+
// unit, mirroring the Ruby indexer (see `index_constant_reference`). Otherwise the
|
|
87
|
+
// parent scope is an orphan name that never resolves, leaving qualified references stuck.
|
|
88
|
+
let offset = Offset::from_rbs_location(&symbol.location());
|
|
89
|
+
self.local_graph
|
|
90
|
+
.add_constant_reference(ConstantReference::new(name_id, self.uri_id, offset));
|
|
91
|
+
|
|
92
|
+
parent_scope = ParentScope::Some(name_id);
|
|
84
93
|
}
|
|
85
94
|
|
|
86
95
|
self.intern_name(&type_name.name(), parent_scope, nesting_name_id)
|
|
@@ -486,7 +495,10 @@ impl Visit for RBSIndexer<'_> {
|
|
|
486
495
|
self.uri_id,
|
|
487
496
|
offset,
|
|
488
497
|
comments,
|
|
489
|
-
|
|
498
|
+
// RBS establishes that the constant exists, but its value type is intentionally not
|
|
499
|
+
// represented in the graph. Treat it like a dynamic Ruby assignment so resolution
|
|
500
|
+
// may promote it when a namespace or singleton receiver is required.
|
|
501
|
+
Self::flags(&constant_node.annotations()) | DefinitionFlags::PROMOTABLE,
|
|
490
502
|
lexical_nesting_id,
|
|
491
503
|
)));
|
|
492
504
|
|
|
@@ -386,21 +386,8 @@ impl<'a> RubyIndexer<'a> {
|
|
|
386
386
|
{
|
|
387
387
|
if let Some(arguments) = node.arguments() {
|
|
388
388
|
for argument in &arguments.arguments() {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
let symbol = argument.as_symbol_node().unwrap();
|
|
392
|
-
|
|
393
|
-
if let Some(value_loc) = symbol.value_loc() {
|
|
394
|
-
let name = Self::location_to_string(&value_loc);
|
|
395
|
-
f(name, value_loc);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
ruby_prism::Node::StringNode { .. } => {
|
|
399
|
-
let string = argument.as_string_node().unwrap();
|
|
400
|
-
let name = String::from_utf8_lossy(string.unescaped()).to_string();
|
|
401
|
-
f(name, argument.location());
|
|
402
|
-
}
|
|
403
|
-
_ => {}
|
|
389
|
+
if let Some((name, location)) = Self::extract_literal_name(&argument) {
|
|
390
|
+
f(name, location);
|
|
404
391
|
}
|
|
405
392
|
}
|
|
406
393
|
}
|
|
@@ -1193,10 +1180,11 @@ impl<'a> RubyIndexer<'a> {
|
|
|
1193
1180
|
|
|
1194
1181
|
fn handle_constant_visibility(&mut self, node: &ruby_prism::CallNode, visibility: Visibility) {
|
|
1195
1182
|
let receiver = node.receiver();
|
|
1183
|
+
let call_name = String::from_utf8_lossy(node.name().as_slice());
|
|
1196
1184
|
|
|
1197
|
-
let receiver_name_id = match receiver {
|
|
1185
|
+
let receiver_name_id = match &receiver {
|
|
1198
1186
|
Some(ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. }) => {
|
|
1199
|
-
self.index_constant_reference(
|
|
1187
|
+
self.index_constant_reference(receiver.as_ref().unwrap(), true)
|
|
1200
1188
|
}
|
|
1201
1189
|
Some(ruby_prism::Node::SelfNode { .. }) | None => match self.nesting_stack.last() {
|
|
1202
1190
|
Some(Nesting::Method(_)) => {
|
|
@@ -1205,20 +1193,20 @@ impl<'a> RubyIndexer<'a> {
|
|
|
1205
1193
|
}
|
|
1206
1194
|
None => {
|
|
1207
1195
|
self.local_graph.add_diagnostic(
|
|
1208
|
-
Rule::
|
|
1196
|
+
Rule::InvalidConstantVisibility,
|
|
1209
1197
|
Offset::from_prism_location(&node.location()),
|
|
1210
|
-
"
|
|
1198
|
+
format!("`{call_name}` called at top level"),
|
|
1211
1199
|
);
|
|
1212
1200
|
self.visit_call_node_parts(node);
|
|
1213
1201
|
return;
|
|
1214
1202
|
}
|
|
1215
1203
|
_ => None,
|
|
1216
1204
|
},
|
|
1217
|
-
|
|
1205
|
+
Some(other) => {
|
|
1218
1206
|
self.local_graph.add_diagnostic(
|
|
1219
|
-
Rule::
|
|
1220
|
-
Offset::from_prism_location(&
|
|
1221
|
-
"Dynamic receiver for
|
|
1207
|
+
Rule::InvalidConstantVisibility,
|
|
1208
|
+
Offset::from_prism_location(&other.location()),
|
|
1209
|
+
format!("Dynamic receiver for `{call_name}`"),
|
|
1222
1210
|
);
|
|
1223
1211
|
self.visit_call_node_parts(node);
|
|
1224
1212
|
return;
|
|
@@ -1230,29 +1218,14 @@ impl<'a> RubyIndexer<'a> {
|
|
|
1230
1218
|
};
|
|
1231
1219
|
|
|
1232
1220
|
for argument in &arguments.arguments() {
|
|
1233
|
-
let (name, location) =
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
}
|
|
1242
|
-
ruby_prism::Node::StringNode { .. } => {
|
|
1243
|
-
let string = argument.as_string_node().unwrap();
|
|
1244
|
-
let name = String::from_utf8_lossy(string.unescaped()).to_string();
|
|
1245
|
-
(name, argument.location())
|
|
1246
|
-
}
|
|
1247
|
-
_ => {
|
|
1248
|
-
self.local_graph.add_diagnostic(
|
|
1249
|
-
Rule::InvalidPrivateConstant,
|
|
1250
|
-
Offset::from_prism_location(&argument.location()),
|
|
1251
|
-
"Private constant called with non-symbol argument".to_string(),
|
|
1252
|
-
);
|
|
1253
|
-
self.visit(&argument);
|
|
1254
|
-
continue;
|
|
1255
|
-
}
|
|
1221
|
+
let Some((name, location)) = Self::extract_literal_name(&argument) else {
|
|
1222
|
+
self.local_graph.add_diagnostic(
|
|
1223
|
+
Rule::InvalidConstantVisibility,
|
|
1224
|
+
Offset::from_prism_location(&argument.location()),
|
|
1225
|
+
format!("`{call_name}` called with a non-literal argument"),
|
|
1226
|
+
);
|
|
1227
|
+
self.visit(&argument);
|
|
1228
|
+
continue;
|
|
1256
1229
|
};
|
|
1257
1230
|
|
|
1258
1231
|
let str_id = self.local_graph.intern_string(name);
|
|
@@ -1414,6 +1387,22 @@ impl<'a> RubyIndexer<'a> {
|
|
|
1414
1387
|
}
|
|
1415
1388
|
}
|
|
1416
1389
|
|
|
1390
|
+
fn extract_literal_name<'b>(arg: &ruby_prism::Node<'b>) -> Option<(String, ruby_prism::Location<'b>)> {
|
|
1391
|
+
match arg {
|
|
1392
|
+
ruby_prism::Node::SymbolNode { .. } => {
|
|
1393
|
+
let symbol = arg.as_symbol_node().unwrap();
|
|
1394
|
+
let value_loc = symbol.value_loc()?;
|
|
1395
|
+
Some((Self::location_to_string(&value_loc), value_loc))
|
|
1396
|
+
}
|
|
1397
|
+
ruby_prism::Node::StringNode { .. } => {
|
|
1398
|
+
let string = arg.as_string_node().unwrap();
|
|
1399
|
+
let name = String::from_utf8_lossy(string.unescaped()).to_string();
|
|
1400
|
+
Some((name, arg.location()))
|
|
1401
|
+
}
|
|
1402
|
+
_ => None,
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1417
1406
|
fn is_attr_call(arg: &ruby_prism::Node) -> bool {
|
|
1418
1407
|
arg.as_call_node().is_some_and(|call| {
|
|
1419
1408
|
let receiver = call.receiver();
|
|
@@ -1452,6 +1441,18 @@ impl<'a> RubyIndexer<'a> {
|
|
|
1452
1441
|
ruby_prism::Node::SymbolNode { .. } | ruby_prism::Node::StringNode { .. }
|
|
1453
1442
|
) {
|
|
1454
1443
|
self.create_method_visibility_definition(&arg, visibility, DefinitionFlags::empty());
|
|
1444
|
+
if visibility == Visibility::ModuleFunction {
|
|
1445
|
+
// `module_function` also creates a public singleton method, so we emit a
|
|
1446
|
+
// second def for the singleton side. The two defs stay separate (rather than
|
|
1447
|
+
// shared) so reverse-lookup invalidation can detach `Foo#bar` and
|
|
1448
|
+
// `Foo::<Foo>#bar` independently. The `SINGLETON_METHOD_VISIBILITY` flag
|
|
1449
|
+
// routes this one to the singleton class.
|
|
1450
|
+
self.create_method_visibility_definition(
|
|
1451
|
+
&arg,
|
|
1452
|
+
visibility,
|
|
1453
|
+
DefinitionFlags::SINGLETON_METHOD_VISIBILITY,
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1455
1456
|
} else {
|
|
1456
1457
|
// Unsupported arg — diagnostic + visit for side effects.
|
|
1457
1458
|
let arg_offset = Offset::from_prism_location(&arg.location());
|
|
@@ -1473,18 +1474,8 @@ impl<'a> RubyIndexer<'a> {
|
|
|
1473
1474
|
visibility: Visibility,
|
|
1474
1475
|
flags: DefinitionFlags,
|
|
1475
1476
|
) {
|
|
1476
|
-
let (name, location) =
|
|
1477
|
-
|
|
1478
|
-
let symbol = arg.as_symbol_node().unwrap();
|
|
1479
|
-
let Some(value_loc) = symbol.value_loc() else { return };
|
|
1480
|
-
(Self::location_to_string(&value_loc), value_loc)
|
|
1481
|
-
}
|
|
1482
|
-
ruby_prism::Node::StringNode { .. } => {
|
|
1483
|
-
let string = arg.as_string_node().unwrap();
|
|
1484
|
-
let name = String::from_utf8_lossy(string.unescaped()).to_string();
|
|
1485
|
-
(name, arg.location())
|
|
1486
|
-
}
|
|
1487
|
-
_ => return,
|
|
1477
|
+
let Some((name, location)) = Self::extract_literal_name(arg) else {
|
|
1478
|
+
return;
|
|
1488
1479
|
};
|
|
1489
1480
|
|
|
1490
1481
|
self.create_method_visibility_definition_from_name(&name, &location, visibility, flags);
|
|
@@ -1750,20 +1750,20 @@ mod visibility_tests {
|
|
|
1750
1750
|
fn index_private_constant_calls_diagnostics() {
|
|
1751
1751
|
let context = index_source({
|
|
1752
1752
|
"
|
|
1753
|
-
private_constant :
|
|
1754
|
-
self.private_constant :
|
|
1755
|
-
foo.private_constant :
|
|
1753
|
+
private_constant :BAR # not indexed, called at top level
|
|
1754
|
+
self.private_constant :BAR # not indexed, self receiver at top level
|
|
1755
|
+
foo.private_constant :BAR # not indexed, dynamic receiver
|
|
1756
1756
|
|
|
1757
1757
|
module Foo
|
|
1758
|
-
private_constant
|
|
1758
|
+
private_constant SomeConst, some_method # not indexed, non-literal arguments
|
|
1759
1759
|
private_constant # not indexed, no arguments
|
|
1760
1760
|
|
|
1761
1761
|
def self.qux
|
|
1762
|
-
private_constant :
|
|
1762
|
+
private_constant :BAR # not indexed, inside a method
|
|
1763
1763
|
end
|
|
1764
1764
|
|
|
1765
1765
|
def foo
|
|
1766
|
-
private_constant :
|
|
1766
|
+
private_constant :BAR # not indexed, inside a method
|
|
1767
1767
|
end
|
|
1768
1768
|
end
|
|
1769
1769
|
"
|
|
@@ -1772,15 +1772,52 @@ mod visibility_tests {
|
|
|
1772
1772
|
assert_local_diagnostics_eq!(
|
|
1773
1773
|
&context,
|
|
1774
1774
|
vec![
|
|
1775
|
-
"invalid-
|
|
1776
|
-
"invalid-
|
|
1777
|
-
"invalid-
|
|
1778
|
-
"invalid-
|
|
1779
|
-
"invalid-
|
|
1775
|
+
"invalid-constant-visibility: `private_constant` called at top level (1:1-1:22)",
|
|
1776
|
+
"invalid-constant-visibility: `private_constant` called at top level (2:1-2:27)",
|
|
1777
|
+
"invalid-constant-visibility: Dynamic receiver for `private_constant` (3:1-3:4)",
|
|
1778
|
+
"invalid-constant-visibility: `private_constant` called with a non-literal argument (6:20-6:29)",
|
|
1779
|
+
"invalid-constant-visibility: `private_constant` called with a non-literal argument (6:31-6:42)",
|
|
1780
1780
|
]
|
|
1781
1781
|
);
|
|
1782
1782
|
|
|
1783
|
-
assert_eq!(context.graph().definitions().len(), 3); // Foo, Foo
|
|
1783
|
+
assert_eq!(context.graph().definitions().len(), 3); // Foo, Foo.qux, Foo#foo
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
#[test]
|
|
1787
|
+
fn index_public_constant_calls_diagnostics() {
|
|
1788
|
+
let context = index_source({
|
|
1789
|
+
"
|
|
1790
|
+
public_constant :BAR # not indexed, called at top level
|
|
1791
|
+
self.public_constant :BAR # not indexed, self receiver at top level
|
|
1792
|
+
foo.public_constant :BAR # not indexed, dynamic receiver
|
|
1793
|
+
|
|
1794
|
+
module Foo
|
|
1795
|
+
public_constant SomeConst, some_method # not indexed, non-literal arguments
|
|
1796
|
+
public_constant # not indexed, no arguments
|
|
1797
|
+
|
|
1798
|
+
def self.qux
|
|
1799
|
+
public_constant :BAR # not indexed, inside a method
|
|
1800
|
+
end
|
|
1801
|
+
|
|
1802
|
+
def foo
|
|
1803
|
+
public_constant :BAR # not indexed, inside a method
|
|
1804
|
+
end
|
|
1805
|
+
end
|
|
1806
|
+
"
|
|
1807
|
+
});
|
|
1808
|
+
|
|
1809
|
+
assert_local_diagnostics_eq!(
|
|
1810
|
+
&context,
|
|
1811
|
+
vec![
|
|
1812
|
+
"invalid-constant-visibility: `public_constant` called at top level (1:1-1:21)",
|
|
1813
|
+
"invalid-constant-visibility: `public_constant` called at top level (2:1-2:26)",
|
|
1814
|
+
"invalid-constant-visibility: Dynamic receiver for `public_constant` (3:1-3:4)",
|
|
1815
|
+
"invalid-constant-visibility: `public_constant` called with a non-literal argument (6:19-6:28)",
|
|
1816
|
+
"invalid-constant-visibility: `public_constant` called with a non-literal argument (6:30-6:41)",
|
|
1817
|
+
]
|
|
1818
|
+
);
|
|
1819
|
+
|
|
1820
|
+
assert_eq!(context.graph().definitions().len(), 3); // Foo, Foo.qux, Foo#foo
|
|
1784
1821
|
}
|
|
1785
1822
|
|
|
1786
1823
|
#[test]
|
|
@@ -2021,10 +2058,29 @@ mod visibility_tests {
|
|
|
2021
2058
|
|
|
2022
2059
|
assert_no_local_diagnostics!(&context);
|
|
2023
2060
|
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2061
|
+
let defs = context.all_definitions_at("4:20-4:23");
|
|
2062
|
+
assert_eq!(defs.len(), 2, "expected two MethodVisibility defs");
|
|
2063
|
+
|
|
2064
|
+
let mut instance_side = None;
|
|
2065
|
+
let mut singleton_side = None;
|
|
2066
|
+
for def in defs {
|
|
2067
|
+
let Definition::MethodVisibility(method_vis) = def else {
|
|
2068
|
+
panic!("expected MethodVisibility, got {:?}", def.kind());
|
|
2069
|
+
};
|
|
2070
|
+
if method_vis.flags().is_singleton_method_visibility() {
|
|
2071
|
+
singleton_side = Some(method_vis.as_ref());
|
|
2072
|
+
} else {
|
|
2073
|
+
instance_side = Some(method_vis.as_ref());
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
let instance_side = instance_side.expect("missing instance-side def");
|
|
2078
|
+
assert_def_str_eq!(&context, instance_side, "foo()");
|
|
2079
|
+
assert_eq!(instance_side.visibility(), &Visibility::ModuleFunction);
|
|
2080
|
+
|
|
2081
|
+
let singleton_side = singleton_side.expect("missing singleton-side def");
|
|
2082
|
+
assert_def_str_eq!(&context, singleton_side, "foo()");
|
|
2083
|
+
assert_eq!(singleton_side.visibility(), &Visibility::ModuleFunction);
|
|
2028
2084
|
}
|
|
2029
2085
|
|
|
2030
2086
|
#[test]
|