rubydex 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +29 -7
- data/THIRD_PARTY_LICENSES.html +238 -2
- data/exe/rdx +2 -149
- data/ext/rubydex/config.c +140 -0
- data/ext/rubydex/config.h +16 -0
- data/ext/rubydex/definition.c +34 -0
- data/ext/rubydex/definition.h +3 -0
- data/ext/rubydex/diagnostic.c +28 -1
- data/ext/rubydex/diagnostic.h +2 -0
- data/ext/rubydex/extconf.rb +4 -2
- data/ext/rubydex/graph.c +71 -55
- data/ext/rubydex/graph.h +6 -0
- data/ext/rubydex/query.c +398 -16
- data/ext/rubydex/rubydex.c +2 -0
- data/ext/rubydex/utils.c +11 -4
- data/lib/ruby_lsp/rubydex/addon.rb +211 -0
- data/lib/rubydex/cli/command/console.rb +55 -0
- data/lib/rubydex/cli/command/lint/explain.rb +76 -0
- data/lib/rubydex/cli/command/lint.rb +208 -0
- data/lib/rubydex/cli/command/mcp.rb +30 -0
- data/lib/rubydex/cli/command/query.rb +70 -0
- data/lib/rubydex/cli/command/skill.rb +69 -0
- data/lib/rubydex/cli/command.rb +168 -0
- data/lib/rubydex/cli.rb +93 -0
- data/lib/rubydex/config.rb +59 -0
- data/lib/rubydex/diagnostic.rb +12 -3
- data/lib/rubydex/errors.rb +42 -1
- data/lib/rubydex/graph.rb +10 -3
- data/lib/rubydex/linter/custom_rule.rb +97 -0
- data/lib/rubydex/linter/helpers/path_helpers.rb +78 -0
- data/lib/rubydex/linter/helpers/source_access_helpers.rb +31 -0
- data/lib/rubydex/linter/rule_loader.rb +36 -0
- data/lib/rubydex/linter/rule_test_case.rb +343 -0
- data/lib/rubydex/linter/runner.rb +56 -0
- data/lib/rubydex/linter.rb +19 -0
- data/lib/rubydex/location.rb +3 -0
- data/lib/rubydex/mcp_server.rb +1 -2
- data/lib/rubydex/related_information.rb +17 -0
- data/lib/rubydex/rule.rb +33 -0
- data/lib/rubydex/rules/dynamic_ancestor.rb +29 -0
- data/lib/rubydex/rules/dynamic_constant_reference.rb +25 -0
- data/lib/rubydex/rules/dynamic_singleton_definition.rb +30 -0
- data/lib/rubydex/rules/invalid_constant_visibility.rb +28 -0
- data/lib/rubydex/rules/invalid_method_visibility.rb +28 -0
- data/lib/rubydex/rules/parse_error.rb +25 -0
- data/lib/rubydex/rules/parse_warning.rb +28 -0
- data/lib/rubydex/rules/top_level_mixin_self.rb +27 -0
- data/lib/rubydex/rules/undefined_constant_visibility_target.rb +27 -0
- data/lib/rubydex/rules/undefined_method_visibility_target.rb +27 -0
- data/lib/rubydex/severity.rb +70 -0
- data/lib/rubydex/skill.rb +89 -0
- data/lib/rubydex/skill_registry.rb +62 -0
- data/lib/rubydex/version.rb +1 -1
- data/lib/rubydex.rb +8 -0
- data/lib/rubydex_linter/rules/rule_structure.rb +140 -0
- data/rbi/rubydex.rbi +464 -21
- data/rust/Cargo.lock +2 -2
- data/rust/rubydex/Cargo.toml +5 -1
- data/rust/rubydex/benches/graph_memory.rs +3 -5
- data/rust/rubydex/src/bin/generate_ruby_rules.rs +94 -0
- data/rust/rubydex/src/config.rs +538 -157
- data/rust/rubydex/src/diagnostic.rs +162 -45
- data/rust/rubydex/src/errors.rs +0 -1
- data/rust/rubydex/src/indexing/local_graph.rs +6 -5
- data/rust/rubydex/src/indexing/rbs_indexer.rs +270 -6
- data/rust/rubydex/src/indexing/ruby_indexer.rs +10 -12
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +134 -81
- data/rust/rubydex/src/lib.rs +1 -0
- data/rust/rubydex/src/listing.rs +26 -1
- data/rust/rubydex/src/main.rs +7 -4
- data/rust/rubydex/src/model/declaration.rs +302 -219
- data/rust/rubydex/src/model/graph.rs +27 -40
- data/rust/rubydex/src/model/name.rs +58 -17
- data/rust/rubydex/src/operation/ruby_builder.rs +30 -45
- data/rust/rubydex/src/path_helpers.rs +77 -0
- data/rust/rubydex/src/query/cypher/schema.rs +63 -0
- data/rust/rubydex/src/query/cypher/tests.rs +26 -1
- data/rust/rubydex/src/query/cypher.rs +8 -11
- data/rust/rubydex/src/query.rs +314 -44
- data/rust/rubydex/src/resolution.rs +139 -187
- data/rust/rubydex/src/resolution_tests.rs +247 -19
- data/rust/rubydex/src/test_utils/context.rs +2 -1
- data/rust/rubydex/src/test_utils/graph_test.rs +26 -12
- data/rust/rubydex/src/test_utils/local_graph_test.rs +19 -0
- data/rust/rubydex/tests/cli.rs +4 -4
- data/rust/rubydex-sys/src/config_api.rs +205 -0
- data/rust/rubydex-sys/src/cypher_api.rs +791 -0
- data/rust/rubydex-sys/src/definition_api.rs +68 -1
- data/rust/rubydex-sys/src/diagnostic_api.rs +42 -8
- data/rust/rubydex-sys/src/graph_api.rs +125 -205
- data/rust/rubydex-sys/src/lib.rs +2 -0
- data/rust/rubydex-sys/src/name_api.rs +49 -17
- data/rust/rubydex-sys/src/utils.rs +37 -0
- data/skills/send-private-method/SKILL.md +133 -0
- metadata +42 -2
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
use rubydex::model::{
|
|
2
|
-
graph::Graph,
|
|
3
|
-
ids::NameId,
|
|
4
|
-
name::{Name, ParentScope},
|
|
5
|
-
};
|
|
1
|
+
use rubydex::model::{graph::Graph, ids::NameId, name::ParentScope};
|
|
6
2
|
|
|
7
3
|
/// Takes a constant name and a nesting stack (e.g.: `["Foo", "Bar::Baz", "Qux"]`) and transforms it into a `NameId`,
|
|
8
4
|
/// registering each required part in the graph. Returns the `NameId` and a list of name ids that need to be untracked
|
|
@@ -17,13 +13,7 @@ pub fn nesting_stack_to_name_id(
|
|
|
17
13
|
let mut names_to_untrack = Vec::new();
|
|
18
14
|
|
|
19
15
|
for entry in nesting {
|
|
20
|
-
process_qualified_name(
|
|
21
|
-
graph,
|
|
22
|
-
&entry,
|
|
23
|
-
&mut current_name,
|
|
24
|
-
&mut current_nesting,
|
|
25
|
-
&mut names_to_untrack,
|
|
26
|
-
);
|
|
16
|
+
process_qualified_name(graph, &entry, current_nesting, &mut current_name, &mut names_to_untrack);
|
|
27
17
|
current_nesting = current_name.as_ref().copied();
|
|
28
18
|
current_name = ParentScope::None;
|
|
29
19
|
}
|
|
@@ -31,12 +21,39 @@ pub fn nesting_stack_to_name_id(
|
|
|
31
21
|
process_qualified_name(
|
|
32
22
|
graph,
|
|
33
23
|
const_name,
|
|
24
|
+
current_nesting,
|
|
34
25
|
&mut current_name,
|
|
35
|
-
&mut current_nesting,
|
|
36
26
|
&mut names_to_untrack,
|
|
37
27
|
);
|
|
38
28
|
|
|
39
29
|
let (ParentScope::Some(name_id) | ParentScope::Attached(name_id)) = current_name else {
|
|
30
|
+
// Invalid names may leave temporary parts in the graph (e.g. `Foo` from `Foo::`).
|
|
31
|
+
for name_id in names_to_untrack {
|
|
32
|
+
graph.untrack_name(name_id);
|
|
33
|
+
}
|
|
34
|
+
return None;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
Some((name_id, names_to_untrack))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Takes a constant name and an existing lexical nesting and transforms it into a `NameId`, registering each required
|
|
41
|
+
/// part in the graph. Returns the `NameId` and a list of name ids that need to be untracked afterwards.
|
|
42
|
+
pub fn name_in_nesting_to_name_id(
|
|
43
|
+
graph: &mut Graph,
|
|
44
|
+
const_name: &str,
|
|
45
|
+
nesting: Option<NameId>,
|
|
46
|
+
) -> Option<(NameId, Vec<NameId>)> {
|
|
47
|
+
let mut current_name = ParentScope::None;
|
|
48
|
+
let mut names_to_untrack = Vec::new();
|
|
49
|
+
|
|
50
|
+
process_qualified_name(graph, const_name, nesting, &mut current_name, &mut names_to_untrack);
|
|
51
|
+
|
|
52
|
+
let (ParentScope::Some(name_id) | ParentScope::Attached(name_id)) = current_name else {
|
|
53
|
+
// Invalid names may leave temporary parts in the graph (e.g. `Foo` from `Foo::`).
|
|
54
|
+
for name_id in names_to_untrack {
|
|
55
|
+
graph.untrack_name(name_id);
|
|
56
|
+
}
|
|
40
57
|
return None;
|
|
41
58
|
};
|
|
42
59
|
|
|
@@ -51,8 +68,8 @@ pub fn nesting_stack_to_name_id(
|
|
|
51
68
|
fn process_qualified_name(
|
|
52
69
|
graph: &mut Graph,
|
|
53
70
|
qualified_name: &str,
|
|
71
|
+
current_nesting: Option<NameId>,
|
|
54
72
|
current_name: &mut ParentScope,
|
|
55
|
-
current_nesting: &mut Option<NameId>,
|
|
56
73
|
names_to_untrack: &mut Vec<NameId>,
|
|
57
74
|
) {
|
|
58
75
|
for part in qualified_name.split("::") {
|
|
@@ -64,17 +81,17 @@ fn process_qualified_name(
|
|
|
64
81
|
let (parent_scope, nesting_for_part) = if part.starts_with('<') {
|
|
65
82
|
let attached_id = match *current_name {
|
|
66
83
|
ParentScope::Some(id) | ParentScope::Attached(id) => Some(id),
|
|
67
|
-
_ =>
|
|
84
|
+
_ => current_nesting,
|
|
68
85
|
};
|
|
69
86
|
|
|
70
87
|
let attached = attached_id.map_or(ParentScope::None, ParentScope::Attached);
|
|
71
88
|
(attached, attached_id)
|
|
72
89
|
} else {
|
|
73
|
-
(*current_name,
|
|
90
|
+
(*current_name, current_nesting)
|
|
74
91
|
};
|
|
75
92
|
|
|
76
93
|
let str_id = graph.intern_string(part.to_owned());
|
|
77
|
-
let name_id = graph.add_name(
|
|
94
|
+
let name_id = graph.add_name(str_id, parent_scope, nesting_for_part);
|
|
78
95
|
names_to_untrack.push(name_id);
|
|
79
96
|
*current_name = ParentScope::Some(name_id);
|
|
80
97
|
}
|
|
@@ -186,4 +203,19 @@ mod tests {
|
|
|
186
203
|
assert!(foo_name.parent_scope().is_none());
|
|
187
204
|
assert!(foo_name.nesting().is_none());
|
|
188
205
|
}
|
|
206
|
+
|
|
207
|
+
#[test]
|
|
208
|
+
fn invalid_names_are_untracked() {
|
|
209
|
+
let mut graph = Graph::new();
|
|
210
|
+
let name_count = graph.names().len();
|
|
211
|
+
let string_count = graph.strings().len();
|
|
212
|
+
|
|
213
|
+
assert!(nesting_stack_to_name_id(&mut graph, "Foo::", vec!["Bar".into()]).is_none());
|
|
214
|
+
assert_eq!(name_count, graph.names().len());
|
|
215
|
+
assert_eq!(string_count, graph.strings().len());
|
|
216
|
+
|
|
217
|
+
assert!(name_in_nesting_to_name_id(&mut graph, "Foo::", None).is_none());
|
|
218
|
+
assert_eq!(name_count, graph.names().len());
|
|
219
|
+
assert_eq!(string_count, graph.strings().len());
|
|
220
|
+
}
|
|
189
221
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
use libc::{c_char, size_t};
|
|
2
2
|
use std::ffi::{CStr, CString};
|
|
3
|
+
use std::path::Path;
|
|
3
4
|
use std::slice;
|
|
4
5
|
use std::str::Utf8Error;
|
|
5
6
|
|
|
@@ -68,3 +69,39 @@ pub unsafe extern "C" fn free_c_string_array(ptr: *const *const c_char, count: u
|
|
|
68
69
|
.map(|arg| unsafe { CString::from_raw((*arg).cast_mut()) })
|
|
69
70
|
.collect();
|
|
70
71
|
}
|
|
72
|
+
|
|
73
|
+
/// Converts a Rust `&str` to an owned C string (`*const c_char`), suitable for returning across the
|
|
74
|
+
/// FFI boundary. The caller is responsible for freeing it with `free_c_string`.
|
|
75
|
+
///
|
|
76
|
+
/// # Panics
|
|
77
|
+
///
|
|
78
|
+
/// Panics if `value` contains an interior null byte, which should never occur in rubydex data.
|
|
79
|
+
#[must_use]
|
|
80
|
+
pub fn cstring_raw(value: &str) -> *const c_char {
|
|
81
|
+
CString::new(value).unwrap().into_raw().cast_const()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Rust uses backslashes as separators on Windows, but Ruby prefers forward slashes everywhere. We need to make sure
|
|
85
|
+
/// we're maintaining the right separators at the boundary.
|
|
86
|
+
#[must_use]
|
|
87
|
+
pub fn interop_path(path: &Path) -> String {
|
|
88
|
+
path.to_string_lossy().replace(std::path::MAIN_SEPARATOR, "/")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#[cfg(test)]
|
|
92
|
+
mod tests {
|
|
93
|
+
use super::*;
|
|
94
|
+
|
|
95
|
+
#[cfg(unix)]
|
|
96
|
+
#[test]
|
|
97
|
+
fn interop_path_leaves_a_unix_path_untouched() {
|
|
98
|
+
// A backslash is an ordinary character in a Unix file name, so rewriting it would name a different file.
|
|
99
|
+
assert_eq!(interop_path(Path::new(r"/tmp/we\ird")), r"/tmp/we\ird");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#[cfg(windows)]
|
|
103
|
+
#[test]
|
|
104
|
+
fn interop_path_separates_a_windows_path_with_forward_slashes() {
|
|
105
|
+
assert_eq!(interop_path(Path::new(r"D:\a\_temp\project")), "D:/a/_temp/project");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: send-private-method
|
|
3
|
+
description: Remediate a `send`/`__send__` call that reaches a private method, restoring a real public seam instead of bypassing visibility. Use when send is being used to invoke private functionality in a class.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Send Private Method
|
|
7
|
+
|
|
8
|
+
A test calls `obj.send(:some_private_method)` (or `__send__`) to invoke a method Ruby's visibility system would otherwise forbid. `send` deliberately bypasses encapsulation. `__send__` behaves identically and is the conventional spelling when a class defines its own `send`; Ruby warns when you redefine `__send__`, but it is not un-overridable. Remediation is the same for both. The smell is not "you used `send`" — it is "the test reaches past the public API into an implementation detail," which breaks the moment the private method is renamed, reorganized, or removed even though the public contract is unchanged.
|
|
9
|
+
|
|
10
|
+
## Confirm the receiver and the method
|
|
11
|
+
|
|
12
|
+
You have a call site: a file path and a line number. Before changing anything, answer two questions:
|
|
13
|
+
|
|
14
|
+
1. **What is the receiver's real type?** Trace the receiver expression at the flagged line back to its class. If it is a local/ivar, find where it is assigned (a `let`, `setup`, factory, or constructor).
|
|
15
|
+
2. **Where is the private method defined?** Search the workspace for `def <method_name>` (and `define_method :<method_name>`). Confirm the definition is under `private` (or `private :<method_name>`). Note its enclosing class/module and read the method body — you cannot decide the fix without knowing what the method does.
|
|
16
|
+
|
|
17
|
+
A name-based search matches by method *name* across the whole workspace, so a public method and a private method sharing the same unqualified name on different classes can produce a false positive. If the receiver's type has a *public* method of that name, this is not the smell — stop here.
|
|
18
|
+
|
|
19
|
+
## Decide which situation you are in
|
|
20
|
+
|
|
21
|
+
Read the method body and the test. Exactly one of these applies:
|
|
22
|
+
|
|
23
|
+
- **The caller needs behavior that should be public.** The private method does something the application legitimately depends on, and there is no public entry point that exposes it. The encapsulation boundary was drawn too tightly.
|
|
24
|
+
- **The caller is a test reaching past an existing public API.** A public method already wraps or drives this private one; the test bypassed it to assert on an intermediate result instead of the observable outcome. If the public outcome is already tested elsewhere, the private-method test is redundant — delete it rather than rewriting it.
|
|
25
|
+
- **The private method is the wrong home for the logic.** The behavior is genuinely useful to more than one caller, but it lives tucked away inside one class's private section. Both production and tests want it.
|
|
26
|
+
|
|
27
|
+
## Fix per situation
|
|
28
|
+
|
|
29
|
+
### 1. Promote to public (or `protected`)
|
|
30
|
+
|
|
31
|
+
If the behavior is part of the object's real contract, make it public and document it as API. Use `protected` only when every legitimate caller is an instance inside the same class hierarchy as the class that defines the method — never to paper over a single test's reach-in.
|
|
32
|
+
|
|
33
|
+
Before:
|
|
34
|
+
```ruby
|
|
35
|
+
class Foo
|
|
36
|
+
def bar = qux.sum
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def qux
|
|
41
|
+
@items.reject(&:voided?).map { |i| i.baz - i.discount }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
# test
|
|
45
|
+
foo.send(:qux)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
After:
|
|
49
|
+
```ruby
|
|
50
|
+
class Foo
|
|
51
|
+
def bar = qux.sum
|
|
52
|
+
|
|
53
|
+
# Public: items with discounts applied, voided items excluded.
|
|
54
|
+
# Used by reporting and by #bar's total.
|
|
55
|
+
def qux
|
|
56
|
+
@items.reject(&:voided?).map { |i| i.baz - i.discount }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
# test
|
|
60
|
+
foo.qux
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. Drive the public entry point, assert on the observable outcome
|
|
64
|
+
|
|
65
|
+
If a public method already exercises the private one, call the public method and assert on what it produces — not on the private intermediate.
|
|
66
|
+
|
|
67
|
+
Before:
|
|
68
|
+
```ruby
|
|
69
|
+
class Foo
|
|
70
|
+
def bar(baz) = Receipt.new(tax: baz.amount * qux(baz.quux))
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def qux(quux) = RATES.fetch(quux)
|
|
75
|
+
end
|
|
76
|
+
# test
|
|
77
|
+
foo.send(:qux, "CA") # => 0.05
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
After:
|
|
81
|
+
```ruby
|
|
82
|
+
# test — assert on the observable result of the public API
|
|
83
|
+
result = foo.bar(baz_with(quux: "CA", amount: 100))
|
|
84
|
+
assert_equal 5.0, result.tax
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 3. Extract to its own object/module
|
|
88
|
+
|
|
89
|
+
When the logic serves multiple callers but is trapped in one class's private section, move it to a dedicated object or module with a public interface both callers can use.
|
|
90
|
+
|
|
91
|
+
Before:
|
|
92
|
+
```ruby
|
|
93
|
+
class Foo
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def bar(raw) = raw.transform_keys(&:to_s).compact
|
|
97
|
+
end
|
|
98
|
+
# test
|
|
99
|
+
foo.send(:bar, { baz: "Q" })
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
After:
|
|
103
|
+
```ruby
|
|
104
|
+
class Bar
|
|
105
|
+
def call(raw) = raw.transform_keys(&:to_s).compact
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class Foo
|
|
109
|
+
def initialize(bar: Bar.new) = @bar = bar
|
|
110
|
+
def baz(raw) = @bar.call(raw)
|
|
111
|
+
end
|
|
112
|
+
# test
|
|
113
|
+
assert_equal({ "baz" => "Q" }, Bar.new.call({ baz: "Q" }))
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Private class methods and `module_function`
|
|
117
|
+
|
|
118
|
+
The three situations above cover instance methods. Two related visibility forms need special handling:
|
|
119
|
+
|
|
120
|
+
- **Private class methods** (`private_class_method :bar`, or a `def bar` under `private` inside `class << self`). A bare `private` does **not** apply to `def self.bar`: that singleton method stays public wherever it sits relative to the `private` line, so a public `def self.bar` under `private` is not this smell. A `send` like `Foo.send(:bar)` reaches a genuinely private singleton method. The remediation mirrors instance methods — promote with `public_class_method :bar` (or drop the `private_class_method :bar` call), drive an existing public class method, or extract. Do not use the instance-method patterns verbatim; the fix targets the singleton class.
|
|
121
|
+
- **`module_function`**. `module_function :foo` creates a public singleton method and a private instance method. If the test calls `instance.send(:foo)` on the private instance side, the public entry point already exists: call `MyModule.foo` directly. This is situation 2 — drive the public singleton method, do not promote the instance method.
|
|
122
|
+
|
|
123
|
+
## Legitimate exceptions — and what to do about them
|
|
124
|
+
|
|
125
|
+
Not every `send` to a private name is a smell to remove. Handle these deliberately:
|
|
126
|
+
|
|
127
|
+
- **Dynamic dispatch over a validated allowlist.** The method name is genuinely computed (`send(action)`), and the action set is bounded and checked. Constrain it: define an `ALLOWED_ACTIONS = %i[...].freeze` constant, guard with `raise unless ALLOWED_ACTIONS.include?(action)`, and call `public_send(action)` so the visibility system still applies. If every allowed action is public, the smell is gone.
|
|
128
|
+
- **Framework or DSL callbacks.** Some libraries require `send` to reach hooks they themselves marked private. If the method name is dictated by the framework contract, leave the call but add a one-line comment naming the framework and the callback it satisfies.
|
|
129
|
+
- **Third-party code you cannot change.** The private method belongs to a gem or an owned-elsewhere class you must not edit. Wrap the reach-in behind a single named adapter method in your own code with a comment stating why (e.g. `# Sends :bar to avoid the gem's private API; remove when upstream exposes a public hook.`). The adapter localizes the violation so it is auditable and removable in one place.
|
|
130
|
+
|
|
131
|
+
## What does NOT fix it
|
|
132
|
+
|
|
133
|
+
Switching `send` to `public_send` on a method that is *still private* does not fix anything — it just moves the failure from "silently bypassed encapsulation" to a `NoMethodError` at runtime. Answer the encapsulation question first; only then choose `public_send` (for dynamic dispatch over public methods) or a direct call (once promoted).
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubydex
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shopify
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A high-performance static analysis suite for Ruby, built in Rust with
|
|
14
14
|
Ruby APIs
|
|
@@ -24,6 +24,8 @@ files:
|
|
|
24
24
|
- README.md
|
|
25
25
|
- THIRD_PARTY_LICENSES.html
|
|
26
26
|
- exe/rdx
|
|
27
|
+
- ext/rubydex/config.c
|
|
28
|
+
- ext/rubydex/config.h
|
|
27
29
|
- ext/rubydex/declaration.c
|
|
28
30
|
- ext/rubydex/declaration.h
|
|
29
31
|
- ext/rubydex/definition.c
|
|
@@ -47,8 +49,18 @@ files:
|
|
|
47
49
|
- ext/rubydex/signature.h
|
|
48
50
|
- ext/rubydex/utils.c
|
|
49
51
|
- ext/rubydex/utils.h
|
|
52
|
+
- lib/ruby_lsp/rubydex/addon.rb
|
|
50
53
|
- lib/rubydex.rb
|
|
54
|
+
- lib/rubydex/cli.rb
|
|
55
|
+
- lib/rubydex/cli/command.rb
|
|
56
|
+
- lib/rubydex/cli/command/console.rb
|
|
57
|
+
- lib/rubydex/cli/command/lint.rb
|
|
58
|
+
- lib/rubydex/cli/command/lint/explain.rb
|
|
59
|
+
- lib/rubydex/cli/command/mcp.rb
|
|
60
|
+
- lib/rubydex/cli/command/query.rb
|
|
61
|
+
- lib/rubydex/cli/command/skill.rb
|
|
51
62
|
- lib/rubydex/comment.rb
|
|
63
|
+
- lib/rubydex/config.rb
|
|
52
64
|
- lib/rubydex/declaration.rb
|
|
53
65
|
- lib/rubydex/diagnostic.rb
|
|
54
66
|
- lib/rubydex/errors.rb
|
|
@@ -56,6 +68,13 @@ files:
|
|
|
56
68
|
- lib/rubydex/graph.rb
|
|
57
69
|
- lib/rubydex/keyword.rb
|
|
58
70
|
- lib/rubydex/keyword_parameter.rb
|
|
71
|
+
- lib/rubydex/linter.rb
|
|
72
|
+
- lib/rubydex/linter/custom_rule.rb
|
|
73
|
+
- lib/rubydex/linter/helpers/path_helpers.rb
|
|
74
|
+
- lib/rubydex/linter/helpers/source_access_helpers.rb
|
|
75
|
+
- lib/rubydex/linter/rule_loader.rb
|
|
76
|
+
- lib/rubydex/linter/rule_test_case.rb
|
|
77
|
+
- lib/rubydex/linter/runner.rb
|
|
59
78
|
- lib/rubydex/location.rb
|
|
60
79
|
- lib/rubydex/mcp_server.rb
|
|
61
80
|
- lib/rubydex/mcp_server/protocol.rb
|
|
@@ -68,8 +87,24 @@ files:
|
|
|
68
87
|
- lib/rubydex/mcp_server/tools/search_declarations_tool.rb
|
|
69
88
|
- lib/rubydex/mixin.rb
|
|
70
89
|
- lib/rubydex/reference.rb
|
|
90
|
+
- lib/rubydex/related_information.rb
|
|
91
|
+
- lib/rubydex/rule.rb
|
|
92
|
+
- lib/rubydex/rules/dynamic_ancestor.rb
|
|
93
|
+
- lib/rubydex/rules/dynamic_constant_reference.rb
|
|
94
|
+
- lib/rubydex/rules/dynamic_singleton_definition.rb
|
|
95
|
+
- lib/rubydex/rules/invalid_constant_visibility.rb
|
|
96
|
+
- lib/rubydex/rules/invalid_method_visibility.rb
|
|
97
|
+
- lib/rubydex/rules/parse_error.rb
|
|
98
|
+
- lib/rubydex/rules/parse_warning.rb
|
|
99
|
+
- lib/rubydex/rules/top_level_mixin_self.rb
|
|
100
|
+
- lib/rubydex/rules/undefined_constant_visibility_target.rb
|
|
101
|
+
- lib/rubydex/rules/undefined_method_visibility_target.rb
|
|
102
|
+
- lib/rubydex/severity.rb
|
|
71
103
|
- lib/rubydex/signature.rb
|
|
104
|
+
- lib/rubydex/skill.rb
|
|
105
|
+
- lib/rubydex/skill_registry.rb
|
|
72
106
|
- lib/rubydex/version.rb
|
|
107
|
+
- lib/rubydex_linter/rules/rule_structure.rb
|
|
73
108
|
- rbi/rubydex.rbi
|
|
74
109
|
- rust/Cargo.lock
|
|
75
110
|
- rust/Cargo.toml
|
|
@@ -79,6 +114,8 @@ files:
|
|
|
79
114
|
- rust/rubydex-sys/Cargo.toml
|
|
80
115
|
- rust/rubydex-sys/build.rs
|
|
81
116
|
- rust/rubydex-sys/cbindgen.toml
|
|
117
|
+
- rust/rubydex-sys/src/config_api.rs
|
|
118
|
+
- rust/rubydex-sys/src/cypher_api.rs
|
|
82
119
|
- rust/rubydex-sys/src/declaration_api.rs
|
|
83
120
|
- rust/rubydex-sys/src/definition_api.rs
|
|
84
121
|
- rust/rubydex-sys/src/diagnostic_api.rs
|
|
@@ -92,6 +129,7 @@ files:
|
|
|
92
129
|
- rust/rubydex-sys/src/utils.rs
|
|
93
130
|
- rust/rubydex/Cargo.toml
|
|
94
131
|
- rust/rubydex/benches/graph_memory.rs
|
|
132
|
+
- rust/rubydex/src/bin/generate_ruby_rules.rs
|
|
95
133
|
- rust/rubydex/src/compile_assertions.rs
|
|
96
134
|
- rust/rubydex/src/config.rs
|
|
97
135
|
- rust/rubydex/src/diagnostic.rs
|
|
@@ -128,6 +166,7 @@ files:
|
|
|
128
166
|
- rust/rubydex/src/operation/mod.rs
|
|
129
167
|
- rust/rubydex/src/operation/printer.rs
|
|
130
168
|
- rust/rubydex/src/operation/ruby_builder.rs
|
|
169
|
+
- rust/rubydex/src/path_helpers.rs
|
|
131
170
|
- rust/rubydex/src/position.rs
|
|
132
171
|
- rust/rubydex/src/query.rs
|
|
133
172
|
- rust/rubydex/src/query/cypher.rs
|
|
@@ -146,6 +185,7 @@ files:
|
|
|
146
185
|
- rust/rubydex/src/test_utils/local_graph_test.rs
|
|
147
186
|
- rust/rubydex/tests/cli.rs
|
|
148
187
|
- rust/rustfmt.toml
|
|
188
|
+
- skills/send-private-method/SKILL.md
|
|
149
189
|
homepage: https://github.com/Shopify/rubydex
|
|
150
190
|
licenses:
|
|
151
191
|
- MIT
|