rubydex 0.4.0-aarch64-linux → 0.4.2-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 +4 -4
- data/README.md +9 -14
- data/THIRD_PARTY_LICENSES.html +13 -24
- data/ext/rubydex/definition.c +34 -0
- data/ext/rubydex/definition.h +3 -0
- data/ext/rubydex/diagnostic.c +2 -49
- data/ext/rubydex/extconf.rb +4 -2
- data/ext/rubydex/graph.c +59 -11
- 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/cli/command/dead_code.rb +59 -0
- data/lib/rubydex/cli/command/lint/explain.rb +8 -6
- data/lib/rubydex/cli/command/lint.rb +10 -4
- data/lib/rubydex/librubydex_sys.so +0 -0
- data/lib/rubydex/linter/rule_loader.rb +1 -1
- data/lib/rubydex/mcp_server/tools/codebase_stats_tool.rb +1 -1
- data/lib/rubydex/mcp_server/tools/find_constant_references_tool.rb +4 -4
- data/lib/rubydex/mcp_server/tools/find_dead_code_candidates_tool.rb +33 -0
- data/lib/rubydex/mcp_server/tools/get_declaration_tool.rb +2 -2
- data/lib/rubydex/mcp_server/tools/get_descendants_tool.rb +4 -4
- data/lib/rubydex/mcp_server/tools/get_file_declarations_tool.rb +2 -2
- data/lib/rubydex/mcp_server/tools/search_declarations_tool.rb +6 -6
- data/lib/rubydex/mcp_server.rb +24 -12
- 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/skill.rb +1 -0
- data/lib/rubydex/version.rb +1 -1
- data/lib/rubydex.rb +2 -0
- data/lib/rubydex_linter/rules/rule_structure.rb +19 -4
- data/rbi/rubydex.rbi +32 -130
- data/rust/Cargo.lock +17 -11
- data/rust/Cargo.toml +3 -0
- data/rust/rubydex/Cargo.toml +6 -2
- data/rust/rubydex/src/bin/generate_ruby_rules.rs +94 -0
- data/rust/rubydex/src/compile_assertions.rs +2 -0
- data/rust/rubydex/src/config.rs +1 -1
- data/rust/rubydex/src/diagnostic.rs +142 -51
- data/rust/rubydex/src/indexing/rbs_indexer.rs +4 -4
- data/rust/rubydex/src/indexing/ruby_indexer.rs +26 -18
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +42 -2
- data/rust/rubydex/src/indexing.rs +6 -6
- data/rust/rubydex/src/listing.rs +12 -12
- data/rust/rubydex/src/model/built_in.rs +1 -1
- data/rust/rubydex/src/model/comment.rs +4 -4
- data/rust/rubydex/src/model/declaration.rs +25 -17
- data/rust/rubydex/src/model/document.rs +11 -11
- data/rust/rubydex/src/model/graph.rs +3 -3
- data/rust/rubydex/src/offset.rs +1 -13
- data/rust/rubydex/src/operation/ruby_builder.rs +29 -19
- data/rust/rubydex/src/query.rs +193 -2
- data/rust/rubydex/src/test_utils/graph_test.rs +2 -2
- data/rust/rubydex/src/test_utils/local_graph_test.rs +13 -6
- data/rust/rubydex-sys/Cargo.toml +2 -2
- data/rust/rubydex-sys/src/definition_api.rs +69 -2
- data/rust/rubydex-sys/src/diagnostic_api.rs +0 -35
- data/rust/rubydex-sys/src/graph_api.rs +193 -45
- data/rust/rubydex-sys/src/name_api.rs +219 -48
- metadata +199 -187
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ef406f89b33ab38d3e0cc4423e9f651003144c22c55feebfc0b5750878ec1e3
|
|
4
|
+
data.tar.gz: 719fa68c55a21101cf13115a6089c23cb145073d4bd9d87e6730335742e71ba1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e4e82d8b4a75b1eac0cb42b43e1c053cf3088167b6c742ee04ff8a3de8f4e97a278ba1f24cb08cfc150ea2244de26e02c84ab3435f641b0899040c8e54a5272
|
|
7
|
+
data.tar.gz: 0ff32e86e8eb8e5f11745e796f8e1a3834080f489b084c93c5a3eab8260d5fb9353890de4367f0a71bf758eef0db98344982144d788f0b045aa8544a8d3ec478
|
data/README.md
CHANGED
|
@@ -114,7 +114,7 @@ Thread safety comes from an `RwLock` on the Rust side, **not** from Ruby's
|
|
|
114
114
|
|
|
115
115
|
All built-in tools are experimental. These tools can change without deprecation warnings.
|
|
116
116
|
|
|
117
|
-
###
|
|
117
|
+
### Querying
|
|
118
118
|
|
|
119
119
|
Rubydex exposes the indexed graph through a read-only subset of the
|
|
120
120
|
[Cypher](https://opencypher.org/) query language. Only read clauses (`MATCH`,
|
|
@@ -153,27 +153,21 @@ puts result.render("json")
|
|
|
153
153
|
puts Rubydex::Query.schema("table")
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
-
###
|
|
156
|
+
### Linter
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
See the [linter docs](docs/linter.md).
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
### Dead code candidates
|
|
161
161
|
|
|
162
162
|
```bash
|
|
163
|
-
bundle exec rdx
|
|
163
|
+
bundle exec rdx dead_code
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
Prints a list of potentially dead code candidates. The list is composed of all declarations for which the analysis found no references for in the codebase.
|
|
167
167
|
|
|
168
|
-
|
|
168
|
+
Since some declarations may be used through meta-programming or otherwise untyped code, there may be false positives and the removal of candidates must be done carefully with context about the codebase.
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
[linter.rules.<Rule name>]
|
|
172
|
-
enabled = true
|
|
173
|
-
exclude = ["path_to_skip/**"]
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### `rdx mcp`
|
|
170
|
+
### MCP server
|
|
177
171
|
|
|
178
172
|
Rubydex can run as an MCP (Model Context Protocol) server, enabling AI assistants
|
|
179
173
|
like Claude to semantically query your Ruby codebase.
|
|
@@ -214,6 +208,7 @@ like Claude to semantically query your Ruby codebase.
|
|
|
214
208
|
| `get_declaration` | Full details by fully qualified name with docs, ancestors, members |
|
|
215
209
|
| `get_descendants` | What classes/modules inherit from or include this one |
|
|
216
210
|
| `find_constant_references` | All precise, resolved constant references across the codebase |
|
|
211
|
+
| `find_dead_code_candidates` | List of potentially unused code |
|
|
217
212
|
| `get_file_declarations` | List declarations defined in a specific file |
|
|
218
213
|
| `codebase_stats` | High-level statistics about the indexed codebase |
|
|
219
214
|
|
data/THIRD_PARTY_LICENSES.html
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
<h2>Overview of licenses:</h2>
|
|
52
52
|
<ul class="licenses-overview">
|
|
53
|
-
<li><a href="#Apache-2.0">Apache License 2.0</a> (
|
|
53
|
+
<li><a href="#Apache-2.0">Apache License 2.0</a> (120)</li>
|
|
54
54
|
<li><a href="#Unicode-3.0">Unicode License v3</a> (19)</li>
|
|
55
55
|
<li><a href="#MIT">MIT License</a> (17)</li>
|
|
56
56
|
<li><a href="#BSD-2-Clause">BSD 2-Clause "Simplified" License</a> (2)</li>
|
|
@@ -2515,6 +2515,9 @@ limitations under the License.
|
|
|
2515
2515
|
<li><a
|
|
2516
2516
|
href=" https://github.com/comex/rust-shlex ">shlex
|
|
2517
2517
|
1.3.0</a></li>
|
|
2518
|
+
<li><a
|
|
2519
|
+
href=" https://github.com/comex/rust-shlex ">shlex
|
|
2520
|
+
2.0.1</a></li>
|
|
2518
2521
|
<li><a
|
|
2519
2522
|
href=" https://github.com/dtolnay/syn ">syn
|
|
2520
2523
|
2.0.104</a></li>
|
|
@@ -2670,7 +2673,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
2670
2673
|
<ul class="license-used-by">
|
|
2671
2674
|
<li><a
|
|
2672
2675
|
href=" https://github.com/DoumanAsh/xxhash-rust ">xxhash-rust
|
|
2673
|
-
0.8.
|
|
2676
|
+
0.8.16</a></li>
|
|
2674
2677
|
</ul>
|
|
2675
2678
|
<pre class="license-text">Boost Software License - Version 1.0 - August 17th, 2003
|
|
2676
2679
|
|
|
@@ -2821,26 +2824,6 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
|
2821
2824
|
|
|
2822
2825
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
2823
2826
|
|
|
2824
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
2825
|
-
</pre>
|
|
2826
|
-
</li>
|
|
2827
|
-
<li class="license">
|
|
2828
|
-
<h3 id="MIT">MIT License</h3>
|
|
2829
|
-
<h4>Used by:</h4>
|
|
2830
|
-
<ul class="license-used-by">
|
|
2831
|
-
<li><a
|
|
2832
|
-
href=" https://github.com/ruby/prism ">ruby-prism-sys
|
|
2833
|
-
1.9.0</a></li>
|
|
2834
|
-
<li><a
|
|
2835
|
-
href=" https://github.com/ruby/prism ">ruby-prism
|
|
2836
|
-
1.9.0</a></li>
|
|
2837
|
-
</ul>
|
|
2838
|
-
<pre class="license-text">Copyright 2022-present, Shopify Inc.
|
|
2839
|
-
|
|
2840
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
2841
|
-
|
|
2842
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
2843
|
-
|
|
2844
2827
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
2845
2828
|
</pre>
|
|
2846
2829
|
</li>
|
|
@@ -2881,13 +2864,19 @@ SOFTWARE.
|
|
|
2881
2864
|
<ul class="license-used-by">
|
|
2882
2865
|
<li><a
|
|
2883
2866
|
href=" https://github.com/Shopify/rubydex ">rubydex
|
|
2884
|
-
0.2
|
|
2867
|
+
0.4.2</a></li>
|
|
2885
2868
|
<li><a
|
|
2886
2869
|
href=" https://github.com/Shopify/rubydex ">rubydex-sys
|
|
2887
|
-
0.2
|
|
2870
|
+
0.4.2</a></li>
|
|
2888
2871
|
<li><a
|
|
2889
2872
|
href=" https://github.com/DimaKudosh/difflib ">difflib
|
|
2890
2873
|
0.4.0</a></li>
|
|
2874
|
+
<li><a
|
|
2875
|
+
href=" https://github.com/ruby/prism ">ruby-prism-sys
|
|
2876
|
+
2.0.0-rc.2</a></li>
|
|
2877
|
+
<li><a
|
|
2878
|
+
href=" https://github.com/ruby/prism ">ruby-prism
|
|
2879
|
+
2.0.0-rc.2</a></li>
|
|
2891
2880
|
</ul>
|
|
2892
2881
|
<pre class="license-text">MIT License
|
|
2893
2882
|
|
data/ext/rubydex/definition.c
CHANGED
|
@@ -38,6 +38,17 @@ VALUE cClassVariableDefinition;
|
|
|
38
38
|
VALUE cMethodAliasDefinition;
|
|
39
39
|
VALUE cGlobalVariableAliasDefinition;
|
|
40
40
|
|
|
41
|
+
uint64_t rdxi_definition_id_for_graph(VALUE definition, VALUE graph_obj) {
|
|
42
|
+
HandleData *data;
|
|
43
|
+
TypedData_Get_Struct(definition, HandleData, &handle_type, data);
|
|
44
|
+
|
|
45
|
+
if (data->graph_obj != graph_obj) {
|
|
46
|
+
rb_raise(rb_eArgError, "definition must belong to this graph");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return data->id;
|
|
50
|
+
}
|
|
51
|
+
|
|
41
52
|
// Keep this in sync with definition.rs
|
|
42
53
|
VALUE rdxi_definition_class_for_kind(DefinitionKind kind) {
|
|
43
54
|
switch (kind) {
|
|
@@ -153,6 +164,25 @@ static VALUE rdxr_definition_name(VALUE self) {
|
|
|
153
164
|
return rdxi_owned_c_string_to_ruby(name);
|
|
154
165
|
}
|
|
155
166
|
|
|
167
|
+
/*
|
|
168
|
+
* call-seq:
|
|
169
|
+
* raw_name -> String
|
|
170
|
+
*
|
|
171
|
+
* Returns the class, module, or constant name as written in source, preserving explicit parent scopes like "Foo::Bar"
|
|
172
|
+
* and rooted paths like "::Bar".
|
|
173
|
+
*/
|
|
174
|
+
static VALUE rdxr_definition_raw_name(VALUE self) {
|
|
175
|
+
HandleData *data;
|
|
176
|
+
void *graph = rdxi_graph_from_handle(self, &data);
|
|
177
|
+
|
|
178
|
+
const char *name = rdx_definition_raw_name(graph, data->id);
|
|
179
|
+
if (name == NULL) {
|
|
180
|
+
rb_raise(rb_eRuntimeError, "Definition not found");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return rdxi_owned_c_string_to_ruby(name);
|
|
184
|
+
}
|
|
185
|
+
|
|
156
186
|
/*
|
|
157
187
|
* call-seq:
|
|
158
188
|
* deprecated? -> bool
|
|
@@ -442,6 +472,7 @@ void rdxi_initialize_definition(VALUE mod) {
|
|
|
442
472
|
rb_define_method(cDefinition, "document", rdxr_definition_document, 0);
|
|
443
473
|
|
|
444
474
|
cClassDefinition = rb_define_class_under(mRubydex, "ClassDefinition", cDefinition);
|
|
475
|
+
rb_define_method(cClassDefinition, "raw_name", rdxr_definition_raw_name, 0);
|
|
445
476
|
rb_define_method(cClassDefinition, "superclass", rdxr_class_definition_superclass, 0);
|
|
446
477
|
rb_define_method(cClassDefinition, "mixins", rdxr_definition_mixins, 0);
|
|
447
478
|
|
|
@@ -449,10 +480,13 @@ void rdxi_initialize_definition(VALUE mod) {
|
|
|
449
480
|
rb_define_method(cSingletonClassDefinition, "mixins", rdxr_definition_mixins, 0);
|
|
450
481
|
|
|
451
482
|
cModuleDefinition = rb_define_class_under(mRubydex, "ModuleDefinition", cDefinition);
|
|
483
|
+
rb_define_method(cModuleDefinition, "raw_name", rdxr_definition_raw_name, 0);
|
|
452
484
|
rb_define_method(cModuleDefinition, "mixins", rdxr_definition_mixins, 0);
|
|
453
485
|
|
|
454
486
|
cConstantDefinition = rb_define_class_under(mRubydex, "ConstantDefinition", cDefinition);
|
|
487
|
+
rb_define_method(cConstantDefinition, "raw_name", rdxr_definition_raw_name, 0);
|
|
455
488
|
cConstantAliasDefinition = rb_define_class_under(mRubydex, "ConstantAliasDefinition", cDefinition);
|
|
489
|
+
rb_define_method(cConstantAliasDefinition, "raw_name", rdxr_definition_raw_name, 0);
|
|
456
490
|
cConstantVisibilityDefinition = rb_define_class_under(mRubydex, "ConstantVisibilityDefinition", cDefinition);
|
|
457
491
|
cMethodVisibilityDefinition = rb_define_class_under(mRubydex, "MethodVisibilityDefinition", cDefinition);
|
|
458
492
|
cMethodDefinition = rb_define_class_under(mRubydex, "MethodDefinition", cDefinition);
|
data/ext/rubydex/definition.h
CHANGED
|
@@ -22,6 +22,9 @@ extern VALUE cGlobalVariableAliasDefinition;
|
|
|
22
22
|
|
|
23
23
|
void rdxi_initialize_definition(VALUE mRubydex);
|
|
24
24
|
|
|
25
|
+
// Returns the definition ID after verifying that the definition belongs to `graph_obj`.
|
|
26
|
+
uint64_t rdxi_definition_id_for_graph(VALUE definition, VALUE graph_obj);
|
|
27
|
+
|
|
25
28
|
// Returns the Ruby class for a given DefinitionKind without calling back into Rust
|
|
26
29
|
VALUE rdxi_definition_class_for_kind(DefinitionKind kind);
|
|
27
30
|
|
data/ext/rubydex/diagnostic.c
CHANGED
|
@@ -6,11 +6,8 @@
|
|
|
6
6
|
* mRubydex = rb_define_module("Rubydex")
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
static VALUE mRubydex;
|
|
10
9
|
static VALUE mRules;
|
|
11
10
|
VALUE cDiagnostic;
|
|
12
|
-
static VALUE cRule;
|
|
13
|
-
static ID id_default_severity;
|
|
14
11
|
|
|
15
12
|
VALUE rdxi_build_diagnostic_severity_value(VALUE mRubydex, DiagnosticSeverity severity) {
|
|
16
13
|
VALUE mSeverity = rb_const_get(mRubydex, rb_intern("Severity"));
|
|
@@ -31,55 +28,11 @@ VALUE rdxi_build_diagnostic_severity_value(VALUE mRubydex, DiagnosticSeverity se
|
|
|
31
28
|
return Qnil;
|
|
32
29
|
}
|
|
33
30
|
|
|
34
|
-
/*
|
|
35
|
-
* call-seq:
|
|
36
|
-
* default_severity -> Rubydex::Severity::Base
|
|
37
|
-
*
|
|
38
|
-
* Returns the Rubydex::Severity subclass the diagnostics of this rule get unless the configuration overrides it.
|
|
39
|
-
*/
|
|
40
|
-
static VALUE rdxr_generated_rule_default_severity(VALUE self) {
|
|
41
|
-
VALUE severity = rb_attr_get(self, id_default_severity);
|
|
42
|
-
|
|
43
|
-
// Class-level instance variables are not inherited, so a class inheriting from a generated rule reaches this without
|
|
44
|
-
// a severity of its own.
|
|
45
|
-
if (NIL_P(severity)) {
|
|
46
|
-
rb_raise(rb_eRuntimeError, "Rule %s has no default severity", rb_class2name(self));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return severity;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Generates a rule class for every rule the graph can report. Severities are resolved here rather than when they are
|
|
53
|
-
// read, which is why Rubydex::Severity is loaded before this extension (see `lib/rubydex.rb`).
|
|
54
|
-
static void define_generated_rules(void) {
|
|
55
|
-
CRuleArray rule_array = rdx_rules();
|
|
56
|
-
VALUE rules = rb_ary_new_capa((long)rule_array.len);
|
|
57
|
-
|
|
58
|
-
for (size_t i = 0; i < rule_array.len; i++) {
|
|
59
|
-
CRule built_in_rule = rule_array.items[i];
|
|
60
|
-
VALUE name = rb_utf8_str_new(built_in_rule.name, (long)built_in_rule.name_length);
|
|
61
|
-
VALUE rule = rb_define_class_under(mRules, StringValueCStr(name), cRule);
|
|
62
|
-
VALUE severity = rdxi_build_diagnostic_severity_value(mRubydex, built_in_rule.default_severity);
|
|
63
|
-
|
|
64
|
-
rb_ivar_set(rule, id_default_severity, severity);
|
|
65
|
-
rb_define_singleton_method(rule, "default_severity", rdxr_generated_rule_default_severity, 0);
|
|
66
|
-
rb_ary_push(rules, rule);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
rdx_rules_free(rule_array);
|
|
70
|
-
rb_define_const(mRules, "ALL", rb_obj_freeze(rules));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
31
|
VALUE rdxi_rule_class_from_name(const char *name, size_t length) {
|
|
74
32
|
return rb_const_get_at(mRules, rb_intern2(name, (long)length));
|
|
75
33
|
}
|
|
76
34
|
|
|
77
35
|
void rdxi_initialize_diagnostic(VALUE moduleRubydex) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
cDiagnostic = rb_define_class_under(mRubydex, "Diagnostic", rb_cObject);
|
|
82
|
-
cRule = rb_define_class_under(mRubydex, "Rule", rb_cObject);
|
|
83
|
-
mRules = rb_define_module_under(mRubydex, "Rules");
|
|
84
|
-
define_generated_rules();
|
|
36
|
+
cDiagnostic = rb_define_class_under(moduleRubydex, "Diagnostic", rb_cObject);
|
|
37
|
+
mRules = rb_define_module_under(moduleRubydex, "Rules");
|
|
85
38
|
}
|
data/ext/rubydex/extconf.rb
CHANGED
|
@@ -23,12 +23,12 @@ release = ENV["RELEASE"] || !developing_rubydex
|
|
|
23
23
|
root_dir = gem_dir.join("rust")
|
|
24
24
|
target_dir = root_dir.join("target")
|
|
25
25
|
target_dir = target_dir.join("x86_64-pc-windows-gnu") if Gem.win_platform?
|
|
26
|
-
target_dir = target_dir.join(
|
|
26
|
+
target_dir = target_dir.join("$(RUST_PROFILE)")
|
|
27
27
|
|
|
28
28
|
bindings_path = root_dir.join("rubydex-sys").join("rustbindings.h")
|
|
29
29
|
|
|
30
30
|
cargo_args = ["--manifest-path #{root_dir.join("Cargo.toml")}"]
|
|
31
|
-
cargo_args << "
|
|
31
|
+
cargo_args << "$(CARGO_PROFILE_FLAG)"
|
|
32
32
|
|
|
33
33
|
if Gem.win_platform?
|
|
34
34
|
cargo_args << "--target x86_64-pc-windows-gnu"
|
|
@@ -102,6 +102,8 @@ makefile = File.read("Makefile")
|
|
|
102
102
|
|
|
103
103
|
new_makefile = makefile.gsub("$(OBJS): $(HDRS) $(ruby_headers)", <<~MAKEFILE.chomp)
|
|
104
104
|
.PHONY: compile_rust
|
|
105
|
+
RUST_PROFILE = $(if $(strip $(RELEASE)),release,#{developing_rubydex ? "debug" : "release"})
|
|
106
|
+
CARGO_PROFILE_FLAG = $(if $(filter release,$(RUST_PROFILE)),--release)
|
|
105
107
|
RUST_SRCS = #{File.expand_path("Cargo.toml", root_dir)} #{File.expand_path("Cargo.lock", root_dir)} #{rust_srcs.join(" ")}
|
|
106
108
|
|
|
107
109
|
.rust_built: $(RUST_SRCS)
|
data/ext/rubydex/graph.c
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include "graph.h"
|
|
2
2
|
#include "config.h"
|
|
3
3
|
#include "declaration.h"
|
|
4
|
+
#include "definition.h"
|
|
4
5
|
#include "diagnostic.h"
|
|
5
6
|
#include "document.h"
|
|
6
7
|
#include "location.h"
|
|
@@ -256,6 +257,30 @@ static VALUE rdxr_graph_fuzzy_search(int argc, VALUE *argv, VALUE self) {
|
|
|
256
257
|
return rdxr_graph_yield_search_results(self, iter);
|
|
257
258
|
}
|
|
258
259
|
|
|
260
|
+
/*
|
|
261
|
+
* call-seq:
|
|
262
|
+
* dead_code_candidates -> Enumerator[Rubydex::Declaration]
|
|
263
|
+
*
|
|
264
|
+
* Returns an enumerator over declarations that may be unused because the analysis could not find any references to
|
|
265
|
+
* them. This misses metaprogramming or untyped code, which is why the term candidates is used.
|
|
266
|
+
*
|
|
267
|
+
* Currently, only supports constants.
|
|
268
|
+
*/
|
|
269
|
+
static VALUE rdxr_graph_dead_code_candidates(VALUE self) {
|
|
270
|
+
if (!rb_block_given_p()) {
|
|
271
|
+
return rb_enumeratorize(self, rb_str_new2("dead_code_candidates"), 0, NULL);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
void *graph;
|
|
275
|
+
TypedData_Get_Struct(self, void *, &graph_type, graph);
|
|
276
|
+
|
|
277
|
+
void *iter = rdx_graph_dead_code_candidates(graph);
|
|
278
|
+
VALUE args = rb_ary_new_from_args(2, self, ULL2NUM((uintptr_t)iter));
|
|
279
|
+
rb_ensure(rdxi_declarations_yield, args, rdxi_declarations_ensure, args);
|
|
280
|
+
|
|
281
|
+
return self;
|
|
282
|
+
}
|
|
283
|
+
|
|
259
284
|
// Body function for rb_ensure in Graph#documents
|
|
260
285
|
static VALUE graph_documents_yield(VALUE args) {
|
|
261
286
|
VALUE self = rb_ary_entry(args, 0);
|
|
@@ -486,26 +511,48 @@ static VALUE rdxr_graph_set_encoding(VALUE self, VALUE encoding) {
|
|
|
486
511
|
|
|
487
512
|
/*
|
|
488
513
|
* call-seq:
|
|
489
|
-
* resolve_constant(name,
|
|
514
|
+
* resolve_constant(name, context) -> Rubydex::Declaration?
|
|
490
515
|
*
|
|
491
|
-
* Runs the resolver on a single constant reference
|
|
516
|
+
* Runs the resolver on a single constant reference using an array of nesting names or a namespace definition as its
|
|
517
|
+
* context.
|
|
492
518
|
*/
|
|
493
|
-
static VALUE rdxr_graph_resolve_constant(VALUE self, VALUE const_name, VALUE
|
|
519
|
+
static VALUE rdxr_graph_resolve_constant(VALUE self, VALUE const_name, VALUE context) {
|
|
494
520
|
Check_Type(const_name, T_STRING);
|
|
495
|
-
rdxi_check_array_of_strings(nesting);
|
|
496
521
|
const char *const_name_string = StringValueCStr(const_name);
|
|
497
522
|
|
|
498
|
-
// Convert the given file paths into a char** array, so that we can pass to Rust
|
|
499
|
-
size_t length = RARRAY_LEN(nesting);
|
|
500
|
-
char **converted_file_paths = rdxi_str_array_to_char(nesting, length);
|
|
501
|
-
|
|
502
523
|
void *graph;
|
|
503
524
|
TypedData_Get_Struct(self, void *, &graph_type, graph);
|
|
504
525
|
|
|
505
|
-
const CDeclaration *decl
|
|
506
|
-
|
|
526
|
+
const CDeclaration *decl;
|
|
527
|
+
if (RB_TYPE_P(context, T_ARRAY)) {
|
|
528
|
+
rdxi_check_array_of_strings(context);
|
|
529
|
+
|
|
530
|
+
size_t length = RARRAY_LEN(context);
|
|
531
|
+
char **converted_names = rdxi_str_array_to_char(context, length);
|
|
532
|
+
decl = rdx_graph_resolve_constant(graph, const_name_string, (const char **)converted_names, length);
|
|
533
|
+
rdxi_free_str_array(converted_names, length);
|
|
534
|
+
} else if (rb_obj_is_kind_of(context, cClassDefinition) ||
|
|
535
|
+
rb_obj_is_kind_of(context, cSingletonClassDefinition) ||
|
|
536
|
+
rb_obj_is_kind_of(context, cModuleDefinition)) {
|
|
537
|
+
uint64_t definition_id = rdxi_definition_id_for_graph(context, self);
|
|
538
|
+
CDefinitionConstantResolutionResult result =
|
|
539
|
+
rdx_graph_resolve_constant_from_definition(graph, const_name_string, definition_id);
|
|
540
|
+
|
|
541
|
+
if (result.status == CDefinitionConstantResolution_DefinitionNotFound) {
|
|
542
|
+
rb_raise(rb_eRuntimeError, "Definition not found");
|
|
543
|
+
}
|
|
544
|
+
if (result.status == CDefinitionConstantResolution_InvalidDefinitionKind) {
|
|
545
|
+
// This is unreachable because the rb_obj_is_kind_of checks above only accept supported definition kinds.
|
|
546
|
+
rb_raise(rb_eRuntimeError, "Unexpected definition kind");
|
|
547
|
+
}
|
|
507
548
|
|
|
508
|
-
|
|
549
|
+
decl = result.declaration;
|
|
550
|
+
} else {
|
|
551
|
+
rb_raise(
|
|
552
|
+
rb_eTypeError,
|
|
553
|
+
"context must be an Array of Strings, ClassDefinition, SingletonClassDefinition, or ModuleDefinition"
|
|
554
|
+
);
|
|
555
|
+
}
|
|
509
556
|
|
|
510
557
|
if (decl == NULL) {
|
|
511
558
|
return Qnil;
|
|
@@ -938,6 +985,7 @@ void rdxi_initialize_graph(VALUE moduleRubydex) {
|
|
|
938
985
|
rb_define_method(cGraph, "[]", rdxr_graph_aref, 1);
|
|
939
986
|
rb_define_method(cGraph, "search", rdxr_graph_search, -1);
|
|
940
987
|
rb_define_method(cGraph, "fuzzy_search", rdxr_graph_fuzzy_search, -1);
|
|
988
|
+
rb_define_method(cGraph, "dead_code_candidates", rdxr_graph_dead_code_candidates, 0);
|
|
941
989
|
rb_define_method(cGraph, "encoding=", rdxr_graph_set_encoding, 1);
|
|
942
990
|
rb_define_method(cGraph, "resolve_require_path", rdxr_graph_resolve_require_path, 2);
|
|
943
991
|
rb_define_method(cGraph, "require_paths", rdxr_graph_require_paths, 1);
|
data/lib/rubydex/3.2/rubydex.so
CHANGED
|
Binary file
|
data/lib/rubydex/3.3/rubydex.so
CHANGED
|
Binary file
|
data/lib/rubydex/3.4/rubydex.so
CHANGED
|
Binary file
|
data/lib/rubydex/4.0/rubydex.so
CHANGED
|
Binary file
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubydex/cli/command"
|
|
4
|
+
|
|
5
|
+
module Rubydex
|
|
6
|
+
module CLI
|
|
7
|
+
# `rdx dead_code` — lists dead code candidates in the current workspace.
|
|
8
|
+
class Command
|
|
9
|
+
class DeadCode < Command
|
|
10
|
+
command "dead_code"
|
|
11
|
+
summary "List dead code candidates in the current workspace"
|
|
12
|
+
|
|
13
|
+
#: -> void
|
|
14
|
+
def run
|
|
15
|
+
parse_options!
|
|
16
|
+
abort_with_usage("unexpected argument: #{argv.first}") unless argv.empty?
|
|
17
|
+
|
|
18
|
+
require "rubydex/linter/helpers/path_helpers"
|
|
19
|
+
|
|
20
|
+
$stderr.puts("Looking for potentially dead code...")
|
|
21
|
+
graph = build_graph($stderr, workspace_path: current_workspace_path)
|
|
22
|
+
candidates = graph.dead_code_candidates.sort_by(&:name)
|
|
23
|
+
|
|
24
|
+
candidates.each do |candidate|
|
|
25
|
+
puts(candidate.name)
|
|
26
|
+
candidate.definitions.map(&:location).sort.each do |location|
|
|
27
|
+
display = location.to_display
|
|
28
|
+
path = Rubydex::Linter::Helpers::PathHelpers.display_path(display, workspace: graph.workspace_path)
|
|
29
|
+
puts(" #{path}:#{display.start_line},#{display.start_column}-#{display.end_line},#{display.end_column}")
|
|
30
|
+
end
|
|
31
|
+
puts
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
print_summary(graph.documents.count, candidates.length)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
#: (Integer file_count, Integer candidate_count) -> void
|
|
40
|
+
def print_summary(file_count, candidate_count)
|
|
41
|
+
if candidate_count.zero?
|
|
42
|
+
puts("#{file_count} #{pluralize("file", file_count)} inspected, no dead code candidates found")
|
|
43
|
+
return
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
puts(
|
|
47
|
+
"#{file_count} #{pluralize("file", file_count)} inspected, " \
|
|
48
|
+
"#{candidate_count} dead code #{pluralize("candidate", candidate_count)} found",
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#: (String word, Integer count) -> String
|
|
53
|
+
def pluralize(word, count)
|
|
54
|
+
count == 1 ? word : "#{word}s"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -9,8 +9,10 @@ module Rubydex
|
|
|
9
9
|
class Lint
|
|
10
10
|
# `rdx lint explain <RULE>` — prints documentation for rules with a matching name.
|
|
11
11
|
class Explain < Command
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
BASE_PATHS = [
|
|
13
|
+
File.expand_path("../../../linter/custom_rule.rb", __dir__),
|
|
14
|
+
File.expand_path("../../../rule.rb", __dir__),
|
|
15
|
+
] #: Array[String]
|
|
14
16
|
|
|
15
17
|
class << self
|
|
16
18
|
#: -> String
|
|
@@ -30,17 +32,17 @@ module Rubydex
|
|
|
30
32
|
workspace_path = current_workspace_path
|
|
31
33
|
|
|
32
34
|
graph = Rubydex::Graph.configure_for_workspace(workspace_path)
|
|
33
|
-
graph.index_all([
|
|
35
|
+
graph.index_all([*BASE_PATHS, *Rubydex::Linter::RuleLoader.paths(workspace_path)])
|
|
34
36
|
graph.resolve
|
|
35
37
|
|
|
36
|
-
base_rule = graph[
|
|
37
|
-
abort("Base rule class
|
|
38
|
+
base_rule = graph["Rubydex::Rule"]
|
|
39
|
+
abort("Base rule class is not found. This is an issue in Rubydex itself") unless base_rule.is_a?(Rubydex::Class)
|
|
38
40
|
|
|
39
41
|
rule_declarations = base_rule.descendants.grep(Rubydex::Class) #: as Array[Rubydex::Class]
|
|
40
42
|
matched_rule_names = rule_declarations.filter_map do |declaration|
|
|
41
43
|
declaration_name = declaration.name
|
|
42
44
|
next unless declaration_name
|
|
43
|
-
next if declaration_name ==
|
|
45
|
+
next if declaration_name == "Rubydex::Rule" || declaration_name == "Rubydex::Linter::CustomRule"
|
|
44
46
|
next unless declaration_name.end_with?(rule_name)
|
|
45
47
|
|
|
46
48
|
declaration
|
|
@@ -35,7 +35,7 @@ module Rubydex
|
|
|
35
35
|
require "rubydex/linter"
|
|
36
36
|
|
|
37
37
|
custom_rules = load_linter_rules(workspace_path)
|
|
38
|
-
warn_unknown_rules
|
|
38
|
+
warn_unknown_rules
|
|
39
39
|
|
|
40
40
|
graph = build_graph($stderr, workspace_path:, config:)
|
|
41
41
|
runner = Rubydex::Linter::Runner.new(graph, custom_rules:, config: linter_config)
|
|
@@ -64,9 +64,15 @@ module Rubydex
|
|
|
64
64
|
@linter_config ||= config.linter #: Rubydex::LinterConfig?
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
#: (
|
|
68
|
-
def warn_unknown_rules
|
|
69
|
-
known_rule_names = (Rubydex::
|
|
67
|
+
#: () -> void
|
|
68
|
+
def warn_unknown_rules
|
|
69
|
+
known_rule_names = (Rule.subclasses + Rubydex::Linter::CustomRule.subclasses).filter_map do |rule|
|
|
70
|
+
name = rule.rule_name
|
|
71
|
+
name unless name == "CustomRule"
|
|
72
|
+
end
|
|
73
|
+
known_rule_names.uniq!
|
|
74
|
+
known_rule_names.sort!
|
|
75
|
+
|
|
70
76
|
unknown_rule_names = linter_config.rules.keys.reject { |name| known_rule_names.include?(name) }.sort
|
|
71
77
|
return if unknown_rule_names.empty?
|
|
72
78
|
|
|
Binary file
|
|
@@ -4,7 +4,7 @@ module Rubydex
|
|
|
4
4
|
module Linter
|
|
5
5
|
# Loads project and bundled-gem rules using the Rubydex linter path convention.
|
|
6
6
|
class RuleLoader
|
|
7
|
-
BUILT_IN_RULE_GLOB = File.expand_path("../../rubydex_linter/rules/**/*.rb", __dir__) #: String
|
|
7
|
+
BUILT_IN_RULE_GLOB = File.expand_path("../../{rubydex_linter,rubydex}/rules/**/*.rb", __dir__) #: String
|
|
8
8
|
RULE_GLOB = "rubydex_linter/rules/**/*.rb" #: String
|
|
9
9
|
|
|
10
10
|
class << self
|
|
@@ -4,7 +4,7 @@ module Rubydex
|
|
|
4
4
|
module MCPServer
|
|
5
5
|
class CodebaseStatsTool < BaseTool
|
|
6
6
|
tool_name "codebase_stats"
|
|
7
|
-
description "
|
|
7
|
+
description "Count indexed files, declarations, definitions, and references, with declarations grouped by kind."
|
|
8
8
|
input_schema(properties: {})
|
|
9
9
|
|
|
10
10
|
#: -> Tool::Response
|
|
@@ -4,12 +4,12 @@ module Rubydex
|
|
|
4
4
|
module MCPServer
|
|
5
5
|
class FindConstantReferencesTool < BaseTool
|
|
6
6
|
tool_name "find_constant_references"
|
|
7
|
-
description "
|
|
7
|
+
description "Locate resolved references to a Ruby class, module, or constant."
|
|
8
8
|
input_schema(
|
|
9
9
|
properties: {
|
|
10
|
-
name: { type: "string", description: "Fully qualified
|
|
11
|
-
limit: { type: "integer", description: "
|
|
12
|
-
offset: { type: "integer", description: "Number of
|
|
10
|
+
name: { type: "string", description: "Fully qualified class, module, or constant name" },
|
|
11
|
+
limit: { type: "integer", description: "Page size (default 50, capped at 200)" },
|
|
12
|
+
offset: { type: "integer", description: "Number of results to skip (default 0)" },
|
|
13
13
|
},
|
|
14
14
|
required: ["name"],
|
|
15
15
|
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubydex
|
|
4
|
+
module MCPServer
|
|
5
|
+
class FindDeadCodeCandidatesTool < BaseTool
|
|
6
|
+
tool_name "find_dead_code_candidates"
|
|
7
|
+
description "Find Ruby classes, modules, and constants with no detected references, including dependency code. Dynamic usage through DSLs or metaprogramming may not be detected."
|
|
8
|
+
input_schema(
|
|
9
|
+
properties: {
|
|
10
|
+
limit: { type: "integer", description: "Page size (default 50, capped at 100)" },
|
|
11
|
+
offset: { type: "integer", description: "Number of results to skip (default 0)" },
|
|
12
|
+
},
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
#: (?limit: Integer, ?offset: Integer) -> Tool::Response
|
|
16
|
+
def call(limit: nil, offset: nil)
|
|
17
|
+
declarations = @graph.dead_code_candidates.sort_by(&:name)
|
|
18
|
+
page, total = paginate(declarations, offset, limit, 100)
|
|
19
|
+
candidates = page.map do |declaration|
|
|
20
|
+
{
|
|
21
|
+
name: declaration.name,
|
|
22
|
+
kind: declaration_kind(declaration),
|
|
23
|
+
locations: declaration.definitions.sort_by(&:location).map do |definition|
|
|
24
|
+
display_location(definition.location)
|
|
25
|
+
end,
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
response(candidates: candidates, total: total)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -4,10 +4,10 @@ module Rubydex
|
|
|
4
4
|
module MCPServer
|
|
5
5
|
class GetDeclarationTool < BaseTool
|
|
6
6
|
tool_name "get_declaration"
|
|
7
|
-
description
|
|
7
|
+
description "Get a Ruby declaration's definition locations, documentation, ancestors, and members."
|
|
8
8
|
input_schema(
|
|
9
9
|
properties: {
|
|
10
|
-
name: { type: "string", description:
|
|
10
|
+
name: { type: "string", description: 'Exact fully qualified name: "Foo::Bar", "Foo::Bar#method_name()" (instance method), "Foo::Bar::<Bar>" (singleton class), or "Foo::Bar::<Bar>#method_name()" (class method)' },
|
|
11
11
|
},
|
|
12
12
|
required: ["name"],
|
|
13
13
|
)
|