rubydex 0.1.0.beta12-aarch64-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 +7 -0
- data/LICENSE.txt +23 -0
- data/README.md +125 -0
- data/THIRD_PARTY_LICENSES.html +4562 -0
- data/exe/rdx +47 -0
- data/ext/rubydex/declaration.c +453 -0
- data/ext/rubydex/declaration.h +23 -0
- data/ext/rubydex/definition.c +284 -0
- data/ext/rubydex/definition.h +28 -0
- data/ext/rubydex/diagnostic.c +6 -0
- data/ext/rubydex/diagnostic.h +11 -0
- data/ext/rubydex/document.c +97 -0
- data/ext/rubydex/document.h +10 -0
- data/ext/rubydex/extconf.rb +138 -0
- data/ext/rubydex/graph.c +681 -0
- data/ext/rubydex/graph.h +10 -0
- data/ext/rubydex/handle.h +44 -0
- data/ext/rubydex/location.c +22 -0
- data/ext/rubydex/location.h +15 -0
- data/ext/rubydex/reference.c +123 -0
- data/ext/rubydex/reference.h +15 -0
- data/ext/rubydex/rubydex.c +22 -0
- data/ext/rubydex/utils.c +108 -0
- data/ext/rubydex/utils.h +34 -0
- data/lib/rubydex/3.2/rubydex.so +0 -0
- data/lib/rubydex/3.3/rubydex.so +0 -0
- data/lib/rubydex/3.4/rubydex.so +0 -0
- data/lib/rubydex/4.0/rubydex.so +0 -0
- data/lib/rubydex/comment.rb +17 -0
- data/lib/rubydex/diagnostic.rb +21 -0
- data/lib/rubydex/failures.rb +15 -0
- data/lib/rubydex/graph.rb +98 -0
- data/lib/rubydex/keyword.rb +17 -0
- data/lib/rubydex/keyword_parameter.rb +13 -0
- data/lib/rubydex/librubydex_sys.so +0 -0
- data/lib/rubydex/location.rb +90 -0
- data/lib/rubydex/mixin.rb +22 -0
- data/lib/rubydex/version.rb +5 -0
- data/lib/rubydex.rb +23 -0
- data/rbi/rubydex.rbi +422 -0
- data/rust/Cargo.lock +1851 -0
- data/rust/Cargo.toml +29 -0
- data/rust/about.hbs +78 -0
- data/rust/about.toml +10 -0
- data/rust/rubydex/Cargo.toml +42 -0
- data/rust/rubydex/src/compile_assertions.rs +13 -0
- data/rust/rubydex/src/diagnostic.rs +110 -0
- data/rust/rubydex/src/errors.rs +28 -0
- data/rust/rubydex/src/indexing/local_graph.rs +224 -0
- data/rust/rubydex/src/indexing/rbs_indexer.rs +1551 -0
- data/rust/rubydex/src/indexing/ruby_indexer.rs +2329 -0
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +4962 -0
- data/rust/rubydex/src/indexing.rs +210 -0
- data/rust/rubydex/src/integrity.rs +279 -0
- data/rust/rubydex/src/job_queue.rs +205 -0
- data/rust/rubydex/src/lib.rs +17 -0
- data/rust/rubydex/src/listing.rs +371 -0
- data/rust/rubydex/src/main.rs +160 -0
- data/rust/rubydex/src/model/built_in.rs +83 -0
- data/rust/rubydex/src/model/comment.rs +24 -0
- data/rust/rubydex/src/model/declaration.rs +671 -0
- data/rust/rubydex/src/model/definitions.rs +1682 -0
- data/rust/rubydex/src/model/document.rs +222 -0
- data/rust/rubydex/src/model/encoding.rs +22 -0
- data/rust/rubydex/src/model/graph.rs +3754 -0
- data/rust/rubydex/src/model/id.rs +110 -0
- data/rust/rubydex/src/model/identity_maps.rs +58 -0
- data/rust/rubydex/src/model/ids.rs +60 -0
- data/rust/rubydex/src/model/keywords.rs +256 -0
- data/rust/rubydex/src/model/name.rs +298 -0
- data/rust/rubydex/src/model/references.rs +111 -0
- data/rust/rubydex/src/model/string_ref.rs +50 -0
- data/rust/rubydex/src/model/visibility.rs +41 -0
- data/rust/rubydex/src/model.rs +15 -0
- data/rust/rubydex/src/offset.rs +147 -0
- data/rust/rubydex/src/position.rs +6 -0
- data/rust/rubydex/src/query.rs +1841 -0
- data/rust/rubydex/src/resolution.rs +6517 -0
- data/rust/rubydex/src/stats/memory.rs +71 -0
- data/rust/rubydex/src/stats/orphan_report.rs +264 -0
- data/rust/rubydex/src/stats/timer.rs +127 -0
- data/rust/rubydex/src/stats.rs +11 -0
- data/rust/rubydex/src/test_utils/context.rs +226 -0
- data/rust/rubydex/src/test_utils/graph_test.rs +730 -0
- data/rust/rubydex/src/test_utils/local_graph_test.rs +602 -0
- data/rust/rubydex/src/test_utils.rs +52 -0
- data/rust/rubydex/src/visualization/dot.rs +192 -0
- data/rust/rubydex/src/visualization.rs +6 -0
- data/rust/rubydex/tests/cli.rs +185 -0
- data/rust/rubydex-mcp/Cargo.toml +28 -0
- data/rust/rubydex-mcp/src/main.rs +48 -0
- data/rust/rubydex-mcp/src/server.rs +1145 -0
- data/rust/rubydex-mcp/src/tools.rs +49 -0
- data/rust/rubydex-mcp/tests/mcp.rs +302 -0
- data/rust/rubydex-sys/Cargo.toml +20 -0
- data/rust/rubydex-sys/build.rs +14 -0
- data/rust/rubydex-sys/cbindgen.toml +12 -0
- data/rust/rubydex-sys/src/declaration_api.rs +485 -0
- data/rust/rubydex-sys/src/definition_api.rs +443 -0
- data/rust/rubydex-sys/src/diagnostic_api.rs +99 -0
- data/rust/rubydex-sys/src/document_api.rs +85 -0
- data/rust/rubydex-sys/src/graph_api.rs +948 -0
- data/rust/rubydex-sys/src/lib.rs +79 -0
- data/rust/rubydex-sys/src/location_api.rs +79 -0
- data/rust/rubydex-sys/src/name_api.rs +135 -0
- data/rust/rubydex-sys/src/reference_api.rs +267 -0
- data/rust/rubydex-sys/src/utils.rs +70 -0
- data/rust/rustfmt.toml +2 -0
- metadata +159 -0
|
@@ -0,0 +1,2329 @@
|
|
|
1
|
+
//! Visit the Ruby AST and create the definitions.
|
|
2
|
+
|
|
3
|
+
use crate::diagnostic::Rule;
|
|
4
|
+
use crate::indexing::local_graph::LocalGraph;
|
|
5
|
+
use crate::model::comment::Comment;
|
|
6
|
+
use crate::model::definitions::{
|
|
7
|
+
AttrAccessorDefinition, AttrReaderDefinition, AttrWriterDefinition, ClassDefinition, ClassVariableDefinition,
|
|
8
|
+
ConstantAliasDefinition, ConstantDefinition, ConstantVisibilityDefinition, Definition, DefinitionFlags,
|
|
9
|
+
ExtendDefinition, GlobalVariableAliasDefinition, GlobalVariableDefinition, IncludeDefinition,
|
|
10
|
+
InstanceVariableDefinition, MethodAliasDefinition, MethodDefinition, MethodVisibilityDefinition, Mixin,
|
|
11
|
+
ModuleDefinition, Parameter, ParameterStruct, PrependDefinition, Receiver, Signatures, SingletonClassDefinition,
|
|
12
|
+
};
|
|
13
|
+
use crate::model::document::Document;
|
|
14
|
+
use crate::model::ids::{DefinitionId, NameId, StringId, UriId};
|
|
15
|
+
use crate::model::name::{Name, ParentScope};
|
|
16
|
+
use crate::model::references::{ConstantReference, MethodRef};
|
|
17
|
+
use crate::model::visibility::Visibility;
|
|
18
|
+
use crate::offset::Offset;
|
|
19
|
+
|
|
20
|
+
use ruby_prism::{ParseResult, Visit};
|
|
21
|
+
|
|
22
|
+
#[derive(Clone, Copy)]
|
|
23
|
+
enum MixinType {
|
|
24
|
+
Include,
|
|
25
|
+
Prepend,
|
|
26
|
+
Extend,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
enum Nesting {
|
|
30
|
+
/// Nesting stack entries that produce a new lexical scope to which constant references must be attached to (i.e.:
|
|
31
|
+
/// the class and module keywords). All lexical scopes are also owner, but the opposite is not true
|
|
32
|
+
LexicalScope(DefinitionId),
|
|
33
|
+
/// An owner entry that will be associated with all members encountered, but will not produce a new lexical scope
|
|
34
|
+
/// (e.g.: Module.new or Class.new)
|
|
35
|
+
Owner(DefinitionId),
|
|
36
|
+
/// A method entry that is used to set the correct owner for instance variables, but cannot own anything itself
|
|
37
|
+
Method(DefinitionId),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl Nesting {
|
|
41
|
+
fn id(&self) -> DefinitionId {
|
|
42
|
+
match self {
|
|
43
|
+
Nesting::LexicalScope(id) | Nesting::Owner(id) | Nesting::Method(id) => *id,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
struct VisibilityModifier {
|
|
49
|
+
visibility: Visibility,
|
|
50
|
+
is_inline: bool,
|
|
51
|
+
offset: Offset,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
impl VisibilityModifier {
|
|
55
|
+
#[must_use]
|
|
56
|
+
pub fn new(visibility: Visibility, is_inline: bool, offset: Offset) -> Self {
|
|
57
|
+
Self {
|
|
58
|
+
visibility,
|
|
59
|
+
is_inline,
|
|
60
|
+
offset,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[must_use]
|
|
65
|
+
pub fn visibility(&self) -> &Visibility {
|
|
66
|
+
&self.visibility
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#[must_use]
|
|
70
|
+
pub fn is_inline(&self) -> bool {
|
|
71
|
+
self.is_inline
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#[must_use]
|
|
75
|
+
pub fn offset(&self) -> &Offset {
|
|
76
|
+
&self.offset
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// The indexer for the definitions found in the Ruby source code.
|
|
81
|
+
///
|
|
82
|
+
/// It implements the `Visit` trait from `ruby_prism` to visit the AST and create a hash of definitions that must be
|
|
83
|
+
/// merged into the global state later.
|
|
84
|
+
pub struct RubyIndexer<'a> {
|
|
85
|
+
uri_id: UriId,
|
|
86
|
+
local_graph: LocalGraph,
|
|
87
|
+
source: &'a str,
|
|
88
|
+
comments: Vec<CommentGroup>,
|
|
89
|
+
nesting_stack: Vec<Nesting>,
|
|
90
|
+
visibility_stack: Vec<VisibilityModifier>,
|
|
91
|
+
pending_decorator_offset: Option<Offset>,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
impl<'a> RubyIndexer<'a> {
|
|
95
|
+
#[must_use]
|
|
96
|
+
pub fn new(uri: String, source: &'a str) -> Self {
|
|
97
|
+
let uri_id = UriId::from(&uri);
|
|
98
|
+
let local_graph = LocalGraph::new(uri_id, Document::new(uri, source));
|
|
99
|
+
|
|
100
|
+
Self {
|
|
101
|
+
uri_id,
|
|
102
|
+
local_graph,
|
|
103
|
+
source,
|
|
104
|
+
comments: Vec::new(),
|
|
105
|
+
nesting_stack: Vec::new(),
|
|
106
|
+
visibility_stack: vec![VisibilityModifier::new(Visibility::Private, false, Offset::new(0, 0))],
|
|
107
|
+
pending_decorator_offset: None,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#[must_use]
|
|
112
|
+
pub fn local_graph(self) -> LocalGraph {
|
|
113
|
+
self.local_graph
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
pub fn index(&mut self) {
|
|
117
|
+
let result = ruby_prism::parse(self.source.as_bytes());
|
|
118
|
+
|
|
119
|
+
for error in result.errors() {
|
|
120
|
+
self.local_graph.add_diagnostic(
|
|
121
|
+
Rule::ParseError,
|
|
122
|
+
Offset::from_prism_location(&error.location()),
|
|
123
|
+
error.message().to_string(),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for warning in result.warnings() {
|
|
128
|
+
self.local_graph.add_diagnostic(
|
|
129
|
+
Rule::ParseWarning,
|
|
130
|
+
Offset::from_prism_location(&warning.location()),
|
|
131
|
+
warning.message().to_string(),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
self.comments = self.parse_comments_into_groups(&result);
|
|
136
|
+
self.visit(&result.node());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fn parse_comments_into_groups(&mut self, result: &ParseResult<'_>) -> Vec<CommentGroup> {
|
|
140
|
+
let mut iter = result.comments().peekable();
|
|
141
|
+
let mut groups = Vec::new();
|
|
142
|
+
|
|
143
|
+
while let Some(comment) = iter.next() {
|
|
144
|
+
let mut group = CommentGroup::new();
|
|
145
|
+
group.add_comment(&comment);
|
|
146
|
+
while let Some(next_comment) = iter.peek() {
|
|
147
|
+
if group.accepts(next_comment, self.source) {
|
|
148
|
+
let next = iter.next().unwrap();
|
|
149
|
+
group.add_comment(&next);
|
|
150
|
+
} else {
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
groups.push(group);
|
|
155
|
+
}
|
|
156
|
+
groups
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
fn location_to_string(location: &ruby_prism::Location) -> String {
|
|
160
|
+
String::from_utf8_lossy(location.as_slice()).to_string()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fn offset_to_string(&self, offset: &Offset) -> String {
|
|
164
|
+
self.source[offset.start() as usize..offset.end() as usize].to_string()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
fn find_comments_for(&self, offset: u32) -> (Box<[Comment]>, DefinitionFlags) {
|
|
168
|
+
let offset_usize = offset as usize;
|
|
169
|
+
if self.comments.is_empty() {
|
|
170
|
+
return (Box::default(), DefinitionFlags::empty());
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let idx = match self.comments.binary_search_by_key(&offset_usize, |g| g.end_offset) {
|
|
174
|
+
Ok(_) => {
|
|
175
|
+
// This should never happen in valid Ruby syntax - a comment cannot end exactly
|
|
176
|
+
// where a definition begins (there must be at least a newline between them)
|
|
177
|
+
debug_assert!(false, "Comment ends exactly at definition start - this indicates a bug");
|
|
178
|
+
return (Box::default(), DefinitionFlags::empty());
|
|
179
|
+
}
|
|
180
|
+
Err(i) if i > 0 => i - 1,
|
|
181
|
+
Err(_) => return (Box::default(), DefinitionFlags::empty()),
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
let group = &self.comments[idx];
|
|
185
|
+
let between = &self.source.as_bytes()[group.end_offset..offset_usize];
|
|
186
|
+
if !between.iter().all(|&b| b.is_ascii_whitespace()) {
|
|
187
|
+
return (Box::default(), DefinitionFlags::empty());
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// We allow at most one blank line between the comment and the definition
|
|
191
|
+
if bytecount::count(between, b'\n') > 2 {
|
|
192
|
+
return (Box::default(), DefinitionFlags::empty());
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
(group.comments(), group.flags())
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// We consider comments above a method decorator like Sorbet's sig to be documentation for methods and attributes.
|
|
199
|
+
/// To find the correct comment offset, we remember the offsets for the sigs we find
|
|
200
|
+
fn take_decorator_offset(&mut self, definition_start: u32) -> Option<u32> {
|
|
201
|
+
let decorator_offset = self.pending_decorator_offset.take()?;
|
|
202
|
+
if decorator_offset.end() > definition_start {
|
|
203
|
+
return None;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let between = &self.source.as_bytes()[decorator_offset.end() as usize..definition_start as usize];
|
|
207
|
+
if between.iter().all(|&b| b.is_ascii_whitespace()) && bytecount::count(between, b'\n') <= 1 {
|
|
208
|
+
Some(decorator_offset.start())
|
|
209
|
+
} else {
|
|
210
|
+
None
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
fn collect_parameters(&mut self, node: &ruby_prism::DefNode) -> Vec<Parameter> {
|
|
215
|
+
let mut parameters: Vec<Parameter> = Vec::new();
|
|
216
|
+
|
|
217
|
+
if let Some(parameters_list) = node.parameters() {
|
|
218
|
+
for parameter in ¶meters_list.requireds() {
|
|
219
|
+
let location = parameter.location();
|
|
220
|
+
let str_id = self.local_graph.intern_string(Self::location_to_string(&location));
|
|
221
|
+
|
|
222
|
+
parameters.push(Parameter::RequiredPositional(ParameterStruct::new(
|
|
223
|
+
Offset::from_prism_location(&location),
|
|
224
|
+
str_id,
|
|
225
|
+
)));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for parameter in ¶meters_list.optionals() {
|
|
229
|
+
let opt_param = parameter.as_optional_parameter_node().unwrap();
|
|
230
|
+
let name_loc = opt_param.name_loc();
|
|
231
|
+
let str_id = self.local_graph.intern_string(Self::location_to_string(&name_loc));
|
|
232
|
+
|
|
233
|
+
parameters.push(Parameter::OptionalPositional(ParameterStruct::new(
|
|
234
|
+
Offset::from_prism_location(&name_loc),
|
|
235
|
+
str_id,
|
|
236
|
+
)));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if let Some(rest) = parameters_list.rest() {
|
|
240
|
+
let rest_param = rest.as_rest_parameter_node().unwrap();
|
|
241
|
+
let location = rest_param.name_loc().unwrap_or_else(|| rest.location());
|
|
242
|
+
let str_id = self.local_graph.intern_string(Self::location_to_string(&location));
|
|
243
|
+
|
|
244
|
+
parameters.push(Parameter::RestPositional(ParameterStruct::new(
|
|
245
|
+
Offset::from_prism_location(&location),
|
|
246
|
+
str_id,
|
|
247
|
+
)));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
for post in ¶meters_list.posts() {
|
|
251
|
+
let location = post.location();
|
|
252
|
+
let str_id = self.local_graph.intern_string(Self::location_to_string(&location));
|
|
253
|
+
|
|
254
|
+
parameters.push(Parameter::Post(ParameterStruct::new(
|
|
255
|
+
Offset::from_prism_location(&location),
|
|
256
|
+
str_id,
|
|
257
|
+
)));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
for keyword in ¶meters_list.keywords() {
|
|
261
|
+
match keyword {
|
|
262
|
+
ruby_prism::Node::RequiredKeywordParameterNode { .. } => {
|
|
263
|
+
let required = keyword.as_required_keyword_parameter_node().unwrap();
|
|
264
|
+
let loc = required.name_loc();
|
|
265
|
+
let full = Offset::from_prism_location(&loc);
|
|
266
|
+
let offset = Offset::new(full.start(), full.end() - 1); // Exclude trailing colon
|
|
267
|
+
let str_id = self.local_graph.intern_string(self.offset_to_string(&offset));
|
|
268
|
+
|
|
269
|
+
parameters.push(Parameter::RequiredKeyword(ParameterStruct::new(offset, str_id)));
|
|
270
|
+
}
|
|
271
|
+
ruby_prism::Node::OptionalKeywordParameterNode { .. } => {
|
|
272
|
+
let optional = keyword.as_optional_keyword_parameter_node().unwrap();
|
|
273
|
+
let loc = optional.name_loc();
|
|
274
|
+
let full = Offset::from_prism_location(&loc);
|
|
275
|
+
let offset = Offset::new(full.start(), full.end() - 1); // Exclude trailing colon
|
|
276
|
+
let str_id = self.local_graph.intern_string(self.offset_to_string(&offset));
|
|
277
|
+
|
|
278
|
+
parameters.push(Parameter::OptionalKeyword(ParameterStruct::new(offset, str_id)));
|
|
279
|
+
}
|
|
280
|
+
_ => {}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if let Some(rest) = parameters_list.keyword_rest() {
|
|
285
|
+
match rest {
|
|
286
|
+
ruby_prism::Node::KeywordRestParameterNode { .. } => {
|
|
287
|
+
let location = rest
|
|
288
|
+
.as_keyword_rest_parameter_node()
|
|
289
|
+
.unwrap()
|
|
290
|
+
.name_loc()
|
|
291
|
+
.unwrap_or_else(|| rest.location());
|
|
292
|
+
let str_id = self.local_graph.intern_string(Self::location_to_string(&location));
|
|
293
|
+
|
|
294
|
+
parameters.push(Parameter::RestKeyword(ParameterStruct::new(
|
|
295
|
+
Offset::from_prism_location(&location),
|
|
296
|
+
str_id,
|
|
297
|
+
)));
|
|
298
|
+
}
|
|
299
|
+
ruby_prism::Node::ForwardingParameterNode { .. } => {
|
|
300
|
+
let location = rest.location();
|
|
301
|
+
let str_id = self.local_graph.intern_string(Self::location_to_string(&location));
|
|
302
|
+
|
|
303
|
+
parameters.push(Parameter::Forward(ParameterStruct::new(
|
|
304
|
+
Offset::from_prism_location(&location),
|
|
305
|
+
str_id,
|
|
306
|
+
)));
|
|
307
|
+
}
|
|
308
|
+
_ => {
|
|
309
|
+
// Do nothing
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if let Some(block) = parameters_list.block() {
|
|
315
|
+
let location = block.name_loc().unwrap_or_else(|| block.location());
|
|
316
|
+
let str_id = self.local_graph.intern_string(Self::location_to_string(&location));
|
|
317
|
+
|
|
318
|
+
parameters.push(Parameter::Block(ParameterStruct::new(
|
|
319
|
+
Offset::from_prism_location(&location),
|
|
320
|
+
str_id,
|
|
321
|
+
)));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
parameters
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/// Gets the `NameId` of the current lexical scope (class/module/singleton class).
|
|
329
|
+
/// Used to resolve `self` to a concrete `NameId` during indexing.
|
|
330
|
+
///
|
|
331
|
+
/// Iterates through the definitions stack in reverse to find the first class/module/singleton class, skipping
|
|
332
|
+
/// methods. Ignores `Class.new` and other owners that do not produce lexical scopes
|
|
333
|
+
///
|
|
334
|
+
/// # Panics
|
|
335
|
+
///
|
|
336
|
+
/// Panics if the definition is not a class, module, or singleton class
|
|
337
|
+
fn current_lexical_scope_name_id(&self) -> Option<NameId> {
|
|
338
|
+
self.nesting_stack.iter().rev().find_map(|nesting| match nesting {
|
|
339
|
+
Nesting::LexicalScope(id) => {
|
|
340
|
+
if let Some(definition) = self.local_graph.definitions().get(id) {
|
|
341
|
+
match definition {
|
|
342
|
+
Definition::Class(class_def) => Some(*class_def.name_id()),
|
|
343
|
+
Definition::Module(module_def) => Some(*module_def.name_id()),
|
|
344
|
+
Definition::SingletonClass(singleton_class_def) => Some(*singleton_class_def.name_id()),
|
|
345
|
+
Definition::Method(_) => None,
|
|
346
|
+
_ => panic!("current nesting is not a class/module/singleton class: {definition:?}"),
|
|
347
|
+
}
|
|
348
|
+
} else {
|
|
349
|
+
None
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
Nesting::Method(_) | Nesting::Owner(_) => None,
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/// Gets the `NameId` of the current owner (class/module/singleton class), including `Class.new`/`Module.new`.
|
|
357
|
+
/// Used to resolve `self` in singleton method definitions (e.g., `def self.bar`).
|
|
358
|
+
///
|
|
359
|
+
/// Unlike `current_lexical_scope_name_id`, this method considers `Nesting::Owner` entries,
|
|
360
|
+
/// because `self` inside a `Class.new` block refers to the new class being created.
|
|
361
|
+
fn current_owner_name_id(&self) -> Option<NameId> {
|
|
362
|
+
self.nesting_stack.iter().rev().find_map(|nesting| match nesting {
|
|
363
|
+
Nesting::LexicalScope(id) | Nesting::Owner(id) => {
|
|
364
|
+
if let Some(definition) = self.local_graph.definitions().get(id) {
|
|
365
|
+
match definition {
|
|
366
|
+
Definition::Class(class_def) => Some(*class_def.name_id()),
|
|
367
|
+
Definition::Module(module_def) => Some(*module_def.name_id()),
|
|
368
|
+
Definition::SingletonClass(singleton_class_def) => Some(*singleton_class_def.name_id()),
|
|
369
|
+
Definition::Method(_) => None,
|
|
370
|
+
_ => panic!("current nesting is not a class/module/singleton class: {definition:?}"),
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
None
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
Nesting::Method(_) => None,
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Runs the given closure for each string or symbol argument of a call node.
|
|
381
|
+
fn each_string_or_symbol_arg<F>(node: &ruby_prism::CallNode, mut f: F)
|
|
382
|
+
where
|
|
383
|
+
F: FnMut(String, ruby_prism::Location),
|
|
384
|
+
{
|
|
385
|
+
if let Some(arguments) = node.arguments() {
|
|
386
|
+
for argument in &arguments.arguments() {
|
|
387
|
+
match argument {
|
|
388
|
+
ruby_prism::Node::SymbolNode { .. } => {
|
|
389
|
+
let symbol = argument.as_symbol_node().unwrap();
|
|
390
|
+
|
|
391
|
+
if let Some(value_loc) = symbol.value_loc() {
|
|
392
|
+
let name = Self::location_to_string(&value_loc);
|
|
393
|
+
f(name, value_loc);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
ruby_prism::Node::StringNode { .. } => {
|
|
397
|
+
let string = argument.as_string_node().unwrap();
|
|
398
|
+
let name = String::from_utf8_lossy(string.unescaped()).to_string();
|
|
399
|
+
f(name, argument.location());
|
|
400
|
+
}
|
|
401
|
+
_ => {}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
fn index_constant_reference(&mut self, node: &ruby_prism::Node, push_final_reference: bool) -> Option<NameId> {
|
|
408
|
+
let mut parent_scope_id = ParentScope::None;
|
|
409
|
+
|
|
410
|
+
let location = match node {
|
|
411
|
+
ruby_prism::Node::ConstantPathNode { .. } => {
|
|
412
|
+
let constant = node.as_constant_path_node().unwrap();
|
|
413
|
+
|
|
414
|
+
if let Some(parent) = constant.parent() {
|
|
415
|
+
// Ignore parent scopes that are not constants, like `foo::Bar`
|
|
416
|
+
match parent {
|
|
417
|
+
ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. } => {}
|
|
418
|
+
_ => {
|
|
419
|
+
self.local_graph.add_diagnostic(
|
|
420
|
+
Rule::DynamicConstantReference,
|
|
421
|
+
Offset::from_prism_location(&parent.location()),
|
|
422
|
+
"Dynamic constant reference".to_string(),
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
return None;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
parent_scope_id = self
|
|
430
|
+
.index_constant_reference(&parent, true)
|
|
431
|
+
.map_or(ParentScope::None, ParentScope::Some);
|
|
432
|
+
} else {
|
|
433
|
+
parent_scope_id = ParentScope::TopLevel;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
constant.name_loc()
|
|
437
|
+
}
|
|
438
|
+
ruby_prism::Node::ConstantPathWriteNode { .. } => {
|
|
439
|
+
let constant = node.as_constant_path_write_node().unwrap();
|
|
440
|
+
let target = constant.target();
|
|
441
|
+
|
|
442
|
+
if let Some(parent) = target.parent() {
|
|
443
|
+
// Ignore parent scopes that are not constants, like `foo::Bar`
|
|
444
|
+
match parent {
|
|
445
|
+
ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. } => {}
|
|
446
|
+
_ => {
|
|
447
|
+
return None;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
parent_scope_id = self
|
|
452
|
+
.index_constant_reference(&parent, true)
|
|
453
|
+
.map_or(ParentScope::None, ParentScope::Some);
|
|
454
|
+
} else {
|
|
455
|
+
parent_scope_id = ParentScope::TopLevel;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
target.name_loc()
|
|
459
|
+
}
|
|
460
|
+
ruby_prism::Node::ConstantReadNode { .. } => node.location(),
|
|
461
|
+
ruby_prism::Node::ConstantAndWriteNode { .. } => node.as_constant_and_write_node().unwrap().name_loc(),
|
|
462
|
+
ruby_prism::Node::ConstantOperatorWriteNode { .. } => {
|
|
463
|
+
node.as_constant_operator_write_node().unwrap().name_loc()
|
|
464
|
+
}
|
|
465
|
+
ruby_prism::Node::ConstantOrWriteNode { .. } => node.as_constant_or_write_node().unwrap().name_loc(),
|
|
466
|
+
ruby_prism::Node::ConstantTargetNode { .. } => node.as_constant_target_node().unwrap().location(),
|
|
467
|
+
ruby_prism::Node::ConstantWriteNode { .. } => node.as_constant_write_node().unwrap().name_loc(),
|
|
468
|
+
ruby_prism::Node::ConstantPathTargetNode { .. } => {
|
|
469
|
+
let target = node.as_constant_path_target_node().unwrap();
|
|
470
|
+
|
|
471
|
+
if let Some(parent) = target.parent() {
|
|
472
|
+
match parent {
|
|
473
|
+
ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. } => {}
|
|
474
|
+
_ => {
|
|
475
|
+
return None;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
parent_scope_id = self
|
|
480
|
+
.index_constant_reference(&parent, true)
|
|
481
|
+
.map_or(ParentScope::None, ParentScope::Some);
|
|
482
|
+
} else {
|
|
483
|
+
parent_scope_id = ParentScope::TopLevel;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
target.name_loc()
|
|
487
|
+
}
|
|
488
|
+
_ => {
|
|
489
|
+
return None;
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
let offset = Offset::from_prism_location(&location);
|
|
494
|
+
let name = Self::location_to_string(&location);
|
|
495
|
+
let string_id = self.local_graph.intern_string(name);
|
|
496
|
+
let name_id = self.local_graph.add_name(Name::new(
|
|
497
|
+
string_id,
|
|
498
|
+
parent_scope_id,
|
|
499
|
+
self.current_lexical_scope_name_id(),
|
|
500
|
+
));
|
|
501
|
+
|
|
502
|
+
if push_final_reference {
|
|
503
|
+
self.local_graph
|
|
504
|
+
.add_constant_reference(ConstantReference::new(name_id, self.uri_id, offset));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
Some(name_id)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
fn index_method_reference(&mut self, name: String, location: &ruby_prism::Location, receiver: Option<NameId>) {
|
|
511
|
+
let offset = Offset::from_prism_location(location);
|
|
512
|
+
let str_id = self.local_graph.intern_string(name);
|
|
513
|
+
let reference = MethodRef::new(str_id, self.uri_id, offset, receiver);
|
|
514
|
+
self.local_graph.add_method_reference(reference);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
fn add_definition_from_location<F>(&mut self, location: &ruby_prism::Location, builder: F) -> DefinitionId
|
|
518
|
+
where
|
|
519
|
+
F: FnOnce(StringId, Offset, Box<[Comment]>, DefinitionFlags, Option<DefinitionId>, UriId) -> Definition,
|
|
520
|
+
{
|
|
521
|
+
let name = Self::location_to_string(location);
|
|
522
|
+
let str_id = self.local_graph.intern_string(name);
|
|
523
|
+
let offset = Offset::from_prism_location(location);
|
|
524
|
+
let (comments, flags) = self.find_comments_for(offset.start());
|
|
525
|
+
let parent_nesting_id = self.parent_nesting_id();
|
|
526
|
+
let uri_id = self.uri_id;
|
|
527
|
+
|
|
528
|
+
let definition = builder(str_id, offset, comments, flags, parent_nesting_id, uri_id);
|
|
529
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
530
|
+
|
|
531
|
+
self.add_member_to_current_owner(definition_id);
|
|
532
|
+
|
|
533
|
+
definition_id
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
fn add_instance_variable_definition(&mut self, location: &ruby_prism::Location) -> DefinitionId {
|
|
537
|
+
let name = Self::location_to_string(location);
|
|
538
|
+
let str_id = self.local_graph.intern_string(name);
|
|
539
|
+
let offset = Offset::from_prism_location(location);
|
|
540
|
+
let (comments, flags) = self.find_comments_for(offset.start());
|
|
541
|
+
let parent_nesting_id = self.parent_nesting_id();
|
|
542
|
+
let uri_id = self.uri_id;
|
|
543
|
+
|
|
544
|
+
let definition = Definition::InstanceVariable(Box::new(InstanceVariableDefinition::new(
|
|
545
|
+
str_id,
|
|
546
|
+
uri_id,
|
|
547
|
+
offset,
|
|
548
|
+
comments,
|
|
549
|
+
flags,
|
|
550
|
+
parent_nesting_id,
|
|
551
|
+
)));
|
|
552
|
+
|
|
553
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
554
|
+
self.add_member_to_current_owner(definition_id);
|
|
555
|
+
definition_id
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/// Adds a class variable definition.
|
|
559
|
+
///
|
|
560
|
+
/// Class variables use lexical scoping - they belong to the lexically enclosing class/module,
|
|
561
|
+
/// not the method receiver. This is different from instance variables which follow the receiver.
|
|
562
|
+
fn add_class_variable_definition(&mut self, location: &ruby_prism::Location) -> DefinitionId {
|
|
563
|
+
let name = Self::location_to_string(location);
|
|
564
|
+
let str_id = self.local_graph.intern_string(name);
|
|
565
|
+
let offset = Offset::from_prism_location(location);
|
|
566
|
+
let (comments, flags) = self.find_comments_for(offset.start());
|
|
567
|
+
// Class variables use the enclosing class/module (skipping methods) as lexical nesting
|
|
568
|
+
let lexical_nesting_id = self.parent_lexical_scope_id();
|
|
569
|
+
let uri_id = self.uri_id;
|
|
570
|
+
|
|
571
|
+
let definition = Definition::ClassVariable(Box::new(ClassVariableDefinition::new(
|
|
572
|
+
str_id,
|
|
573
|
+
uri_id,
|
|
574
|
+
offset,
|
|
575
|
+
comments,
|
|
576
|
+
flags,
|
|
577
|
+
lexical_nesting_id,
|
|
578
|
+
)));
|
|
579
|
+
|
|
580
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
581
|
+
self.add_member_to_current_owner(definition_id);
|
|
582
|
+
definition_id
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/// Returns whether a value node represents a method call that could produce a class or module.
|
|
586
|
+
/// Only regular method calls (bare calls or dot/safe-nav calls) are considered promotable.
|
|
587
|
+
/// Operator calls like `1 + 2` are `CallNode`s in Prism but should not be promotable.
|
|
588
|
+
fn is_promotable_value(value: &ruby_prism::Node) -> bool {
|
|
589
|
+
value.as_call_node().is_some_and(|call| {
|
|
590
|
+
// Bare calls (no receiver): `some_factory_call`
|
|
591
|
+
// Dot/safe-nav/scope calls: `Struct.new(...)`, `foo&.bar`, `Struct::new`
|
|
592
|
+
// Excluded: operator calls like `1 + 2` which have a receiver but no call operator
|
|
593
|
+
call.receiver().is_none() || call.call_operator_loc().is_some()
|
|
594
|
+
})
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
fn add_constant_definition(
|
|
598
|
+
&mut self,
|
|
599
|
+
node: &ruby_prism::Node,
|
|
600
|
+
also_add_reference: bool,
|
|
601
|
+
promotable: bool,
|
|
602
|
+
) -> Option<DefinitionId> {
|
|
603
|
+
let name_id = self.index_constant_reference(node, also_add_reference)?;
|
|
604
|
+
|
|
605
|
+
// Get the location for the constant name/path only (not including the value)
|
|
606
|
+
let location = match node {
|
|
607
|
+
ruby_prism::Node::ConstantWriteNode { .. } => node.as_constant_write_node().unwrap().name_loc(),
|
|
608
|
+
ruby_prism::Node::ConstantOrWriteNode { .. } => node.as_constant_or_write_node().unwrap().name_loc(),
|
|
609
|
+
ruby_prism::Node::ConstantPathNode { .. } => node.as_constant_path_node().unwrap().name_loc(),
|
|
610
|
+
_ => node.location(),
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
let offset = Offset::from_prism_location(&location);
|
|
614
|
+
let (comments, mut flags) = self.find_comments_for(offset.start());
|
|
615
|
+
if promotable {
|
|
616
|
+
flags |= DefinitionFlags::PROMOTABLE;
|
|
617
|
+
}
|
|
618
|
+
let lexical_nesting_id = self.parent_lexical_scope_id();
|
|
619
|
+
|
|
620
|
+
let definition = Definition::Constant(Box::new(ConstantDefinition::new(
|
|
621
|
+
name_id,
|
|
622
|
+
self.uri_id,
|
|
623
|
+
offset,
|
|
624
|
+
comments,
|
|
625
|
+
flags,
|
|
626
|
+
lexical_nesting_id,
|
|
627
|
+
)));
|
|
628
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
629
|
+
|
|
630
|
+
self.add_member_to_current_owner(definition_id);
|
|
631
|
+
|
|
632
|
+
Some(definition_id)
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
fn handle_class_definition(
|
|
636
|
+
&mut self,
|
|
637
|
+
location: &ruby_prism::Location,
|
|
638
|
+
name_node: Option<&ruby_prism::Node>,
|
|
639
|
+
body_node: Option<ruby_prism::Node>,
|
|
640
|
+
superclass_node: Option<ruby_prism::Node>,
|
|
641
|
+
nesting_type: fn(DefinitionId) -> Nesting,
|
|
642
|
+
) {
|
|
643
|
+
let offset = Offset::from_prism_location(location);
|
|
644
|
+
let (comments, flags) = self.find_comments_for(offset.start());
|
|
645
|
+
let lexical_nesting_id = self.parent_lexical_scope_id();
|
|
646
|
+
let superclass = superclass_node.as_ref().and_then(|n| {
|
|
647
|
+
// Try direct constant reference first
|
|
648
|
+
if let Some(id) = self.index_constant_reference(n, false) {
|
|
649
|
+
return Some(self.local_graph.add_constant_reference(ConstantReference::new(
|
|
650
|
+
id,
|
|
651
|
+
self.uri_id,
|
|
652
|
+
Offset::from_prism_location(&n.location()),
|
|
653
|
+
)));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// For call nodes (e.g. `ActiveRecord::Migration[7.0]`), try the receiver constant
|
|
657
|
+
if let ruby_prism::Node::CallNode { .. } = n {
|
|
658
|
+
let call = n.as_call_node().unwrap();
|
|
659
|
+
if let Some(receiver) = call.receiver()
|
|
660
|
+
&& let Some(id) = self.index_constant_reference(&receiver, false)
|
|
661
|
+
{
|
|
662
|
+
return Some(self.local_graph.add_constant_reference(ConstantReference::new(
|
|
663
|
+
id,
|
|
664
|
+
self.uri_id,
|
|
665
|
+
Offset::from_prism_location(&receiver.location()),
|
|
666
|
+
)));
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
None
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
if let Some(superclass_node) = superclass_node
|
|
674
|
+
&& superclass.is_none()
|
|
675
|
+
{
|
|
676
|
+
self.local_graph.add_diagnostic(
|
|
677
|
+
Rule::DynamicAncestor,
|
|
678
|
+
Offset::from_prism_location(&superclass_node.location()),
|
|
679
|
+
"Dynamic superclass".to_string(),
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
let (name_id, name_offset) = if let Some(name_node) = name_node {
|
|
684
|
+
let name_loc = match name_node {
|
|
685
|
+
ruby_prism::Node::ConstantPathNode { .. } => name_node.as_constant_path_node().unwrap().name_loc(),
|
|
686
|
+
ruby_prism::Node::ConstantPathWriteNode { .. } => {
|
|
687
|
+
name_node.as_constant_path_write_node().unwrap().target().name_loc()
|
|
688
|
+
}
|
|
689
|
+
_ => name_node.location(),
|
|
690
|
+
};
|
|
691
|
+
(
|
|
692
|
+
self.index_constant_reference(name_node, false),
|
|
693
|
+
Offset::from_prism_location(&name_loc),
|
|
694
|
+
)
|
|
695
|
+
} else {
|
|
696
|
+
let string_id = self
|
|
697
|
+
.local_graph
|
|
698
|
+
.intern_string(format!("{}:{}<anonymous>", self.uri_id, offset.start()));
|
|
699
|
+
|
|
700
|
+
(
|
|
701
|
+
Some(self.local_graph.add_name(Name::new(string_id, ParentScope::None, None))),
|
|
702
|
+
offset.clone(),
|
|
703
|
+
)
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
if let Some(name_id) = name_id {
|
|
707
|
+
let definition = Definition::Class(Box::new(ClassDefinition::new(
|
|
708
|
+
name_id,
|
|
709
|
+
self.uri_id,
|
|
710
|
+
offset.clone(),
|
|
711
|
+
name_offset,
|
|
712
|
+
comments,
|
|
713
|
+
flags,
|
|
714
|
+
lexical_nesting_id,
|
|
715
|
+
superclass,
|
|
716
|
+
)));
|
|
717
|
+
|
|
718
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
719
|
+
|
|
720
|
+
self.add_member_to_current_lexical_scope(definition_id);
|
|
721
|
+
|
|
722
|
+
if let Some(body) = body_node {
|
|
723
|
+
self.nesting_stack.push(nesting_type(definition_id));
|
|
724
|
+
self.visibility_stack
|
|
725
|
+
.push(VisibilityModifier::new(Visibility::Public, false, offset));
|
|
726
|
+
self.visit(&body);
|
|
727
|
+
self.visibility_stack.pop();
|
|
728
|
+
self.nesting_stack.pop();
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
fn handle_module_definition(
|
|
734
|
+
&mut self,
|
|
735
|
+
location: &ruby_prism::Location,
|
|
736
|
+
name_node: Option<&ruby_prism::Node>,
|
|
737
|
+
body_node: Option<ruby_prism::Node>,
|
|
738
|
+
nesting_type: fn(DefinitionId) -> Nesting,
|
|
739
|
+
) {
|
|
740
|
+
let offset = Offset::from_prism_location(location);
|
|
741
|
+
let (comments, flags) = self.find_comments_for(offset.start());
|
|
742
|
+
let lexical_nesting_id = self.parent_lexical_scope_id();
|
|
743
|
+
|
|
744
|
+
let (name_id, name_offset) = if let Some(name_node) = name_node {
|
|
745
|
+
let name_loc = match name_node {
|
|
746
|
+
ruby_prism::Node::ConstantPathNode { .. } => name_node.as_constant_path_node().unwrap().name_loc(),
|
|
747
|
+
ruby_prism::Node::ConstantPathWriteNode { .. } => {
|
|
748
|
+
name_node.as_constant_path_write_node().unwrap().target().name_loc()
|
|
749
|
+
}
|
|
750
|
+
_ => name_node.location(),
|
|
751
|
+
};
|
|
752
|
+
(
|
|
753
|
+
self.index_constant_reference(name_node, false),
|
|
754
|
+
Offset::from_prism_location(&name_loc),
|
|
755
|
+
)
|
|
756
|
+
} else {
|
|
757
|
+
let string_id = self
|
|
758
|
+
.local_graph
|
|
759
|
+
.intern_string(format!("{}:{}<anonymous>", self.uri_id, offset.start()));
|
|
760
|
+
|
|
761
|
+
(
|
|
762
|
+
Some(self.local_graph.add_name(Name::new(string_id, ParentScope::None, None))),
|
|
763
|
+
offset.clone(),
|
|
764
|
+
)
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
if let Some(name_id) = name_id {
|
|
768
|
+
let definition = Definition::Module(Box::new(ModuleDefinition::new(
|
|
769
|
+
name_id,
|
|
770
|
+
self.uri_id,
|
|
771
|
+
offset.clone(),
|
|
772
|
+
name_offset,
|
|
773
|
+
comments,
|
|
774
|
+
flags,
|
|
775
|
+
lexical_nesting_id,
|
|
776
|
+
)));
|
|
777
|
+
|
|
778
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
779
|
+
|
|
780
|
+
self.add_member_to_current_lexical_scope(definition_id);
|
|
781
|
+
|
|
782
|
+
if let Some(body) = body_node {
|
|
783
|
+
self.nesting_stack.push(nesting_type(definition_id));
|
|
784
|
+
self.visibility_stack
|
|
785
|
+
.push(VisibilityModifier::new(Visibility::Public, false, offset));
|
|
786
|
+
self.visit(&body);
|
|
787
|
+
self.visibility_stack.pop();
|
|
788
|
+
self.nesting_stack.pop();
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/// Handle dynamic class or module definitions, like `Module.new`, `Class.new`, `Data.define` and so on
|
|
794
|
+
fn handle_dynamic_class_or_module(&mut self, node: &ruby_prism::Node, value: &ruby_prism::Node) -> bool {
|
|
795
|
+
let Some(call_node) = value.as_call_node() else {
|
|
796
|
+
return false;
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
if call_node.name().as_slice() != b"new" {
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
let Some(receiver) = call_node.receiver() else {
|
|
804
|
+
return false;
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
let receiver_name = receiver.location().as_slice();
|
|
808
|
+
|
|
809
|
+
if matches!(receiver_name, b"Module" | b"::Module") {
|
|
810
|
+
self.handle_module_definition(&node.location(), Some(node), call_node.block(), Nesting::Owner);
|
|
811
|
+
} else if matches!(receiver_name, b"Class" | b"::Class") {
|
|
812
|
+
self.handle_class_definition(
|
|
813
|
+
&node.location(),
|
|
814
|
+
Some(node),
|
|
815
|
+
call_node.block(),
|
|
816
|
+
call_node.arguments().and_then(|args| args.arguments().iter().next()),
|
|
817
|
+
Nesting::Owner,
|
|
818
|
+
);
|
|
819
|
+
} else {
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
self.index_method_reference_for_call(&call_node);
|
|
824
|
+
true
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/// Returns the definition ID of the current nesting (class, module, or singleton class),
|
|
828
|
+
/// but skips methods in the definitions stack.
|
|
829
|
+
fn current_nesting_definition_id(&self) -> Option<DefinitionId> {
|
|
830
|
+
self.nesting_stack.iter().rev().find_map(|nesting| match nesting {
|
|
831
|
+
Nesting::LexicalScope(id) | Nesting::Owner(id) => Some(*id),
|
|
832
|
+
Nesting::Method(_) => None,
|
|
833
|
+
})
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
fn current_nesting_is_module(&self) -> bool {
|
|
837
|
+
self.current_nesting_definition_id().is_some_and(|id| {
|
|
838
|
+
self.local_graph
|
|
839
|
+
.definitions()
|
|
840
|
+
.get(&id)
|
|
841
|
+
.is_some_and(|def| matches!(def, Definition::Module(_)))
|
|
842
|
+
})
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/// Indexes the final constant target from a value node, unwrapping chained assignments.
|
|
846
|
+
///
|
|
847
|
+
/// For `A = B = C`, when processing `A`, the value is `ConstantWriteNode(B)`.
|
|
848
|
+
/// This function recursively unwraps to find the final `ConstantReadNode(C)` and indexes it.
|
|
849
|
+
///
|
|
850
|
+
/// Returns `Some(NameId)` if the final value is a constant (`ConstantReadNode` or `ConstantPathNode`),
|
|
851
|
+
/// or `None` if the chain ends in a non-constant value.
|
|
852
|
+
fn index_constant_alias_target(&mut self, value: &ruby_prism::Node) -> Option<NameId> {
|
|
853
|
+
match value {
|
|
854
|
+
ruby_prism::Node::ConstantReadNode { .. } | ruby_prism::Node::ConstantPathNode { .. } => {
|
|
855
|
+
self.index_constant_reference(value, true)
|
|
856
|
+
}
|
|
857
|
+
ruby_prism::Node::ConstantWriteNode { .. } => {
|
|
858
|
+
let node = value.as_constant_write_node().unwrap();
|
|
859
|
+
let target_name_id = self.index_constant_alias_target(&node.value())?;
|
|
860
|
+
self.add_constant_alias_definition(value, target_name_id, false);
|
|
861
|
+
Some(target_name_id)
|
|
862
|
+
}
|
|
863
|
+
ruby_prism::Node::ConstantOrWriteNode { .. } => {
|
|
864
|
+
let node = value.as_constant_or_write_node().unwrap();
|
|
865
|
+
let target_name_id = self.index_constant_alias_target(&node.value())?;
|
|
866
|
+
self.add_constant_alias_definition(value, target_name_id, false);
|
|
867
|
+
Some(target_name_id)
|
|
868
|
+
}
|
|
869
|
+
ruby_prism::Node::ConstantPathWriteNode { .. } => {
|
|
870
|
+
let node = value.as_constant_path_write_node().unwrap();
|
|
871
|
+
let target_name_id = self.index_constant_alias_target(&node.value())?;
|
|
872
|
+
self.add_constant_alias_definition(&node.target().as_node(), target_name_id, false);
|
|
873
|
+
Some(target_name_id)
|
|
874
|
+
}
|
|
875
|
+
ruby_prism::Node::ConstantPathOrWriteNode { .. } => {
|
|
876
|
+
let node = value.as_constant_path_or_write_node().unwrap();
|
|
877
|
+
let target_name_id = self.index_constant_alias_target(&node.value())?;
|
|
878
|
+
self.add_constant_alias_definition(&node.target().as_node(), target_name_id, true);
|
|
879
|
+
Some(target_name_id)
|
|
880
|
+
}
|
|
881
|
+
_ => None,
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
fn add_constant_alias_definition(
|
|
886
|
+
&mut self,
|
|
887
|
+
name_node: &ruby_prism::Node,
|
|
888
|
+
target_name_id: NameId,
|
|
889
|
+
also_add_reference: bool,
|
|
890
|
+
) -> Option<DefinitionId> {
|
|
891
|
+
let name_id = self.index_constant_reference(name_node, also_add_reference)?;
|
|
892
|
+
|
|
893
|
+
// Get the location for just the constant name (not including the namespace or value).
|
|
894
|
+
let location = match name_node {
|
|
895
|
+
ruby_prism::Node::ConstantWriteNode { .. } => name_node.as_constant_write_node().unwrap().name_loc(),
|
|
896
|
+
ruby_prism::Node::ConstantOrWriteNode { .. } => name_node.as_constant_or_write_node().unwrap().name_loc(),
|
|
897
|
+
ruby_prism::Node::ConstantPathNode { .. } => name_node.as_constant_path_node().unwrap().name_loc(),
|
|
898
|
+
_ => name_node.location(),
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
let offset = Offset::from_prism_location(&location);
|
|
902
|
+
let (comments, flags) = self.find_comments_for(offset.start());
|
|
903
|
+
let lexical_nesting_id = self.parent_lexical_scope_id();
|
|
904
|
+
|
|
905
|
+
let alias_constant = ConstantDefinition::new(name_id, self.uri_id, offset, comments, flags, lexical_nesting_id);
|
|
906
|
+
let definition =
|
|
907
|
+
Definition::ConstantAlias(Box::new(ConstantAliasDefinition::new(target_name_id, alias_constant)));
|
|
908
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
909
|
+
|
|
910
|
+
self.add_member_to_current_owner(definition_id);
|
|
911
|
+
|
|
912
|
+
Some(definition_id)
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/// Adds a member to the current owner (class, module, or singleton class).
|
|
916
|
+
///
|
|
917
|
+
/// Iterates through the definitions stack in reverse to find the first class/module/singleton
|
|
918
|
+
/// class, skipping methods, and adds the member to it.
|
|
919
|
+
fn add_member_to_current_owner(&mut self, member_id: DefinitionId) {
|
|
920
|
+
let Some(owner_id) = self.current_nesting_definition_id() else {
|
|
921
|
+
return;
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
let owner = self
|
|
925
|
+
.local_graph
|
|
926
|
+
.get_definition_mut(owner_id)
|
|
927
|
+
.expect("owner definition should exist");
|
|
928
|
+
|
|
929
|
+
match owner {
|
|
930
|
+
Definition::Class(class) => class.add_member(member_id),
|
|
931
|
+
Definition::SingletonClass(singleton_class) => singleton_class.add_member(member_id),
|
|
932
|
+
Definition::Module(module) => module.add_member(member_id),
|
|
933
|
+
_ => unreachable!("find above only matches anonymous/class/module/singleton"),
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/// Adds a member to the current lexical scope
|
|
938
|
+
///
|
|
939
|
+
/// Iterates through the definitions stack in reverse to find the first class/module/singleton class, skipping
|
|
940
|
+
/// methods, and adds the member to it. Ignores owner nestings such as Class.new
|
|
941
|
+
fn add_member_to_current_lexical_scope(&mut self, member_id: DefinitionId) {
|
|
942
|
+
let Some(owner_id) = self.parent_lexical_scope_id() else {
|
|
943
|
+
return;
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
let owner = self
|
|
947
|
+
.local_graph
|
|
948
|
+
.get_definition_mut(owner_id)
|
|
949
|
+
.expect("owner definition should exist");
|
|
950
|
+
|
|
951
|
+
match owner {
|
|
952
|
+
Definition::Class(class) => class.add_member(member_id),
|
|
953
|
+
Definition::SingletonClass(singleton_class) => singleton_class.add_member(member_id),
|
|
954
|
+
Definition::Module(module) => module.add_member(member_id),
|
|
955
|
+
_ => unreachable!("find above only matches class/module/singleton"),
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
fn handle_mixin(&mut self, node: &ruby_prism::CallNode, mixin_type: MixinType) {
|
|
960
|
+
let Some(arguments) = node.arguments() else {
|
|
961
|
+
return;
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
let parent_nesting_id = self.current_nesting_definition_id();
|
|
965
|
+
|
|
966
|
+
// Collect all arguments as constant references. Ignore anything that isn't a constant
|
|
967
|
+
let mixin_arguments = arguments
|
|
968
|
+
.arguments()
|
|
969
|
+
.iter()
|
|
970
|
+
.filter_map(|arg| {
|
|
971
|
+
if arg.as_self_node().is_some() {
|
|
972
|
+
if parent_nesting_id.is_none() {
|
|
973
|
+
self.local_graph.add_diagnostic(
|
|
974
|
+
Rule::TopLevelMixinSelf,
|
|
975
|
+
Offset::from_prism_location(&arg.location()),
|
|
976
|
+
"Top level mixin self".to_string(),
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
return None;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
Some((
|
|
983
|
+
self.current_lexical_scope_name_id().unwrap(),
|
|
984
|
+
Offset::from_prism_location(&arg.location()),
|
|
985
|
+
))
|
|
986
|
+
} else if let Some(name_id) = self.index_constant_reference(&arg, false) {
|
|
987
|
+
Some((name_id, Offset::from_prism_location(&arg.location())))
|
|
988
|
+
} else {
|
|
989
|
+
self.local_graph.add_diagnostic(
|
|
990
|
+
Rule::DynamicAncestor,
|
|
991
|
+
Offset::from_prism_location(&arg.location()),
|
|
992
|
+
"Dynamic mixin argument".to_string(),
|
|
993
|
+
);
|
|
994
|
+
|
|
995
|
+
None
|
|
996
|
+
}
|
|
997
|
+
})
|
|
998
|
+
.collect::<Vec<(NameId, Offset)>>();
|
|
999
|
+
|
|
1000
|
+
if mixin_arguments.is_empty() {
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
let Some(lexical_nesting_id) = parent_nesting_id else {
|
|
1005
|
+
return;
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
// Mixin operations with multiple arguments are inserted in reverse, so that they are processed in the expected
|
|
1009
|
+
// order by resolution
|
|
1010
|
+
for (id, offset) in mixin_arguments.into_iter().rev() {
|
|
1011
|
+
let constant_ref_id =
|
|
1012
|
+
self.local_graph
|
|
1013
|
+
.add_constant_reference(ConstantReference::new(id, self.uri_id, offset));
|
|
1014
|
+
|
|
1015
|
+
let mixin = match mixin_type {
|
|
1016
|
+
MixinType::Include => Mixin::Include(IncludeDefinition::new(constant_ref_id)),
|
|
1017
|
+
MixinType::Prepend => Mixin::Prepend(PrependDefinition::new(constant_ref_id)),
|
|
1018
|
+
MixinType::Extend => Mixin::Extend(ExtendDefinition::new(constant_ref_id)),
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
match self.local_graph.get_definition_mut(lexical_nesting_id).unwrap() {
|
|
1022
|
+
Definition::Class(class_def) => class_def.add_mixin(mixin),
|
|
1023
|
+
Definition::Module(module_def) => module_def.add_mixin(mixin),
|
|
1024
|
+
Definition::SingletonClass(singleton_class_def) => singleton_class_def.add_mixin(mixin),
|
|
1025
|
+
_ => {}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/// Indexes a method reference for a call node, creating constant references for the receiver when applicable.
|
|
1031
|
+
fn index_method_reference_for_call(&mut self, node: &ruby_prism::CallNode) {
|
|
1032
|
+
let method_receiver = self.method_receiver(node.receiver().as_ref(), node.location());
|
|
1033
|
+
|
|
1034
|
+
if method_receiver.is_none()
|
|
1035
|
+
&& let Some(receiver) = node.receiver()
|
|
1036
|
+
{
|
|
1037
|
+
self.visit(&receiver);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
let message = String::from_utf8_lossy(node.name().as_slice()).to_string();
|
|
1041
|
+
self.index_method_reference(message, &node.message_loc().unwrap(), method_receiver);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/// Visits every part of a call node, except for the message itself. Convenient for when we're only interested in
|
|
1045
|
+
/// continuing the traversal
|
|
1046
|
+
fn visit_call_node_parts(&mut self, node: &ruby_prism::CallNode) {
|
|
1047
|
+
if let Some(receiver) = node.receiver() {
|
|
1048
|
+
self.visit(&receiver);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if let Some(arguments) = node.arguments() {
|
|
1052
|
+
self.visit_arguments_node(&arguments);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
if let Some(block) = node.block() {
|
|
1056
|
+
self.visit(&block);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
#[must_use]
|
|
1061
|
+
fn parent_lexical_scope_id(&self) -> Option<DefinitionId> {
|
|
1062
|
+
self.nesting_stack.iter().rev().find_map(|nesting| match nesting {
|
|
1063
|
+
Nesting::LexicalScope(id) => Some(*id),
|
|
1064
|
+
Nesting::Owner(_) | Nesting::Method(_) => None,
|
|
1065
|
+
})
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
#[must_use]
|
|
1069
|
+
fn parent_nesting_id(&self) -> Option<DefinitionId> {
|
|
1070
|
+
self.nesting_stack.last().map(Nesting::id)
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
#[must_use]
|
|
1074
|
+
fn current_visibility(&self) -> &VisibilityModifier {
|
|
1075
|
+
self.visibility_stack
|
|
1076
|
+
.last()
|
|
1077
|
+
.expect("visibility stack should not be empty")
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
fn method_receiver(
|
|
1081
|
+
&mut self,
|
|
1082
|
+
receiver: Option<&ruby_prism::Node>,
|
|
1083
|
+
fallback_location: ruby_prism::Location,
|
|
1084
|
+
) -> Option<NameId> {
|
|
1085
|
+
let mut is_singleton_name = false;
|
|
1086
|
+
|
|
1087
|
+
let name_id = match receiver {
|
|
1088
|
+
Some(ruby_prism::Node::SelfNode { .. }) | None => {
|
|
1089
|
+
// Implicit or explicit self receiver
|
|
1090
|
+
|
|
1091
|
+
match self.nesting_stack.last() {
|
|
1092
|
+
Some(Nesting::LexicalScope(id) | Nesting::Owner(id)) => {
|
|
1093
|
+
let definition = self
|
|
1094
|
+
.local_graph
|
|
1095
|
+
.definitions()
|
|
1096
|
+
.get(id)
|
|
1097
|
+
.expect("Nesting definition should exist");
|
|
1098
|
+
|
|
1099
|
+
match definition {
|
|
1100
|
+
Definition::Class(class_def) => {
|
|
1101
|
+
is_singleton_name = true;
|
|
1102
|
+
Some(*class_def.name_id())
|
|
1103
|
+
}
|
|
1104
|
+
Definition::Module(module_def) => {
|
|
1105
|
+
is_singleton_name = true;
|
|
1106
|
+
Some(*module_def.name_id())
|
|
1107
|
+
}
|
|
1108
|
+
Definition::SingletonClass(singleton_class_def) => {
|
|
1109
|
+
is_singleton_name = true;
|
|
1110
|
+
Some(*singleton_class_def.name_id())
|
|
1111
|
+
}
|
|
1112
|
+
Definition::Method(_) => None,
|
|
1113
|
+
_ => panic!("current nesting is not a class/module/singleton class: {definition:?}"),
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
Some(Nesting::Method(id)) => {
|
|
1117
|
+
// If we're inside a method definition, we need to check what its receiver is as that changes the type of `self`
|
|
1118
|
+
let Some(Definition::Method(definition)) = self.local_graph.definitions().get(id) else {
|
|
1119
|
+
unreachable!("method definition for nesting should exist")
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
if let Some(receiver) = definition.receiver() {
|
|
1123
|
+
is_singleton_name = true;
|
|
1124
|
+
match receiver {
|
|
1125
|
+
Receiver::SelfReceiver(def_id) => self
|
|
1126
|
+
.local_graph
|
|
1127
|
+
.definitions()
|
|
1128
|
+
.get(def_id)
|
|
1129
|
+
.and_then(Definition::name_id)
|
|
1130
|
+
.copied(),
|
|
1131
|
+
Receiver::ConstantReceiver(name_id) => Some(*name_id),
|
|
1132
|
+
}
|
|
1133
|
+
} else {
|
|
1134
|
+
self.current_owner_name_id()
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
None => {
|
|
1138
|
+
let str_id = self.local_graph.intern_string("Object".into());
|
|
1139
|
+
Some(self.local_graph.add_name(Name::new(str_id, ParentScope::None, None)))
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
Some(ruby_prism::Node::CallNode { .. }) => {
|
|
1144
|
+
// Check if the receiver is `singleton_class`
|
|
1145
|
+
let call_node = receiver.unwrap().as_call_node().unwrap();
|
|
1146
|
+
|
|
1147
|
+
if call_node.name().as_slice() == b"singleton_class" {
|
|
1148
|
+
is_singleton_name = true;
|
|
1149
|
+
self.method_receiver(call_node.receiver().as_ref(), call_node.location())
|
|
1150
|
+
} else {
|
|
1151
|
+
None
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
Some(node) => {
|
|
1155
|
+
is_singleton_name = true;
|
|
1156
|
+
self.index_constant_reference(node, true)
|
|
1157
|
+
}
|
|
1158
|
+
}?;
|
|
1159
|
+
|
|
1160
|
+
if !is_singleton_name {
|
|
1161
|
+
return Some(name_id);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
let singleton_class_name = {
|
|
1165
|
+
let name = self
|
|
1166
|
+
.local_graph
|
|
1167
|
+
.names()
|
|
1168
|
+
.get(&name_id)
|
|
1169
|
+
.expect("Indexed constant name should exist");
|
|
1170
|
+
|
|
1171
|
+
let target_str = self
|
|
1172
|
+
.local_graph
|
|
1173
|
+
.strings()
|
|
1174
|
+
.get(name.str())
|
|
1175
|
+
.expect("Indexed constant string should exist");
|
|
1176
|
+
|
|
1177
|
+
format!("<{}>", target_str.as_str())
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
let string_id = self.local_graph.intern_string(singleton_class_name);
|
|
1181
|
+
let new_name_id = self
|
|
1182
|
+
.local_graph
|
|
1183
|
+
.add_name(Name::new(string_id, ParentScope::Attached(name_id), None));
|
|
1184
|
+
|
|
1185
|
+
let location = receiver.map_or(fallback_location, ruby_prism::Node::location);
|
|
1186
|
+
let offset = Offset::from_prism_location(&location);
|
|
1187
|
+
self.local_graph
|
|
1188
|
+
.add_constant_reference(ConstantReference::new(new_name_id, self.uri_id, offset));
|
|
1189
|
+
Some(new_name_id)
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
fn handle_constant_visibility(&mut self, node: &ruby_prism::CallNode, visibility: Visibility) {
|
|
1193
|
+
let receiver = node.receiver();
|
|
1194
|
+
|
|
1195
|
+
let receiver_name_id = match receiver {
|
|
1196
|
+
Some(ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. }) => {
|
|
1197
|
+
self.index_constant_reference(&receiver.unwrap(), true)
|
|
1198
|
+
}
|
|
1199
|
+
Some(ruby_prism::Node::SelfNode { .. }) | None => match self.nesting_stack.last() {
|
|
1200
|
+
Some(Nesting::Method(_)) => {
|
|
1201
|
+
// Dynamic private constant (called from a method), we ignore it but don't report an error since it's valid Ruby
|
|
1202
|
+
// if being called from a singleton method.
|
|
1203
|
+
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
None => {
|
|
1207
|
+
self.local_graph.add_diagnostic(
|
|
1208
|
+
Rule::InvalidPrivateConstant,
|
|
1209
|
+
Offset::from_prism_location(&node.location()),
|
|
1210
|
+
"Private constant called at top level".to_string(),
|
|
1211
|
+
);
|
|
1212
|
+
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
_ => None,
|
|
1216
|
+
},
|
|
1217
|
+
_ => {
|
|
1218
|
+
self.local_graph.add_diagnostic(
|
|
1219
|
+
Rule::InvalidPrivateConstant,
|
|
1220
|
+
Offset::from_prism_location(&node.location()),
|
|
1221
|
+
"Dynamic receiver for private constant".to_string(),
|
|
1222
|
+
);
|
|
1223
|
+
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
let Some(arguments) = node.arguments() else {
|
|
1229
|
+
return;
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
for argument in &arguments.arguments() {
|
|
1233
|
+
let (name, location) = match argument {
|
|
1234
|
+
ruby_prism::Node::SymbolNode { .. } => {
|
|
1235
|
+
let symbol = argument.as_symbol_node().unwrap();
|
|
1236
|
+
if let Some(value_loc) = symbol.value_loc() {
|
|
1237
|
+
(Self::location_to_string(&value_loc), value_loc)
|
|
1238
|
+
} else {
|
|
1239
|
+
continue;
|
|
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
|
+
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1258
|
+
let str_id = self.local_graph.intern_string(name);
|
|
1259
|
+
let offset = Offset::from_prism_location(&location);
|
|
1260
|
+
let definition = Definition::ConstantVisibility(Box::new(ConstantVisibilityDefinition::new(
|
|
1261
|
+
self.local_graph.add_name(Name::new(
|
|
1262
|
+
str_id,
|
|
1263
|
+
receiver_name_id.map_or(ParentScope::None, ParentScope::Some),
|
|
1264
|
+
self.current_lexical_scope_name_id(),
|
|
1265
|
+
)),
|
|
1266
|
+
visibility,
|
|
1267
|
+
self.uri_id,
|
|
1268
|
+
offset,
|
|
1269
|
+
Box::default(),
|
|
1270
|
+
DefinitionFlags::empty(),
|
|
1271
|
+
self.current_nesting_definition_id(),
|
|
1272
|
+
)));
|
|
1273
|
+
|
|
1274
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
1275
|
+
|
|
1276
|
+
self.add_member_to_current_owner(definition_id);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
fn is_attr_call(arg: &ruby_prism::Node) -> bool {
|
|
1281
|
+
arg.as_call_node().is_some_and(|call| {
|
|
1282
|
+
let receiver = call.receiver();
|
|
1283
|
+
let bare_or_self = receiver.is_none() || receiver.as_ref().is_some_and(|r| r.as_self_node().is_some());
|
|
1284
|
+
bare_or_self
|
|
1285
|
+
&& matches!(
|
|
1286
|
+
call.name().as_slice(),
|
|
1287
|
+
b"attr" | b"attr_reader" | b"attr_writer" | b"attr_accessor"
|
|
1288
|
+
)
|
|
1289
|
+
})
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/// Classifies each visibility argument and applies visibility left-to-right:
|
|
1293
|
+
/// - `DefNode`: inline visibility (always valid)
|
|
1294
|
+
/// - Sole attr_* call: inline visibility (multi-arg attr_* is unsupported — returns array)
|
|
1295
|
+
/// - `SymbolNode`/`StringNode`: retroactive `MethodVisibilityDefinition`
|
|
1296
|
+
/// - Anything else: per-arg diagnostic
|
|
1297
|
+
fn handle_visibility_arguments(
|
|
1298
|
+
&mut self,
|
|
1299
|
+
arguments: &ruby_prism::ArgumentsNode,
|
|
1300
|
+
visibility: Visibility,
|
|
1301
|
+
call_offset: &Offset,
|
|
1302
|
+
call_name: &str,
|
|
1303
|
+
) {
|
|
1304
|
+
let args = arguments.arguments();
|
|
1305
|
+
let arg_count = args.len();
|
|
1306
|
+
|
|
1307
|
+
for arg in &args {
|
|
1308
|
+
if matches!(arg, ruby_prism::Node::DefNode { .. }) || (arg_count == 1 && Self::is_attr_call(&arg)) {
|
|
1309
|
+
self.visibility_stack
|
|
1310
|
+
.push(VisibilityModifier::new(visibility, true, call_offset.clone()));
|
|
1311
|
+
self.visit(&arg);
|
|
1312
|
+
self.visibility_stack.pop();
|
|
1313
|
+
} else if matches!(
|
|
1314
|
+
arg,
|
|
1315
|
+
ruby_prism::Node::SymbolNode { .. } | ruby_prism::Node::StringNode { .. }
|
|
1316
|
+
) {
|
|
1317
|
+
self.create_method_visibility_definition(&arg, visibility);
|
|
1318
|
+
} else {
|
|
1319
|
+
// Unsupported arg — diagnostic + visit for side effects.
|
|
1320
|
+
let arg_offset = Offset::from_prism_location(&arg.location());
|
|
1321
|
+
let message = if Self::is_attr_call(&arg) {
|
|
1322
|
+
format!("`{call_name}` with `attr_*` is only supported as a single argument")
|
|
1323
|
+
} else {
|
|
1324
|
+
format!("`{call_name}` called with a non-literal argument")
|
|
1325
|
+
};
|
|
1326
|
+
self.local_graph
|
|
1327
|
+
.add_diagnostic(Rule::InvalidMethodVisibility, arg_offset, message);
|
|
1328
|
+
self.visit(&arg);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
fn create_method_visibility_definition(&mut self, arg: &ruby_prism::Node, visibility: Visibility) {
|
|
1334
|
+
let (name, location) = match arg {
|
|
1335
|
+
ruby_prism::Node::SymbolNode { .. } => {
|
|
1336
|
+
let symbol = arg.as_symbol_node().unwrap();
|
|
1337
|
+
if let Some(value_loc) = symbol.value_loc() {
|
|
1338
|
+
(Self::location_to_string(&value_loc), value_loc)
|
|
1339
|
+
} else {
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
ruby_prism::Node::StringNode { .. } => {
|
|
1344
|
+
let string = arg.as_string_node().unwrap();
|
|
1345
|
+
let name = String::from_utf8_lossy(string.unescaped()).to_string();
|
|
1346
|
+
(name, arg.location())
|
|
1347
|
+
}
|
|
1348
|
+
_ => return,
|
|
1349
|
+
};
|
|
1350
|
+
|
|
1351
|
+
let str_id = self.local_graph.intern_string(format!("{name}()"));
|
|
1352
|
+
let arg_offset = Offset::from_prism_location(&location);
|
|
1353
|
+
let definition = Definition::MethodVisibility(Box::new(MethodVisibilityDefinition::new(
|
|
1354
|
+
str_id,
|
|
1355
|
+
visibility,
|
|
1356
|
+
self.uri_id,
|
|
1357
|
+
arg_offset,
|
|
1358
|
+
Box::default(),
|
|
1359
|
+
DefinitionFlags::empty(),
|
|
1360
|
+
self.current_nesting_definition_id(),
|
|
1361
|
+
)));
|
|
1362
|
+
|
|
1363
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
1364
|
+
self.add_member_to_current_owner(definition_id);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
struct CommentGroup {
|
|
1369
|
+
end_offset: usize,
|
|
1370
|
+
comments: Vec<Comment>,
|
|
1371
|
+
deprecated: bool,
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
impl CommentGroup {
|
|
1375
|
+
#[must_use]
|
|
1376
|
+
pub fn new() -> Self {
|
|
1377
|
+
Self {
|
|
1378
|
+
end_offset: 0,
|
|
1379
|
+
comments: Vec::new(),
|
|
1380
|
+
deprecated: false,
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// Accepts the next line if it is continuous
|
|
1385
|
+
fn accepts(&self, next: &ruby_prism::Comment, source: &str) -> bool {
|
|
1386
|
+
let current_end_offset = self.end_offset;
|
|
1387
|
+
let next_line_start_offset = next.location().start_offset();
|
|
1388
|
+
|
|
1389
|
+
let between = &source.as_bytes()[current_end_offset..next_line_start_offset];
|
|
1390
|
+
if !between.iter().all(|&b| b.is_ascii_whitespace()) {
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// If there is at most one newline between the two texts,
|
|
1395
|
+
// that means two texts are continuous
|
|
1396
|
+
bytecount::count(between, b'\n') <= 1
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// For the magic comments, what we want to do is the following:
|
|
1400
|
+
// 1. still move the group end offset to the end of the magic comment
|
|
1401
|
+
// 2. not add the comment to the comments array
|
|
1402
|
+
fn add_comment(&mut self, comment: &ruby_prism::Comment) {
|
|
1403
|
+
self.end_offset = comment.location().end_offset();
|
|
1404
|
+
let text = String::from_utf8_lossy(comment.location().as_slice()).to_string();
|
|
1405
|
+
|
|
1406
|
+
if text.lines().any(|line| line.starts_with("# @deprecated")) {
|
|
1407
|
+
self.deprecated = true;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
self.comments.push(Comment::new(
|
|
1411
|
+
Offset::from_prism_location(&comment.location()),
|
|
1412
|
+
text.trim().to_string(),
|
|
1413
|
+
));
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
fn comments(&self) -> Box<[Comment]> {
|
|
1417
|
+
self.comments.clone().into_boxed_slice()
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
fn flags(&self) -> DefinitionFlags {
|
|
1421
|
+
if self.deprecated {
|
|
1422
|
+
DefinitionFlags::DEPRECATED
|
|
1423
|
+
} else {
|
|
1424
|
+
DefinitionFlags::empty()
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
impl Visit<'_> for RubyIndexer<'_> {
|
|
1430
|
+
fn visit_class_node(&mut self, node: &ruby_prism::ClassNode<'_>) {
|
|
1431
|
+
self.handle_class_definition(
|
|
1432
|
+
&node.location(),
|
|
1433
|
+
Some(&node.constant_path()),
|
|
1434
|
+
node.body(),
|
|
1435
|
+
node.superclass(),
|
|
1436
|
+
Nesting::LexicalScope,
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
fn visit_module_node(&mut self, node: &ruby_prism::ModuleNode) {
|
|
1441
|
+
self.handle_module_definition(
|
|
1442
|
+
&node.location(),
|
|
1443
|
+
Some(&node.constant_path()),
|
|
1444
|
+
node.body(),
|
|
1445
|
+
Nesting::LexicalScope,
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
fn visit_singleton_class_node(&mut self, node: &ruby_prism::SingletonClassNode) {
|
|
1450
|
+
let expression = node.expression();
|
|
1451
|
+
|
|
1452
|
+
// Determine the attached_target for the singleton class and the name_offset
|
|
1453
|
+
let (attached_target, name_offset) = if expression.as_self_node().is_some() {
|
|
1454
|
+
// `class << self` - resolve self to current class/module's NameId
|
|
1455
|
+
// name_offset points to "self"
|
|
1456
|
+
(
|
|
1457
|
+
self.current_lexical_scope_name_id(),
|
|
1458
|
+
Offset::from_prism_location(&expression.location()),
|
|
1459
|
+
)
|
|
1460
|
+
} else if matches!(
|
|
1461
|
+
expression,
|
|
1462
|
+
ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. }
|
|
1463
|
+
) {
|
|
1464
|
+
// `class << Foo` or `class << Foo::Bar` - use the constant's NameId
|
|
1465
|
+
// name_offset points to the expression (the constant reference)
|
|
1466
|
+
(
|
|
1467
|
+
self.index_constant_reference(&expression, true),
|
|
1468
|
+
Offset::from_prism_location(&expression.location()),
|
|
1469
|
+
)
|
|
1470
|
+
} else {
|
|
1471
|
+
// Dynamic expression (e.g., `class << some_var`) - skip creating definition
|
|
1472
|
+
self.visit(&expression);
|
|
1473
|
+
self.local_graph.add_diagnostic(
|
|
1474
|
+
Rule::DynamicSingletonDefinition,
|
|
1475
|
+
Offset::from_prism_location(&node.location()),
|
|
1476
|
+
"Dynamic singleton class definition".to_string(),
|
|
1477
|
+
);
|
|
1478
|
+
return;
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1481
|
+
let Some(attached_target) = attached_target else {
|
|
1482
|
+
self.local_graph.add_diagnostic(
|
|
1483
|
+
Rule::DynamicSingletonDefinition,
|
|
1484
|
+
Offset::from_prism_location(&node.location()),
|
|
1485
|
+
"Dynamic singleton class definition".to_string(),
|
|
1486
|
+
);
|
|
1487
|
+
|
|
1488
|
+
return;
|
|
1489
|
+
};
|
|
1490
|
+
|
|
1491
|
+
let offset = Offset::from_prism_location(&node.location());
|
|
1492
|
+
let (comments, flags) = self.find_comments_for(offset.start());
|
|
1493
|
+
let lexical_nesting_id = self.parent_lexical_scope_id();
|
|
1494
|
+
|
|
1495
|
+
let singleton_class_name = {
|
|
1496
|
+
let name = self
|
|
1497
|
+
.local_graph
|
|
1498
|
+
.names()
|
|
1499
|
+
.get(&attached_target)
|
|
1500
|
+
.expect("Attached target name should exist");
|
|
1501
|
+
let target_str = self
|
|
1502
|
+
.local_graph
|
|
1503
|
+
.strings()
|
|
1504
|
+
.get(name.str())
|
|
1505
|
+
.expect("Attached target string should exist");
|
|
1506
|
+
format!("<{}>", target_str.as_str())
|
|
1507
|
+
};
|
|
1508
|
+
|
|
1509
|
+
let string_id = self.local_graph.intern_string(singleton_class_name);
|
|
1510
|
+
let name_id = self
|
|
1511
|
+
.local_graph
|
|
1512
|
+
.add_name(Name::new(string_id, ParentScope::Attached(attached_target), None));
|
|
1513
|
+
|
|
1514
|
+
let definition = Definition::SingletonClass(Box::new(SingletonClassDefinition::new(
|
|
1515
|
+
name_id,
|
|
1516
|
+
self.uri_id,
|
|
1517
|
+
offset.clone(),
|
|
1518
|
+
name_offset,
|
|
1519
|
+
comments,
|
|
1520
|
+
flags,
|
|
1521
|
+
lexical_nesting_id,
|
|
1522
|
+
)));
|
|
1523
|
+
|
|
1524
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
1525
|
+
|
|
1526
|
+
self.add_member_to_current_owner(definition_id);
|
|
1527
|
+
|
|
1528
|
+
if let Some(body) = node.body() {
|
|
1529
|
+
self.nesting_stack.push(Nesting::LexicalScope(definition_id));
|
|
1530
|
+
self.visibility_stack
|
|
1531
|
+
.push(VisibilityModifier::new(Visibility::Public, false, offset));
|
|
1532
|
+
self.visit(&body);
|
|
1533
|
+
self.visibility_stack.pop();
|
|
1534
|
+
self.nesting_stack.pop();
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
fn visit_constant_and_write_node(&mut self, node: &ruby_prism::ConstantAndWriteNode) {
|
|
1539
|
+
self.index_constant_reference(&node.as_node(), true);
|
|
1540
|
+
self.visit(&node.value());
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
fn visit_constant_operator_write_node(&mut self, node: &ruby_prism::ConstantOperatorWriteNode) {
|
|
1544
|
+
self.index_constant_reference(&node.as_node(), true);
|
|
1545
|
+
self.visit(&node.value());
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
fn visit_constant_or_write_node(&mut self, node: &ruby_prism::ConstantOrWriteNode) {
|
|
1549
|
+
if let Some(target_name_id) = self.index_constant_alias_target(&node.value()) {
|
|
1550
|
+
self.add_constant_alias_definition(&node.as_node(), target_name_id, true);
|
|
1551
|
+
} else {
|
|
1552
|
+
self.add_constant_definition(&node.as_node(), true, Self::is_promotable_value(&node.value()));
|
|
1553
|
+
self.visit(&node.value());
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
fn visit_constant_write_node(&mut self, node: &ruby_prism::ConstantWriteNode) {
|
|
1558
|
+
let value = node.value();
|
|
1559
|
+
if self.handle_dynamic_class_or_module(&node.as_node(), &value) {
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
if let Some(target_name_id) = self.index_constant_alias_target(&value) {
|
|
1564
|
+
self.add_constant_alias_definition(&node.as_node(), target_name_id, false);
|
|
1565
|
+
} else {
|
|
1566
|
+
self.add_constant_definition(&node.as_node(), false, Self::is_promotable_value(&value));
|
|
1567
|
+
self.visit(&value);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
fn visit_constant_path_and_write_node(&mut self, node: &ruby_prism::ConstantPathAndWriteNode) {
|
|
1572
|
+
self.visit_constant_path_node(&node.target());
|
|
1573
|
+
self.visit(&node.value());
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
fn visit_constant_path_operator_write_node(&mut self, node: &ruby_prism::ConstantPathOperatorWriteNode) {
|
|
1577
|
+
self.visit_constant_path_node(&node.target());
|
|
1578
|
+
self.visit(&node.value());
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
fn visit_constant_path_or_write_node(&mut self, node: &ruby_prism::ConstantPathOrWriteNode) {
|
|
1582
|
+
if let Some(target_name_id) = self.index_constant_alias_target(&node.value()) {
|
|
1583
|
+
self.add_constant_alias_definition(&node.target().as_node(), target_name_id, true);
|
|
1584
|
+
} else {
|
|
1585
|
+
self.add_constant_definition(&node.target().as_node(), true, Self::is_promotable_value(&node.value()));
|
|
1586
|
+
self.visit(&node.value());
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
fn visit_constant_path_write_node(&mut self, node: &ruby_prism::ConstantPathWriteNode) {
|
|
1591
|
+
let value = node.value();
|
|
1592
|
+
if self.handle_dynamic_class_or_module(&node.as_node(), &value) {
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
if let Some(target_name_id) = self.index_constant_alias_target(&value) {
|
|
1597
|
+
self.add_constant_alias_definition(&node.target().as_node(), target_name_id, false);
|
|
1598
|
+
} else {
|
|
1599
|
+
self.add_constant_definition(&node.target().as_node(), false, Self::is_promotable_value(&value));
|
|
1600
|
+
self.visit(&value);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
fn visit_constant_read_node(&mut self, node: &ruby_prism::ConstantReadNode<'_>) {
|
|
1605
|
+
self.index_constant_reference(&node.as_node(), true);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
fn visit_constant_path_node(&mut self, node: &ruby_prism::ConstantPathNode<'_>) {
|
|
1609
|
+
self.index_constant_reference(&node.as_node(), true);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
fn visit_multi_write_node(&mut self, node: &ruby_prism::MultiWriteNode) {
|
|
1613
|
+
for left in &node.lefts() {
|
|
1614
|
+
match left {
|
|
1615
|
+
ruby_prism::Node::ConstantTargetNode { .. } | ruby_prism::Node::ConstantPathTargetNode { .. } => {
|
|
1616
|
+
// Individual values aren't available in multi-write, so we default to
|
|
1617
|
+
// promotable because multi-assignment often comes from meta-programming
|
|
1618
|
+
// (e.g., `A, B = create_classes`).
|
|
1619
|
+
self.add_constant_definition(&left, false, true);
|
|
1620
|
+
}
|
|
1621
|
+
ruby_prism::Node::GlobalVariableTargetNode { .. } => {
|
|
1622
|
+
self.add_definition_from_location(
|
|
1623
|
+
&left.location(),
|
|
1624
|
+
|str_id, offset, comments, flags, lexical_nesting_id, uri_id| {
|
|
1625
|
+
Definition::GlobalVariable(Box::new(GlobalVariableDefinition::new(
|
|
1626
|
+
str_id,
|
|
1627
|
+
uri_id,
|
|
1628
|
+
offset,
|
|
1629
|
+
comments,
|
|
1630
|
+
flags,
|
|
1631
|
+
lexical_nesting_id,
|
|
1632
|
+
)))
|
|
1633
|
+
},
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
ruby_prism::Node::InstanceVariableTargetNode { .. } => {
|
|
1637
|
+
self.add_instance_variable_definition(&left.location());
|
|
1638
|
+
}
|
|
1639
|
+
ruby_prism::Node::ClassVariableTargetNode { .. } => {
|
|
1640
|
+
self.add_class_variable_definition(&left.location());
|
|
1641
|
+
}
|
|
1642
|
+
ruby_prism::Node::CallTargetNode { .. } => {
|
|
1643
|
+
let call_target_node = left.as_call_target_node().unwrap();
|
|
1644
|
+
let method_receiver = self.method_receiver(Some(&call_target_node.receiver()), left.location());
|
|
1645
|
+
|
|
1646
|
+
if method_receiver.is_none() {
|
|
1647
|
+
self.visit(&call_target_node.receiver());
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
let name = String::from_utf8_lossy(call_target_node.name().as_slice()).to_string();
|
|
1651
|
+
self.index_method_reference(name, &call_target_node.location(), method_receiver);
|
|
1652
|
+
}
|
|
1653
|
+
_ => {}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
self.visit(&node.value());
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
fn visit_def_node(&mut self, node: &ruby_prism::DefNode) {
|
|
1661
|
+
let name = Self::location_to_string(&node.name_loc());
|
|
1662
|
+
let str_id = self.local_graph.intern_string(format!("{name}()"));
|
|
1663
|
+
let offset = Offset::from_prism_location(&node.location());
|
|
1664
|
+
let parent_nesting_id = self.current_nesting_definition_id();
|
|
1665
|
+
let parameters = self.collect_parameters(node);
|
|
1666
|
+
let is_singleton = node.receiver().is_some();
|
|
1667
|
+
|
|
1668
|
+
let current_visibility = self.current_visibility();
|
|
1669
|
+
let (visibility, offset_for_comments) = if is_singleton {
|
|
1670
|
+
(Visibility::Public, offset.clone())
|
|
1671
|
+
} else if current_visibility.is_inline() {
|
|
1672
|
+
// If the visibility is inline, we use its offset for the comments
|
|
1673
|
+
(*current_visibility.visibility(), current_visibility.offset().clone())
|
|
1674
|
+
} else {
|
|
1675
|
+
(*current_visibility.visibility(), offset.clone())
|
|
1676
|
+
};
|
|
1677
|
+
|
|
1678
|
+
let comment_offset = self
|
|
1679
|
+
.take_decorator_offset(offset_for_comments.start())
|
|
1680
|
+
.unwrap_or_else(|| offset_for_comments.start());
|
|
1681
|
+
let (comments, flags) = self.find_comments_for(comment_offset);
|
|
1682
|
+
|
|
1683
|
+
let receiver = if let Some(recv_node) = node.receiver() {
|
|
1684
|
+
match recv_node {
|
|
1685
|
+
// def self.foo - receiver is the enclosing definition's DefinitionId
|
|
1686
|
+
ruby_prism::Node::SelfNode { .. } => self.current_nesting_definition_id().map(Receiver::SelfReceiver),
|
|
1687
|
+
// def Foo.bar or def Foo::Bar.baz - receiver is the constant's NameId
|
|
1688
|
+
ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. } => self
|
|
1689
|
+
.index_constant_reference(&recv_node, true)
|
|
1690
|
+
.map(Receiver::ConstantReceiver),
|
|
1691
|
+
// Dynamic receiver (def foo.bar) - visit and then skip
|
|
1692
|
+
// We still want to visit because it could be a variable reference
|
|
1693
|
+
_ => {
|
|
1694
|
+
self.local_graph.add_diagnostic(
|
|
1695
|
+
Rule::DynamicSingletonDefinition,
|
|
1696
|
+
Offset::from_prism_location(&node.location()),
|
|
1697
|
+
"Dynamic receiver for singleton method definition".to_string(),
|
|
1698
|
+
);
|
|
1699
|
+
|
|
1700
|
+
self.visit(&recv_node);
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
} else {
|
|
1705
|
+
None
|
|
1706
|
+
};
|
|
1707
|
+
|
|
1708
|
+
let definition_id = if receiver.is_none() && visibility == Visibility::ModuleFunction {
|
|
1709
|
+
// module_function creates two method definitions:
|
|
1710
|
+
// 1. Public singleton method (class/module method)
|
|
1711
|
+
let method = Definition::Method(Box::new(MethodDefinition::new(
|
|
1712
|
+
str_id,
|
|
1713
|
+
self.uri_id,
|
|
1714
|
+
offset.clone(),
|
|
1715
|
+
comments.clone(),
|
|
1716
|
+
flags.clone(),
|
|
1717
|
+
parent_nesting_id,
|
|
1718
|
+
Signatures::Simple(parameters.clone().into_boxed_slice()),
|
|
1719
|
+
Visibility::Public,
|
|
1720
|
+
self.current_nesting_definition_id().map(Receiver::SelfReceiver),
|
|
1721
|
+
)));
|
|
1722
|
+
let definition_id = self.local_graph.add_definition(method);
|
|
1723
|
+
|
|
1724
|
+
self.add_member_to_current_owner(definition_id);
|
|
1725
|
+
|
|
1726
|
+
// 2. Private instance method
|
|
1727
|
+
let method = Definition::Method(Box::new(MethodDefinition::new(
|
|
1728
|
+
str_id,
|
|
1729
|
+
self.uri_id,
|
|
1730
|
+
offset,
|
|
1731
|
+
comments,
|
|
1732
|
+
flags,
|
|
1733
|
+
parent_nesting_id,
|
|
1734
|
+
Signatures::Simple(parameters.into_boxed_slice()),
|
|
1735
|
+
Visibility::Private,
|
|
1736
|
+
receiver,
|
|
1737
|
+
)));
|
|
1738
|
+
let definition_id = self.local_graph.add_definition(method);
|
|
1739
|
+
|
|
1740
|
+
self.add_member_to_current_owner(definition_id);
|
|
1741
|
+
|
|
1742
|
+
definition_id
|
|
1743
|
+
} else {
|
|
1744
|
+
let method = Definition::Method(Box::new(MethodDefinition::new(
|
|
1745
|
+
str_id,
|
|
1746
|
+
self.uri_id,
|
|
1747
|
+
offset,
|
|
1748
|
+
comments,
|
|
1749
|
+
flags,
|
|
1750
|
+
parent_nesting_id,
|
|
1751
|
+
Signatures::Simple(parameters.into_boxed_slice()),
|
|
1752
|
+
visibility,
|
|
1753
|
+
receiver,
|
|
1754
|
+
)));
|
|
1755
|
+
let definition_id = self.local_graph.add_definition(method);
|
|
1756
|
+
|
|
1757
|
+
self.add_member_to_current_owner(definition_id);
|
|
1758
|
+
|
|
1759
|
+
definition_id
|
|
1760
|
+
};
|
|
1761
|
+
|
|
1762
|
+
if let Some(body) = node.body() {
|
|
1763
|
+
self.nesting_stack.push(Nesting::Method(definition_id));
|
|
1764
|
+
self.visit(&body);
|
|
1765
|
+
self.nesting_stack.pop();
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
#[allow(clippy::too_many_lines)]
|
|
1770
|
+
fn visit_call_node(&mut self, node: &ruby_prism::CallNode) {
|
|
1771
|
+
enum AttrKind {
|
|
1772
|
+
Accessor,
|
|
1773
|
+
Reader,
|
|
1774
|
+
Writer,
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
let mut index_attr = |kind: AttrKind, call: &ruby_prism::CallNode| {
|
|
1778
|
+
let receiver = call.receiver();
|
|
1779
|
+
if receiver.is_some() && receiver.unwrap().as_self_node().is_none() {
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
let call_offset = Offset::from_prism_location(&call.location());
|
|
1784
|
+
|
|
1785
|
+
let current_visibility = self.current_visibility();
|
|
1786
|
+
let (visibility, offset_for_comments) = if current_visibility.is_inline() {
|
|
1787
|
+
(*current_visibility.visibility(), current_visibility.offset().clone())
|
|
1788
|
+
} else {
|
|
1789
|
+
(*current_visibility.visibility(), call_offset.clone())
|
|
1790
|
+
};
|
|
1791
|
+
|
|
1792
|
+
let comment_offset = self
|
|
1793
|
+
.take_decorator_offset(offset_for_comments.start())
|
|
1794
|
+
.unwrap_or_else(|| offset_for_comments.start());
|
|
1795
|
+
|
|
1796
|
+
Self::each_string_or_symbol_arg(call, |name, location| {
|
|
1797
|
+
let str_id = self.local_graph.intern_string(format!("{name}()"));
|
|
1798
|
+
let parent_nesting_id = self.parent_nesting_id();
|
|
1799
|
+
let offset = Offset::from_prism_location(&location);
|
|
1800
|
+
|
|
1801
|
+
let (comments, flags) = self.find_comments_for(comment_offset);
|
|
1802
|
+
|
|
1803
|
+
// module_function makes attr_* methods private (without creating singleton methods)
|
|
1804
|
+
let visibility = match visibility {
|
|
1805
|
+
Visibility::ModuleFunction => Visibility::Private,
|
|
1806
|
+
v => v,
|
|
1807
|
+
};
|
|
1808
|
+
|
|
1809
|
+
let definition = match kind {
|
|
1810
|
+
AttrKind::Accessor => Definition::AttrAccessor(Box::new(AttrAccessorDefinition::new(
|
|
1811
|
+
str_id,
|
|
1812
|
+
self.uri_id,
|
|
1813
|
+
offset,
|
|
1814
|
+
comments,
|
|
1815
|
+
flags,
|
|
1816
|
+
parent_nesting_id,
|
|
1817
|
+
visibility,
|
|
1818
|
+
))),
|
|
1819
|
+
AttrKind::Reader => Definition::AttrReader(Box::new(AttrReaderDefinition::new(
|
|
1820
|
+
str_id,
|
|
1821
|
+
self.uri_id,
|
|
1822
|
+
offset,
|
|
1823
|
+
comments,
|
|
1824
|
+
flags,
|
|
1825
|
+
parent_nesting_id,
|
|
1826
|
+
visibility,
|
|
1827
|
+
))),
|
|
1828
|
+
AttrKind::Writer => Definition::AttrWriter(Box::new(AttrWriterDefinition::new(
|
|
1829
|
+
str_id,
|
|
1830
|
+
self.uri_id,
|
|
1831
|
+
offset,
|
|
1832
|
+
comments,
|
|
1833
|
+
flags,
|
|
1834
|
+
parent_nesting_id,
|
|
1835
|
+
visibility,
|
|
1836
|
+
))),
|
|
1837
|
+
};
|
|
1838
|
+
|
|
1839
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
1840
|
+
self.add_member_to_current_owner(definition_id);
|
|
1841
|
+
});
|
|
1842
|
+
};
|
|
1843
|
+
|
|
1844
|
+
let message_loc = node.message_loc();
|
|
1845
|
+
|
|
1846
|
+
if message_loc.is_none() {
|
|
1847
|
+
// No message, we can't index this node
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
let message = String::from_utf8_lossy(node.name().as_slice()).to_string();
|
|
1852
|
+
|
|
1853
|
+
match message.as_str() {
|
|
1854
|
+
"attr_accessor" => {
|
|
1855
|
+
index_attr(AttrKind::Accessor, node);
|
|
1856
|
+
}
|
|
1857
|
+
"attr_reader" => {
|
|
1858
|
+
index_attr(AttrKind::Reader, node);
|
|
1859
|
+
}
|
|
1860
|
+
"attr_writer" => {
|
|
1861
|
+
index_attr(AttrKind::Writer, node);
|
|
1862
|
+
}
|
|
1863
|
+
"attr" => {
|
|
1864
|
+
// attr :foo, true => both reader and writer
|
|
1865
|
+
// attr :foo, false => only reader
|
|
1866
|
+
// attr :foo => only reader
|
|
1867
|
+
// attr :foo, "bar", :baz => only readers for foo, bar, and baz
|
|
1868
|
+
let create_writer = if let Some(arguments) = node.arguments() {
|
|
1869
|
+
let args_vec: Vec<_> = arguments.arguments().iter().collect();
|
|
1870
|
+
matches!(args_vec.as_slice(), [_, ruby_prism::Node::TrueNode { .. }])
|
|
1871
|
+
} else {
|
|
1872
|
+
false
|
|
1873
|
+
};
|
|
1874
|
+
|
|
1875
|
+
if create_writer {
|
|
1876
|
+
index_attr(AttrKind::Accessor, node);
|
|
1877
|
+
} else {
|
|
1878
|
+
index_attr(AttrKind::Reader, node);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
"alias_method" => {
|
|
1882
|
+
let recv_node = node.receiver();
|
|
1883
|
+
let recv_ref = recv_node.as_ref();
|
|
1884
|
+
if recv_ref.is_some_and(|recv| {
|
|
1885
|
+
!matches!(
|
|
1886
|
+
recv,
|
|
1887
|
+
ruby_prism::Node::SelfNode { .. }
|
|
1888
|
+
| ruby_prism::Node::ConstantReadNode { .. }
|
|
1889
|
+
| ruby_prism::Node::ConstantPathNode { .. }
|
|
1890
|
+
)
|
|
1891
|
+
}) {
|
|
1892
|
+
// TODO: Add a diagnostic for dynamic receivers
|
|
1893
|
+
self.visit_call_node_parts(node);
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
let mut names: Vec<(String, Offset)> = Vec::new();
|
|
1898
|
+
|
|
1899
|
+
Self::each_string_or_symbol_arg(node, |name, location| {
|
|
1900
|
+
names.push((name, Offset::from_prism_location(&location)));
|
|
1901
|
+
});
|
|
1902
|
+
|
|
1903
|
+
if names.len() != 2 {
|
|
1904
|
+
// TODO: Add a diagnostic for this
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
let (new_name, _new_offset) = &names[0];
|
|
1909
|
+
let (old_name, old_offset) = &names[1];
|
|
1910
|
+
|
|
1911
|
+
let new_name_str_id = self.local_graph.intern_string(format!("{new_name}()"));
|
|
1912
|
+
let old_name_str_id = self.local_graph.intern_string(format!("{old_name}()"));
|
|
1913
|
+
|
|
1914
|
+
let (receiver, method_receiver) = match recv_ref {
|
|
1915
|
+
Some(
|
|
1916
|
+
recv @ (ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. }),
|
|
1917
|
+
) => {
|
|
1918
|
+
let name_id = self.index_constant_reference(recv, true);
|
|
1919
|
+
(name_id.map(Receiver::ConstantReceiver), name_id)
|
|
1920
|
+
}
|
|
1921
|
+
_ => (None, self.method_receiver(recv_ref, node.location())),
|
|
1922
|
+
};
|
|
1923
|
+
let reference = MethodRef::new(old_name_str_id, self.uri_id, old_offset.clone(), method_receiver);
|
|
1924
|
+
self.local_graph.add_method_reference(reference);
|
|
1925
|
+
|
|
1926
|
+
let offset = Offset::from_prism_location(&node.location());
|
|
1927
|
+
let (comments, flags) = self.find_comments_for(offset.start());
|
|
1928
|
+
|
|
1929
|
+
let definition = Definition::MethodAlias(Box::new(MethodAliasDefinition::new(
|
|
1930
|
+
new_name_str_id,
|
|
1931
|
+
old_name_str_id,
|
|
1932
|
+
self.uri_id,
|
|
1933
|
+
offset,
|
|
1934
|
+
comments,
|
|
1935
|
+
flags,
|
|
1936
|
+
self.current_nesting_definition_id(),
|
|
1937
|
+
receiver,
|
|
1938
|
+
)));
|
|
1939
|
+
|
|
1940
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
1941
|
+
|
|
1942
|
+
self.add_member_to_current_owner(definition_id);
|
|
1943
|
+
}
|
|
1944
|
+
"include" => {
|
|
1945
|
+
let receiver = node.receiver();
|
|
1946
|
+
if receiver.is_none() || receiver.as_ref().is_some_and(|r| r.as_self_node().is_some()) {
|
|
1947
|
+
self.handle_mixin(node, MixinType::Include);
|
|
1948
|
+
} else {
|
|
1949
|
+
self.visit_call_node_parts(node);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
"prepend" => {
|
|
1953
|
+
let receiver = node.receiver();
|
|
1954
|
+
if receiver.is_none() || receiver.as_ref().is_some_and(|r| r.as_self_node().is_some()) {
|
|
1955
|
+
self.handle_mixin(node, MixinType::Prepend);
|
|
1956
|
+
} else {
|
|
1957
|
+
self.visit_call_node_parts(node);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
"extend" => {
|
|
1961
|
+
let receiver = node.receiver();
|
|
1962
|
+
if receiver.is_none() || receiver.as_ref().is_some_and(|r| r.as_self_node().is_some()) {
|
|
1963
|
+
self.handle_mixin(node, MixinType::Extend);
|
|
1964
|
+
} else {
|
|
1965
|
+
self.visit_call_node_parts(node);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
"private" | "protected" | "public" | "module_function" => {
|
|
1969
|
+
if node.receiver().is_some() {
|
|
1970
|
+
let offset = Offset::from_prism_location(&node.location());
|
|
1971
|
+
self.local_graph.add_diagnostic(
|
|
1972
|
+
Rule::InvalidMethodVisibility,
|
|
1973
|
+
offset,
|
|
1974
|
+
format!("`{message}` cannot be called with an explicit receiver"),
|
|
1975
|
+
);
|
|
1976
|
+
self.visit_call_node_parts(node);
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
let visibility = Visibility::from_string(message.as_str());
|
|
1981
|
+
let offset = Offset::from_prism_location(&node.location());
|
|
1982
|
+
|
|
1983
|
+
if let Some(arguments) = node.arguments() {
|
|
1984
|
+
if visibility == Visibility::ModuleFunction && !self.current_nesting_is_module() {
|
|
1985
|
+
self.local_graph.add_diagnostic(
|
|
1986
|
+
Rule::InvalidMethodVisibility,
|
|
1987
|
+
offset,
|
|
1988
|
+
"`module_function` can only be used in modules".to_string(),
|
|
1989
|
+
);
|
|
1990
|
+
self.visit_arguments_node(&arguments);
|
|
1991
|
+
} else {
|
|
1992
|
+
self.handle_visibility_arguments(&arguments, visibility, &offset, message.as_str());
|
|
1993
|
+
}
|
|
1994
|
+
} else {
|
|
1995
|
+
// Flag mode: `private` with no arguments
|
|
1996
|
+
//
|
|
1997
|
+
// Replace the current visibility so it affects all subsequent method definitions.
|
|
1998
|
+
let last_visibility = self.visibility_stack.last_mut().unwrap();
|
|
1999
|
+
*last_visibility = VisibilityModifier::new(visibility, false, offset);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
"new" => {
|
|
2003
|
+
let receiver_name = node.receiver().map(|r| r.location().as_slice());
|
|
2004
|
+
|
|
2005
|
+
if matches!(receiver_name, Some(b"Class" | b"::Class")) {
|
|
2006
|
+
self.handle_class_definition(
|
|
2007
|
+
&node.location(),
|
|
2008
|
+
None,
|
|
2009
|
+
node.block(),
|
|
2010
|
+
node.arguments().and_then(|args| args.arguments().iter().next()),
|
|
2011
|
+
Nesting::Owner,
|
|
2012
|
+
);
|
|
2013
|
+
} else if matches!(receiver_name, Some(b"Module" | b"::Module")) {
|
|
2014
|
+
self.handle_module_definition(&node.location(), None, node.block(), Nesting::Owner);
|
|
2015
|
+
} else {
|
|
2016
|
+
if let Some(arguments) = node.arguments() {
|
|
2017
|
+
self.visit_arguments_node(&arguments);
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
if let Some(block) = node.block() {
|
|
2021
|
+
self.visit(&block);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
self.index_method_reference_for_call(node);
|
|
2026
|
+
}
|
|
2027
|
+
"sig"
|
|
2028
|
+
if node.receiver().is_none()
|
|
2029
|
+
|| matches!(
|
|
2030
|
+
node.receiver(),
|
|
2031
|
+
Some(ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. })
|
|
2032
|
+
) =>
|
|
2033
|
+
{
|
|
2034
|
+
self.pending_decorator_offset = Some(Offset::from_prism_location(&node.location()));
|
|
2035
|
+
|
|
2036
|
+
if let Some(arguments) = node.arguments() {
|
|
2037
|
+
self.visit_arguments_node(&arguments);
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
if let Some(block) = node.block() {
|
|
2041
|
+
self.visit(&block);
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
self.index_method_reference_for_call(node);
|
|
2045
|
+
}
|
|
2046
|
+
"private_constant" => {
|
|
2047
|
+
self.handle_constant_visibility(node, Visibility::Private);
|
|
2048
|
+
}
|
|
2049
|
+
"public_constant" => {
|
|
2050
|
+
self.handle_constant_visibility(node, Visibility::Public);
|
|
2051
|
+
}
|
|
2052
|
+
_ => {
|
|
2053
|
+
// For method calls that we don't explicitly handle each part, we continue visiting their parts as we
|
|
2054
|
+
// may discover something inside
|
|
2055
|
+
if let Some(arguments) = node.arguments() {
|
|
2056
|
+
self.visit_arguments_node(&arguments);
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
if let Some(block) = node.block() {
|
|
2060
|
+
self.visit(&block);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
let method_receiver = self.method_receiver(node.receiver().as_ref(), node.location());
|
|
2064
|
+
|
|
2065
|
+
if method_receiver.is_none()
|
|
2066
|
+
&& let Some(receiver) = node.receiver()
|
|
2067
|
+
{
|
|
2068
|
+
self.visit(&receiver);
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
self.index_method_reference(message.clone(), &node.message_loc().unwrap(), method_receiver);
|
|
2072
|
+
|
|
2073
|
+
match message.as_str() {
|
|
2074
|
+
">" | "<" | ">=" | "<=" => {
|
|
2075
|
+
self.index_method_reference("<=>".to_string(), &node.message_loc().unwrap(), method_receiver);
|
|
2076
|
+
}
|
|
2077
|
+
_ => {}
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
fn visit_call_and_write_node(&mut self, node: &ruby_prism::CallAndWriteNode) {
|
|
2084
|
+
let method_receiver = self.method_receiver(node.receiver().as_ref(), node.location());
|
|
2085
|
+
|
|
2086
|
+
if method_receiver.is_none()
|
|
2087
|
+
&& let Some(receiver) = node.receiver()
|
|
2088
|
+
{
|
|
2089
|
+
self.visit(&receiver);
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
let read_name = String::from_utf8_lossy(node.read_name().as_slice()).to_string();
|
|
2093
|
+
self.index_method_reference(read_name, &node.operator_loc(), method_receiver);
|
|
2094
|
+
|
|
2095
|
+
let write_name = String::from_utf8_lossy(node.write_name().as_slice()).to_string();
|
|
2096
|
+
self.index_method_reference(write_name, &node.operator_loc(), method_receiver);
|
|
2097
|
+
|
|
2098
|
+
self.visit(&node.value());
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
fn visit_call_operator_write_node(&mut self, node: &ruby_prism::CallOperatorWriteNode) {
|
|
2102
|
+
let method_receiver = self.method_receiver(node.receiver().as_ref(), node.location());
|
|
2103
|
+
|
|
2104
|
+
if method_receiver.is_none()
|
|
2105
|
+
&& let Some(receiver) = node.receiver()
|
|
2106
|
+
{
|
|
2107
|
+
self.visit(&receiver);
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
let read_name = String::from_utf8_lossy(node.read_name().as_slice()).to_string();
|
|
2111
|
+
self.index_method_reference(read_name, &node.call_operator_loc().unwrap(), method_receiver);
|
|
2112
|
+
|
|
2113
|
+
let write_name = String::from_utf8_lossy(node.write_name().as_slice()).to_string();
|
|
2114
|
+
self.index_method_reference(write_name, &node.call_operator_loc().unwrap(), method_receiver);
|
|
2115
|
+
|
|
2116
|
+
self.visit(&node.value());
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
fn visit_call_or_write_node(&mut self, node: &ruby_prism::CallOrWriteNode) {
|
|
2120
|
+
let method_receiver = self.method_receiver(node.receiver().as_ref(), node.location());
|
|
2121
|
+
|
|
2122
|
+
if method_receiver.is_none()
|
|
2123
|
+
&& let Some(receiver) = node.receiver()
|
|
2124
|
+
{
|
|
2125
|
+
self.visit(&receiver);
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
let read_name = String::from_utf8_lossy(node.read_name().as_slice()).to_string();
|
|
2129
|
+
self.index_method_reference(read_name, &node.operator_loc(), method_receiver);
|
|
2130
|
+
|
|
2131
|
+
let write_name = String::from_utf8_lossy(node.write_name().as_slice()).to_string();
|
|
2132
|
+
self.index_method_reference(write_name, &node.operator_loc(), method_receiver);
|
|
2133
|
+
|
|
2134
|
+
self.visit(&node.value());
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
fn visit_global_variable_write_node(&mut self, node: &ruby_prism::GlobalVariableWriteNode) {
|
|
2138
|
+
self.add_definition_from_location(
|
|
2139
|
+
&node.name_loc(),
|
|
2140
|
+
|str_id, offset, comments, flags, lexical_nesting_id, uri_id| {
|
|
2141
|
+
Definition::GlobalVariable(Box::new(GlobalVariableDefinition::new(
|
|
2142
|
+
str_id,
|
|
2143
|
+
uri_id,
|
|
2144
|
+
offset,
|
|
2145
|
+
comments,
|
|
2146
|
+
flags,
|
|
2147
|
+
lexical_nesting_id,
|
|
2148
|
+
)))
|
|
2149
|
+
},
|
|
2150
|
+
);
|
|
2151
|
+
self.visit(&node.value());
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
fn visit_global_variable_and_write_node(&mut self, node: &ruby_prism::GlobalVariableAndWriteNode<'_>) {
|
|
2155
|
+
self.add_definition_from_location(
|
|
2156
|
+
&node.name_loc(),
|
|
2157
|
+
|str_id, offset, comments, flags, nesting_id, uri_id| {
|
|
2158
|
+
Definition::GlobalVariable(Box::new(GlobalVariableDefinition::new(
|
|
2159
|
+
str_id, uri_id, offset, comments, flags, nesting_id,
|
|
2160
|
+
)))
|
|
2161
|
+
},
|
|
2162
|
+
);
|
|
2163
|
+
self.visit(&node.value());
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
fn visit_global_variable_or_write_node(&mut self, node: &ruby_prism::GlobalVariableOrWriteNode<'_>) {
|
|
2167
|
+
self.add_definition_from_location(
|
|
2168
|
+
&node.name_loc(),
|
|
2169
|
+
|str_id, offset, comments, flags, nesting_id, uri_id| {
|
|
2170
|
+
Definition::GlobalVariable(Box::new(GlobalVariableDefinition::new(
|
|
2171
|
+
str_id, uri_id, offset, comments, flags, nesting_id,
|
|
2172
|
+
)))
|
|
2173
|
+
},
|
|
2174
|
+
);
|
|
2175
|
+
self.visit(&node.value());
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
fn visit_global_variable_operator_write_node(&mut self, node: &ruby_prism::GlobalVariableOperatorWriteNode<'_>) {
|
|
2179
|
+
self.add_definition_from_location(
|
|
2180
|
+
&node.name_loc(),
|
|
2181
|
+
|str_id, offset, comments, flags, nesting_id, uri_id| {
|
|
2182
|
+
Definition::GlobalVariable(Box::new(GlobalVariableDefinition::new(
|
|
2183
|
+
str_id, uri_id, offset, comments, flags, nesting_id,
|
|
2184
|
+
)))
|
|
2185
|
+
},
|
|
2186
|
+
);
|
|
2187
|
+
self.visit(&node.value());
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
fn visit_instance_variable_and_write_node(&mut self, node: &ruby_prism::InstanceVariableAndWriteNode) {
|
|
2191
|
+
self.add_instance_variable_definition(&node.name_loc());
|
|
2192
|
+
self.visit(&node.value());
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
fn visit_instance_variable_operator_write_node(&mut self, node: &ruby_prism::InstanceVariableOperatorWriteNode) {
|
|
2196
|
+
self.add_instance_variable_definition(&node.name_loc());
|
|
2197
|
+
self.visit(&node.value());
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
fn visit_instance_variable_or_write_node(&mut self, node: &ruby_prism::InstanceVariableOrWriteNode) {
|
|
2201
|
+
self.add_instance_variable_definition(&node.name_loc());
|
|
2202
|
+
self.visit(&node.value());
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
fn visit_instance_variable_write_node(&mut self, node: &ruby_prism::InstanceVariableWriteNode) {
|
|
2206
|
+
self.add_instance_variable_definition(&node.name_loc());
|
|
2207
|
+
self.visit(&node.value());
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
fn visit_class_variable_and_write_node(&mut self, node: &ruby_prism::ClassVariableAndWriteNode) {
|
|
2211
|
+
self.add_class_variable_definition(&node.name_loc());
|
|
2212
|
+
self.visit(&node.value());
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
fn visit_class_variable_operator_write_node(&mut self, node: &ruby_prism::ClassVariableOperatorWriteNode) {
|
|
2216
|
+
self.add_class_variable_definition(&node.name_loc());
|
|
2217
|
+
self.visit(&node.value());
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
fn visit_class_variable_or_write_node(&mut self, node: &ruby_prism::ClassVariableOrWriteNode) {
|
|
2221
|
+
self.add_class_variable_definition(&node.name_loc());
|
|
2222
|
+
self.visit(&node.value());
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
fn visit_class_variable_write_node(&mut self, node: &ruby_prism::ClassVariableWriteNode) {
|
|
2226
|
+
self.add_class_variable_definition(&node.name_loc());
|
|
2227
|
+
self.visit(&node.value());
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
fn visit_block_argument_node(&mut self, node: &ruby_prism::BlockArgumentNode<'_>) {
|
|
2231
|
+
let expression = node.expression();
|
|
2232
|
+
if let Some(expression) = expression {
|
|
2233
|
+
match expression {
|
|
2234
|
+
ruby_prism::Node::SymbolNode { .. } => {
|
|
2235
|
+
let symbol = expression.as_symbol_node().unwrap();
|
|
2236
|
+
let name = Self::location_to_string(&symbol.value_loc().unwrap());
|
|
2237
|
+
self.index_method_reference(name, &node.location(), None);
|
|
2238
|
+
}
|
|
2239
|
+
_ => {
|
|
2240
|
+
self.visit(&expression);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
fn visit_alias_method_node(&mut self, node: &ruby_prism::AliasMethodNode<'_>) {
|
|
2247
|
+
let mut new_name = if let Some(symbol_node) = node.new_name().as_symbol_node() {
|
|
2248
|
+
Self::location_to_string(&symbol_node.value_loc().unwrap())
|
|
2249
|
+
} else {
|
|
2250
|
+
Self::location_to_string(&node.new_name().location())
|
|
2251
|
+
};
|
|
2252
|
+
|
|
2253
|
+
let mut old_name = if let Some(symbol_node) = node.old_name().as_symbol_node() {
|
|
2254
|
+
Self::location_to_string(&symbol_node.value_loc().unwrap())
|
|
2255
|
+
} else {
|
|
2256
|
+
Self::location_to_string(&node.old_name().location())
|
|
2257
|
+
};
|
|
2258
|
+
|
|
2259
|
+
new_name.push_str("()");
|
|
2260
|
+
old_name.push_str("()");
|
|
2261
|
+
|
|
2262
|
+
let offset = Offset::from_prism_location(&node.location());
|
|
2263
|
+
let (comments, flags) = self.find_comments_for(offset.start());
|
|
2264
|
+
let definition = Definition::MethodAlias(Box::new(MethodAliasDefinition::new(
|
|
2265
|
+
self.local_graph.intern_string(new_name),
|
|
2266
|
+
self.local_graph.intern_string(old_name.clone()),
|
|
2267
|
+
self.uri_id,
|
|
2268
|
+
offset,
|
|
2269
|
+
comments,
|
|
2270
|
+
flags,
|
|
2271
|
+
self.current_nesting_definition_id(),
|
|
2272
|
+
None,
|
|
2273
|
+
)));
|
|
2274
|
+
|
|
2275
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
2276
|
+
|
|
2277
|
+
self.add_member_to_current_owner(definition_id);
|
|
2278
|
+
self.index_method_reference(old_name, &node.old_name().location(), None);
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
fn visit_alias_global_variable_node(&mut self, node: &ruby_prism::AliasGlobalVariableNode<'_>) {
|
|
2282
|
+
let new_name = Self::location_to_string(&node.new_name().location());
|
|
2283
|
+
let old_name = Self::location_to_string(&node.old_name().location());
|
|
2284
|
+
let offset = Offset::from_prism_location(&node.location());
|
|
2285
|
+
let (comments, flags) = self.find_comments_for(offset.start());
|
|
2286
|
+
|
|
2287
|
+
let definition = Definition::GlobalVariableAlias(Box::new(GlobalVariableAliasDefinition::new(
|
|
2288
|
+
self.local_graph.intern_string(new_name),
|
|
2289
|
+
self.local_graph.intern_string(old_name),
|
|
2290
|
+
self.uri_id,
|
|
2291
|
+
offset,
|
|
2292
|
+
comments,
|
|
2293
|
+
flags,
|
|
2294
|
+
self.parent_nesting_id(),
|
|
2295
|
+
)));
|
|
2296
|
+
|
|
2297
|
+
let definition_id = self.local_graph.add_definition(definition);
|
|
2298
|
+
|
|
2299
|
+
self.add_member_to_current_owner(definition_id);
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
fn visit_and_node(&mut self, node: &ruby_prism::AndNode) {
|
|
2303
|
+
let left = node.left();
|
|
2304
|
+
let method_receiver = self.method_receiver(Some(&left), left.location());
|
|
2305
|
+
|
|
2306
|
+
if method_receiver.is_none() {
|
|
2307
|
+
self.visit(&left);
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
self.index_method_reference("&&".to_string(), &node.location(), method_receiver);
|
|
2311
|
+
self.visit(&node.right());
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
fn visit_or_node(&mut self, node: &ruby_prism::OrNode) {
|
|
2315
|
+
let left = node.left();
|
|
2316
|
+
let method_receiver = self.method_receiver(Some(&left), left.location());
|
|
2317
|
+
|
|
2318
|
+
if method_receiver.is_none() {
|
|
2319
|
+
self.visit(&left);
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
self.index_method_reference("||".to_string(), &node.location(), method_receiver);
|
|
2323
|
+
self.visit(&node.right());
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
#[cfg(test)]
|
|
2328
|
+
#[path = "ruby_indexer_tests.rs"]
|
|
2329
|
+
mod tests;
|