rubydex 0.2.5 → 0.2.6
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 +17 -16
- data/THIRD_PARTY_LICENSES.html +6 -6
- data/ext/rubydex/definition.c +33 -2
- data/ext/rubydex/document.c +36 -0
- data/ext/rubydex/graph.c +32 -18
- data/ext/rubydex/handle.h +21 -5
- data/lib/rubydex/bin/rubydex_mcp.exe +0 -0
- data/lib/rubydex/errors.rb +8 -0
- data/lib/rubydex/location.rb +24 -0
- data/lib/rubydex/version.rb +1 -1
- data/lib/rubydex.rb +1 -0
- data/rbi/rubydex.rbi +29 -12
- data/rust/Cargo.lock +3 -3
- data/rust/rubydex/Cargo.toml +7 -1
- data/rust/rubydex/src/dot.rs +609 -0
- data/rust/rubydex/src/indexing/rbs_indexer.rs +19 -1
- data/rust/rubydex/src/indexing/ruby_indexer.rs +4 -0
- data/rust/rubydex/src/lib.rs +1 -1
- data/rust/rubydex/src/main.rs +8 -5
- data/rust/rubydex/src/model/built_in.rs +5 -2
- data/rust/rubydex/src/model/comment.rs +2 -0
- data/rust/rubydex/src/model/declaration.rs +1 -0
- data/rust/rubydex/src/model/definitions.rs +13 -1
- data/rust/rubydex/src/model/document.rs +2 -0
- data/rust/rubydex/src/model/encoding.rs +2 -0
- data/rust/rubydex/src/model/graph.rs +51 -13
- data/rust/rubydex/src/model/identity_maps.rs +3 -0
- data/rust/rubydex/src/model/keywords.rs +3 -0
- data/rust/rubydex/src/model/name.rs +2 -0
- data/rust/rubydex/src/model/string_ref.rs +2 -0
- data/rust/rubydex/src/model/visibility.rs +3 -0
- data/rust/rubydex/src/operation/applier.rs +1 -0
- data/rust/rubydex/src/operation/mod.rs +1 -0
- data/rust/rubydex/src/operation/ruby_builder.rs +4 -0
- data/rust/rubydex/src/query.rs +114 -33
- data/rust/rubydex/src/resolution.rs +16 -8
- data/rust/rubydex/src/resolution_tests.rs +132 -0
- data/rust/rubydex/tests/cli.rs +17 -61
- data/rust/rubydex-mcp/Cargo.toml +9 -3
- data/rust/rubydex-sys/Cargo.toml +9 -2
- data/rust/rubydex-sys/src/definition_api.rs +72 -2
- data/rust/rubydex-sys/src/document_api.rs +28 -0
- data/rust/rubydex-sys/src/graph_api.rs +1 -3
- metadata +4 -4
- data/rust/rubydex/src/visualization/dot.rs +0 -192
- data/rust/rubydex/src/visualization.rs +0 -6
data/rust/rubydex/src/query.rs
CHANGED
|
@@ -454,20 +454,7 @@ fn expression_completion<'a>(
|
|
|
454
454
|
let NameRef::Resolved(name_ref) = name_ref else {
|
|
455
455
|
return Err(format!("Expected name {nesting_name_id} to be resolved").into());
|
|
456
456
|
};
|
|
457
|
-
|
|
458
|
-
// When explicit, follow constant aliases so callers can pass whatever the expression that set self
|
|
459
|
-
// resolves to without having to unwrap aliases themselves. Missing or non-namespace decls are graph
|
|
460
|
-
// inconsistencies and surfaced as errors.
|
|
461
|
-
let resolved_self_decl_id = match self_decl_id {
|
|
462
|
-
Some(id) => resolve_self_namespace(graph, id)?,
|
|
463
|
-
None => *name_ref.declaration_id(),
|
|
464
|
-
};
|
|
465
|
-
let self_decl = graph
|
|
466
|
-
.declarations()
|
|
467
|
-
.get(&resolved_self_decl_id)
|
|
468
|
-
.unwrap()
|
|
469
|
-
.as_namespace()
|
|
470
|
-
.ok_or("Expected associated declaration to be a namespace")?;
|
|
457
|
+
|
|
471
458
|
let innermost_lexical_decl = graph
|
|
472
459
|
.declarations()
|
|
473
460
|
.get(name_ref.declaration_id())
|
|
@@ -496,7 +483,16 @@ fn expression_completion<'a>(
|
|
|
496
483
|
|
|
497
484
|
// Collect methods and instance variables, which are based on the inheritance chain of the `self` type (which may
|
|
498
485
|
// not match the immediate lexical scope)
|
|
499
|
-
|
|
486
|
+
if let Some(self_decl_id) = self_decl_id.map(|id| resolve_self_namespace(graph, id)).transpose()? {
|
|
487
|
+
let self_decl = graph
|
|
488
|
+
.declarations()
|
|
489
|
+
.get(&self_decl_id)
|
|
490
|
+
.unwrap()
|
|
491
|
+
.as_namespace()
|
|
492
|
+
.ok_or("Expected associated declaration to be a namespace")?;
|
|
493
|
+
|
|
494
|
+
collect_methods_and_ivars_from_self(graph, self_decl, &mut context, &mut candidates);
|
|
495
|
+
}
|
|
500
496
|
|
|
501
497
|
// Keywords are always available in expression contexts
|
|
502
498
|
candidates.extend(keywords::KEYWORDS.iter().map(CompletionCandidate::Keyword));
|
|
@@ -1120,7 +1116,7 @@ mod tests {
|
|
|
1120
1116
|
assert_declaration_completion_eq!(
|
|
1121
1117
|
context,
|
|
1122
1118
|
CompletionReceiver::Expression {
|
|
1123
|
-
self_decl_id:
|
|
1119
|
+
self_decl_id: Some(DeclarationId::from("Child")),
|
|
1124
1120
|
nesting_name_id: name_id,
|
|
1125
1121
|
},
|
|
1126
1122
|
[
|
|
@@ -1169,7 +1165,7 @@ mod tests {
|
|
|
1169
1165
|
assert_declaration_completion_eq!(
|
|
1170
1166
|
context,
|
|
1171
1167
|
CompletionReceiver::Expression {
|
|
1172
|
-
self_decl_id:
|
|
1168
|
+
self_decl_id: Some(DeclarationId::from("Child")),
|
|
1173
1169
|
nesting_name_id: name_id,
|
|
1174
1170
|
},
|
|
1175
1171
|
[
|
|
@@ -1218,7 +1214,7 @@ mod tests {
|
|
|
1218
1214
|
assert_declaration_completion_eq!(
|
|
1219
1215
|
context,
|
|
1220
1216
|
CompletionReceiver::Expression {
|
|
1221
|
-
self_decl_id:
|
|
1217
|
+
self_decl_id: Some(DeclarationId::from("Foo")),
|
|
1222
1218
|
nesting_name_id: name_id,
|
|
1223
1219
|
},
|
|
1224
1220
|
[
|
|
@@ -1268,7 +1264,7 @@ mod tests {
|
|
|
1268
1264
|
assert_declaration_completion_eq!(
|
|
1269
1265
|
context,
|
|
1270
1266
|
CompletionReceiver::Expression {
|
|
1271
|
-
self_decl_id:
|
|
1267
|
+
self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
|
|
1272
1268
|
nesting_name_id: name_id,
|
|
1273
1269
|
},
|
|
1274
1270
|
[
|
|
@@ -1288,7 +1284,7 @@ mod tests {
|
|
|
1288
1284
|
assert_declaration_completion_eq!(
|
|
1289
1285
|
context,
|
|
1290
1286
|
CompletionReceiver::Expression {
|
|
1291
|
-
self_decl_id:
|
|
1287
|
+
self_decl_id: Some(DeclarationId::from("Bar")),
|
|
1292
1288
|
nesting_name_id: name_id,
|
|
1293
1289
|
},
|
|
1294
1290
|
[
|
|
@@ -1305,6 +1301,48 @@ mod tests {
|
|
|
1305
1301
|
);
|
|
1306
1302
|
}
|
|
1307
1303
|
|
|
1304
|
+
#[test]
|
|
1305
|
+
fn completion_candidates_for_instance_variables_inside_singleton_class_body() {
|
|
1306
|
+
let mut context = GraphTest::new();
|
|
1307
|
+
context.index_uri(
|
|
1308
|
+
"file:///foo.rb",
|
|
1309
|
+
"
|
|
1310
|
+
class Foo
|
|
1311
|
+
@class_level_ivar = 1
|
|
1312
|
+
|
|
1313
|
+
def initialize
|
|
1314
|
+
@instance_level_ivar = 1
|
|
1315
|
+
end
|
|
1316
|
+
|
|
1317
|
+
class << self
|
|
1318
|
+
@singleton_level_ivar = 1
|
|
1319
|
+
end
|
|
1320
|
+
end
|
|
1321
|
+
",
|
|
1322
|
+
);
|
|
1323
|
+
context.resolve();
|
|
1324
|
+
|
|
1325
|
+
let foo_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
1326
|
+
let name_id = Name::new(StringId::from("<Foo>"), ParentScope::Attached(foo_id), Some(foo_id)).id();
|
|
1327
|
+
|
|
1328
|
+
assert_declaration_completion_eq!(
|
|
1329
|
+
context,
|
|
1330
|
+
CompletionReceiver::Expression {
|
|
1331
|
+
self_decl_id: Some(DeclarationId::from("Foo::<Foo>::<<Foo>>")),
|
|
1332
|
+
nesting_name_id: name_id,
|
|
1333
|
+
},
|
|
1334
|
+
[
|
|
1335
|
+
"Module",
|
|
1336
|
+
"Class",
|
|
1337
|
+
"Object",
|
|
1338
|
+
"BasicObject",
|
|
1339
|
+
"Kernel",
|
|
1340
|
+
"Foo",
|
|
1341
|
+
"Foo::<Foo>::<<Foo>>#@singleton_level_ivar"
|
|
1342
|
+
]
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1308
1346
|
#[test]
|
|
1309
1347
|
fn completion_candidates_includes_constants_accessible_within_lexical_scope() {
|
|
1310
1348
|
let mut context = GraphTest::new();
|
|
@@ -1337,10 +1375,11 @@ mod tests {
|
|
|
1337
1375
|
Some(Name::new(StringId::from("Foo"), ParentScope::None, None).id()),
|
|
1338
1376
|
)
|
|
1339
1377
|
.id();
|
|
1378
|
+
|
|
1340
1379
|
assert_declaration_completion_eq!(
|
|
1341
1380
|
context,
|
|
1342
1381
|
CompletionReceiver::Expression {
|
|
1343
|
-
self_decl_id:
|
|
1382
|
+
self_decl_id: Some(DeclarationId::from("Bar")),
|
|
1344
1383
|
nesting_name_id: name_id,
|
|
1345
1384
|
},
|
|
1346
1385
|
[
|
|
@@ -1361,7 +1400,7 @@ mod tests {
|
|
|
1361
1400
|
assert_declaration_completion_eq!(
|
|
1362
1401
|
context,
|
|
1363
1402
|
CompletionReceiver::Expression {
|
|
1364
|
-
self_decl_id:
|
|
1403
|
+
self_decl_id: Some(DeclarationId::from("Bar")),
|
|
1365
1404
|
nesting_name_id: name_id,
|
|
1366
1405
|
},
|
|
1367
1406
|
[
|
|
@@ -1405,7 +1444,7 @@ mod tests {
|
|
|
1405
1444
|
assert_declaration_completion_eq!(
|
|
1406
1445
|
context,
|
|
1407
1446
|
CompletionReceiver::Expression {
|
|
1408
|
-
self_decl_id:
|
|
1447
|
+
self_decl_id: Some(DeclarationId::from("Foo::Bar")),
|
|
1409
1448
|
nesting_name_id: name_id,
|
|
1410
1449
|
},
|
|
1411
1450
|
[
|
|
@@ -1452,7 +1491,7 @@ mod tests {
|
|
|
1452
1491
|
assert_declaration_completion_eq!(
|
|
1453
1492
|
context,
|
|
1454
1493
|
CompletionReceiver::Expression {
|
|
1455
|
-
self_decl_id:
|
|
1494
|
+
self_decl_id: Some(DeclarationId::from("Foo::Bar")),
|
|
1456
1495
|
nesting_name_id: name_id,
|
|
1457
1496
|
},
|
|
1458
1497
|
["Foo::Bar", "$var2", "$var", "Foo::Bar#bar_m()"]
|
|
@@ -1982,7 +2021,7 @@ mod tests {
|
|
|
1982
2021
|
assert_declaration_completion_eq!(
|
|
1983
2022
|
context,
|
|
1984
2023
|
CompletionReceiver::MethodArgument {
|
|
1985
|
-
self_decl_id:
|
|
2024
|
+
self_decl_id: Some(DeclarationId::from("Foo")),
|
|
1986
2025
|
nesting_name_id: name_id,
|
|
1987
2026
|
method_decl_id: DeclarationId::from("Foo#greet()"),
|
|
1988
2027
|
},
|
|
@@ -2000,6 +2039,48 @@ mod tests {
|
|
|
2000
2039
|
);
|
|
2001
2040
|
}
|
|
2002
2041
|
|
|
2042
|
+
#[test]
|
|
2043
|
+
fn method_argument_in_body_completion_uses_singleton_self() {
|
|
2044
|
+
let mut context = GraphTest::new();
|
|
2045
|
+
context.index_uri(
|
|
2046
|
+
"file:///foo.rb",
|
|
2047
|
+
"
|
|
2048
|
+
class Foo
|
|
2049
|
+
@class_level_ivar = 1
|
|
2050
|
+
|
|
2051
|
+
def instance_method; end
|
|
2052
|
+
|
|
2053
|
+
def self.configure(name:, label: 'default'); end
|
|
2054
|
+
|
|
2055
|
+
# `configure(...)` is invoked at class body level — cursor inside the args.
|
|
2056
|
+
end
|
|
2057
|
+
",
|
|
2058
|
+
);
|
|
2059
|
+
context.resolve();
|
|
2060
|
+
|
|
2061
|
+
let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
2062
|
+
assert_declaration_completion_eq!(
|
|
2063
|
+
context,
|
|
2064
|
+
CompletionReceiver::MethodArgument {
|
|
2065
|
+
self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
|
|
2066
|
+
nesting_name_id: name_id,
|
|
2067
|
+
method_decl_id: DeclarationId::from("Foo::<Foo>#configure()"),
|
|
2068
|
+
},
|
|
2069
|
+
[
|
|
2070
|
+
"Module",
|
|
2071
|
+
"Class",
|
|
2072
|
+
"Object",
|
|
2073
|
+
"BasicObject",
|
|
2074
|
+
"Kernel",
|
|
2075
|
+
"Foo",
|
|
2076
|
+
"Foo::<Foo>#configure()",
|
|
2077
|
+
"Foo::<Foo>#@class_level_ivar",
|
|
2078
|
+
"name:",
|
|
2079
|
+
"label:"
|
|
2080
|
+
]
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2003
2084
|
#[test]
|
|
2004
2085
|
fn method_argument_completion_no_keyword_params() {
|
|
2005
2086
|
let mut context = GraphTest::new();
|
|
@@ -2018,7 +2099,7 @@ mod tests {
|
|
|
2018
2099
|
assert_declaration_completion_eq!(
|
|
2019
2100
|
context,
|
|
2020
2101
|
CompletionReceiver::MethodArgument {
|
|
2021
|
-
self_decl_id:
|
|
2102
|
+
self_decl_id: Some(DeclarationId::from("Foo")),
|
|
2022
2103
|
nesting_name_id: name_id,
|
|
2023
2104
|
method_decl_id: DeclarationId::from("Foo#bar()"),
|
|
2024
2105
|
},
|
|
@@ -2044,7 +2125,7 @@ mod tests {
|
|
|
2044
2125
|
assert_declaration_completion_eq!(
|
|
2045
2126
|
context,
|
|
2046
2127
|
CompletionReceiver::MethodArgument {
|
|
2047
|
-
self_decl_id:
|
|
2128
|
+
self_decl_id: Some(DeclarationId::from("Foo")),
|
|
2048
2129
|
nesting_name_id: name_id,
|
|
2049
2130
|
method_decl_id: DeclarationId::from("Foo#search()"),
|
|
2050
2131
|
},
|
|
@@ -2088,7 +2169,7 @@ mod tests {
|
|
|
2088
2169
|
assert_declaration_completion_eq!(
|
|
2089
2170
|
context,
|
|
2090
2171
|
CompletionReceiver::MethodArgument {
|
|
2091
|
-
self_decl_id:
|
|
2172
|
+
self_decl_id: Some(DeclarationId::from("Foo")),
|
|
2092
2173
|
nesting_name_id: name_id,
|
|
2093
2174
|
method_decl_id: DeclarationId::from("Foo#bar()"),
|
|
2094
2175
|
},
|
|
@@ -2181,7 +2262,7 @@ mod tests {
|
|
|
2181
2262
|
assert_completion_eq!(
|
|
2182
2263
|
context,
|
|
2183
2264
|
CompletionReceiver::MethodArgument {
|
|
2184
|
-
self_decl_id:
|
|
2265
|
+
self_decl_id: Some(DeclarationId::from("Foo")),
|
|
2185
2266
|
nesting_name_id: name_id,
|
|
2186
2267
|
method_decl_id: DeclarationId::from("Foo#bar()"),
|
|
2187
2268
|
},
|
|
@@ -2612,7 +2693,7 @@ mod tests {
|
|
|
2612
2693
|
assert_declaration_completion_eq!(
|
|
2613
2694
|
context,
|
|
2614
2695
|
CompletionReceiver::Expression {
|
|
2615
|
-
self_decl_id:
|
|
2696
|
+
self_decl_id: Some(DeclarationId::from("Object")),
|
|
2616
2697
|
nesting_name_id: name_id,
|
|
2617
2698
|
},
|
|
2618
2699
|
[
|
|
@@ -2950,7 +3031,7 @@ mod tests {
|
|
|
2950
3031
|
assert_declaration_completion_eq!(
|
|
2951
3032
|
context,
|
|
2952
3033
|
CompletionReceiver::Expression {
|
|
2953
|
-
self_decl_id:
|
|
3034
|
+
self_decl_id: Some(DeclarationId::from("Foo")),
|
|
2954
3035
|
nesting_name_id: foo_name_id,
|
|
2955
3036
|
},
|
|
2956
3037
|
[
|
|
@@ -2970,7 +3051,7 @@ mod tests {
|
|
|
2970
3051
|
assert_declaration_completion_eq!(
|
|
2971
3052
|
context,
|
|
2972
3053
|
CompletionReceiver::Expression {
|
|
2973
|
-
self_decl_id:
|
|
3054
|
+
self_decl_id: Some(DeclarationId::from("Bar")),
|
|
2974
3055
|
nesting_name_id: bar_name_id,
|
|
2975
3056
|
},
|
|
2976
3057
|
[
|
|
@@ -3286,7 +3367,7 @@ mod tests {
|
|
|
3286
3367
|
assert_declaration_completion_eq!(
|
|
3287
3368
|
context,
|
|
3288
3369
|
CompletionReceiver::Expression {
|
|
3289
|
-
self_decl_id:
|
|
3370
|
+
self_decl_id: Some(DeclarationId::from("Foo")),
|
|
3290
3371
|
nesting_name_id: foo_name_id,
|
|
3291
3372
|
},
|
|
3292
3373
|
[
|
|
@@ -375,10 +375,16 @@ impl<'a> Resolver<'a> {
|
|
|
375
375
|
Definition::Method(method) => {
|
|
376
376
|
if let Some(receiver) = method.receiver() {
|
|
377
377
|
let receiver_decl_id = match receiver {
|
|
378
|
-
Receiver::SelfReceiver(def_id) =>
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
378
|
+
Receiver::SelfReceiver(def_id) => {
|
|
379
|
+
let Some(&receiver_decl_id) =
|
|
380
|
+
self.graph.definition_id_to_declaration_id(*def_id)
|
|
381
|
+
else {
|
|
382
|
+
self.graph.push_work(Unit::Definition(id));
|
|
383
|
+
continue;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
receiver_decl_id
|
|
387
|
+
}
|
|
382
388
|
Receiver::ConstantReceiver(name_id) => {
|
|
383
389
|
let Some(receiver_decl_id) = self.resolve_constant_receiver(*name_id, id)
|
|
384
390
|
else {
|
|
@@ -513,13 +519,15 @@ impl<'a> Resolver<'a> {
|
|
|
513
519
|
let new_name_str_id = *alias.new_name_str_id();
|
|
514
520
|
let owner_id = match alias.receiver() {
|
|
515
521
|
Some(Receiver::SelfReceiver(def_id)) => {
|
|
516
|
-
let decl_id = *
|
|
517
|
-
.graph
|
|
518
|
-
|
|
519
|
-
|
|
522
|
+
let Some(&decl_id) = self.graph.definition_id_to_declaration_id(*def_id) else {
|
|
523
|
+
self.graph.push_work(Unit::Definition(id));
|
|
524
|
+
continue;
|
|
525
|
+
};
|
|
526
|
+
|
|
520
527
|
let Some(owner_id) = self.get_or_create_singleton_class(decl_id, true) else {
|
|
521
528
|
continue;
|
|
522
529
|
};
|
|
530
|
+
|
|
523
531
|
owner_id
|
|
524
532
|
}
|
|
525
533
|
Some(Receiver::ConstantReceiver(name_id)) => {
|
|
@@ -4674,6 +4674,138 @@ mod promotability_tests {
|
|
|
4674
4674
|
assert_declaration_does_not_exist!(context, "Foo::<Foo>");
|
|
4675
4675
|
assert_declaration_does_not_exist!(context, "Foo::<Foo>#bar()");
|
|
4676
4676
|
}
|
|
4677
|
+
|
|
4678
|
+
#[test]
|
|
4679
|
+
fn ivar_defined_inside_of_undefined_alias_namespace() {
|
|
4680
|
+
let mut context = graph_test();
|
|
4681
|
+
context.index_uri("file:///alias.rb", {
|
|
4682
|
+
r"
|
|
4683
|
+
Aliased = Undefined
|
|
4684
|
+
|
|
4685
|
+
class Aliased::Inner
|
|
4686
|
+
def self.run
|
|
4687
|
+
@ivar = 1
|
|
4688
|
+
end
|
|
4689
|
+
end
|
|
4690
|
+
"
|
|
4691
|
+
});
|
|
4692
|
+
|
|
4693
|
+
context.resolve();
|
|
4694
|
+
assert_no_diagnostics!(&context);
|
|
4695
|
+
|
|
4696
|
+
// Since we have no idea what `Aliased` is, then we cannot create `Inner`, `run()` or `@ivar` declarations
|
|
4697
|
+
assert_declaration_does_not_exist!(context, "Aliased::Inner");
|
|
4698
|
+
assert_declaration_does_not_exist!(context, "Aliased::Inner::<Inner>#run()");
|
|
4699
|
+
assert_declaration_does_not_exist!(context, "Aliased::Inner::<Inner>#@ivar");
|
|
4700
|
+
}
|
|
4701
|
+
|
|
4702
|
+
#[test]
|
|
4703
|
+
fn ivar_inside_undefined_alias_namespace_recovers_when_target_is_defined() {
|
|
4704
|
+
let mut context = graph_test();
|
|
4705
|
+
context.index_uri("file:///alias.rb", {
|
|
4706
|
+
r"
|
|
4707
|
+
Aliased = Undefined
|
|
4708
|
+
|
|
4709
|
+
class Aliased::Inner
|
|
4710
|
+
def self.run
|
|
4711
|
+
@ivar = 1
|
|
4712
|
+
end
|
|
4713
|
+
end
|
|
4714
|
+
"
|
|
4715
|
+
});
|
|
4716
|
+
context.resolve();
|
|
4717
|
+
|
|
4718
|
+
// Nothing can be placed yet: `Aliased` aliases a constant that does not exist.
|
|
4719
|
+
assert_declaration_does_not_exist!(context, "Aliased::Inner");
|
|
4720
|
+
|
|
4721
|
+
// A later edit defines the alias target. The instance variable must not have been
|
|
4722
|
+
// dropped permanently: it should be remembered and placed once its owner exists.
|
|
4723
|
+
context.index_uri("file:///target.rb", {
|
|
4724
|
+
r"
|
|
4725
|
+
module Undefined
|
|
4726
|
+
end
|
|
4727
|
+
"
|
|
4728
|
+
});
|
|
4729
|
+
context.resolve();
|
|
4730
|
+
assert_no_diagnostics!(&context);
|
|
4731
|
+
|
|
4732
|
+
// `Aliased` now resolves to `Undefined`, so the nested declarations materialize under it,
|
|
4733
|
+
// including the previously-deferred instance variable.
|
|
4734
|
+
assert_declaration_kind_eq!(context, "Undefined::Inner", "Class");
|
|
4735
|
+
assert_declaration_kind_eq!(context, "Undefined::Inner::<Inner>", "SingletonClass");
|
|
4736
|
+
assert_declaration_kind_eq!(context, "Undefined::Inner::<Inner>#run()", "Method");
|
|
4737
|
+
assert_declaration_kind_eq!(context, "Undefined::Inner::<Inner>#@ivar", "InstanceVariable");
|
|
4738
|
+
}
|
|
4739
|
+
|
|
4740
|
+
#[test]
|
|
4741
|
+
fn self_method_alias_defined_inside_of_undefined_alias_namespace() {
|
|
4742
|
+
let mut context = graph_test();
|
|
4743
|
+
context.index_uri("file:///alias.rb", {
|
|
4744
|
+
r"
|
|
4745
|
+
Aliased = Undefined
|
|
4746
|
+
"
|
|
4747
|
+
});
|
|
4748
|
+
// RBS singleton method alias (`alias self.x self.y`) nested under the undefined-alias namespace.
|
|
4749
|
+
context.index_rbs_uri(
|
|
4750
|
+
"file:///alias.rbs",
|
|
4751
|
+
r"
|
|
4752
|
+
class Aliased::Inner
|
|
4753
|
+
def self.run: () -> void
|
|
4754
|
+
alias self.execute self.run
|
|
4755
|
+
end
|
|
4756
|
+
",
|
|
4757
|
+
);
|
|
4758
|
+
|
|
4759
|
+
context.resolve();
|
|
4760
|
+
assert_no_diagnostics!(&context);
|
|
4761
|
+
|
|
4762
|
+
// Since we have no idea what `Aliased` is, none of the nested declarations (including the
|
|
4763
|
+
// singleton method alias) can be created.
|
|
4764
|
+
assert_declaration_does_not_exist!(context, "Aliased::Inner");
|
|
4765
|
+
assert_declaration_does_not_exist!(context, "Aliased::Inner::<Inner>#run()");
|
|
4766
|
+
assert_declaration_does_not_exist!(context, "Aliased::Inner::<Inner>#execute()");
|
|
4767
|
+
}
|
|
4768
|
+
|
|
4769
|
+
#[test]
|
|
4770
|
+
fn self_method_alias_inside_undefined_alias_namespace_recovers_when_target_is_defined() {
|
|
4771
|
+
let mut context = graph_test();
|
|
4772
|
+
context.index_uri("file:///alias.rb", {
|
|
4773
|
+
r"
|
|
4774
|
+
Aliased = Undefined
|
|
4775
|
+
"
|
|
4776
|
+
});
|
|
4777
|
+
context.index_rbs_uri(
|
|
4778
|
+
"file:///alias.rbs",
|
|
4779
|
+
r"
|
|
4780
|
+
class Aliased::Inner
|
|
4781
|
+
def self.run: () -> void
|
|
4782
|
+
alias self.execute self.run
|
|
4783
|
+
end
|
|
4784
|
+
",
|
|
4785
|
+
);
|
|
4786
|
+
context.resolve();
|
|
4787
|
+
|
|
4788
|
+
// Nothing can be placed yet: `Aliased` aliases a constant that does not exist.
|
|
4789
|
+
assert_declaration_does_not_exist!(context, "Aliased::Inner");
|
|
4790
|
+
|
|
4791
|
+
// A later edit defines the alias target. The singleton method alias must not have been
|
|
4792
|
+
// dropped permanently: it should be remembered and placed once its owner exists.
|
|
4793
|
+
context.index_uri("file:///target.rb", {
|
|
4794
|
+
r"
|
|
4795
|
+
module Undefined
|
|
4796
|
+
end
|
|
4797
|
+
"
|
|
4798
|
+
});
|
|
4799
|
+
context.resolve();
|
|
4800
|
+
assert_no_diagnostics!(&context);
|
|
4801
|
+
|
|
4802
|
+
// `Aliased` now resolves to `Undefined`, so the nested declarations materialize under it,
|
|
4803
|
+
// including the previously-deferred singleton method alias.
|
|
4804
|
+
assert_declaration_kind_eq!(context, "Undefined::Inner", "Class");
|
|
4805
|
+
assert_declaration_kind_eq!(context, "Undefined::Inner::<Inner>", "SingletonClass");
|
|
4806
|
+
assert_declaration_kind_eq!(context, "Undefined::Inner::<Inner>#run()", "Method");
|
|
4807
|
+
assert_declaration_kind_eq!(context, "Undefined::Inner::<Inner>#execute()", "Method");
|
|
4808
|
+
}
|
|
4677
4809
|
}
|
|
4678
4810
|
|
|
4679
4811
|
mod rbs_tests {
|
data/rust/rubydex/tests/cli.rs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
use assert_cmd::{assert::Assert, prelude::*};
|
|
2
2
|
use predicates::prelude::*;
|
|
3
|
-
use
|
|
4
|
-
use rubydex::test_utils::{normalize_indentation, with_context};
|
|
3
|
+
use rubydex::test_utils::with_context;
|
|
5
4
|
use std::process::Command;
|
|
6
5
|
|
|
7
6
|
fn rdx_cmd(args: &[&str]) -> Command {
|
|
@@ -21,7 +20,7 @@ fn prints_help() {
|
|
|
21
20
|
.stdout(predicate::str::contains("A Static Analysis Toolkit for Ruby"))
|
|
22
21
|
.stdout(predicate::str::contains("Usage:"))
|
|
23
22
|
.stdout(predicate::str::contains("--stats"))
|
|
24
|
-
.stdout(predicate::str::contains("--
|
|
23
|
+
.stdout(predicate::str::contains("--dot"))
|
|
25
24
|
.stdout(predicate::str::contains("--stop-after"));
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -68,68 +67,25 @@ fn prints_index_metrics() {
|
|
|
68
67
|
});
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
fn normalize_visualization_output(output: &str) -> String {
|
|
72
|
-
let def_re = Regex::new(r"def_-?[a-f0-9]+").unwrap();
|
|
73
|
-
let uri_re = Regex::new(r#"file://[^"]+/([^/"]+\.rb)"#).unwrap();
|
|
74
|
-
|
|
75
|
-
let normalized = def_re.replace_all(output, "def_<ID>");
|
|
76
|
-
uri_re.replace_all(&normalized, "file://<PATH>/$1").to_string()
|
|
77
|
-
}
|
|
78
|
-
|
|
79
70
|
#[test]
|
|
80
|
-
fn
|
|
71
|
+
fn dot_flag() {
|
|
81
72
|
with_context(|context| {
|
|
82
73
|
context.write("simple.rb", "class SimpleClass\nend\n");
|
|
83
74
|
|
|
84
|
-
|
|
85
|
-
.
|
|
86
|
-
.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"Name:BasicObject" [label="BasicObject",shape=hexagon];
|
|
99
|
-
"Name:BasicObject" -> "def_<ID>" [dir=both];
|
|
100
|
-
"Name:Class" [label="Class",shape=hexagon];
|
|
101
|
-
"Name:Class" -> "def_<ID>" [dir=both];
|
|
102
|
-
"Name:Kernel" [label="Kernel",shape=hexagon];
|
|
103
|
-
"Name:Kernel" -> "def_<ID>" [dir=both];
|
|
104
|
-
"Name:Module" [label="Module",shape=hexagon];
|
|
105
|
-
"Name:Module" -> "def_<ID>" [dir=both];
|
|
106
|
-
"Name:Object" [label="Object",shape=hexagon];
|
|
107
|
-
"Name:Object" -> "def_<ID>" [dir=both];
|
|
108
|
-
"Name:SimpleClass" [label="SimpleClass",shape=hexagon];
|
|
109
|
-
"Name:SimpleClass" -> "def_<ID>" [dir=both];
|
|
110
|
-
|
|
111
|
-
"def_<ID>" [label="Class(BasicObject)",shape=ellipse];
|
|
112
|
-
"def_<ID>" [label="Class(Class)",shape=ellipse];
|
|
113
|
-
"def_<ID>" [label="Class(Module)",shape=ellipse];
|
|
114
|
-
"def_<ID>" [label="Class(Object)",shape=ellipse];
|
|
115
|
-
"def_<ID>" [label="Class(SimpleClass)",shape=ellipse];
|
|
116
|
-
"def_<ID>" [label="Module(Kernel)",shape=ellipse];
|
|
117
|
-
|
|
118
|
-
"file://<PATH>/simple.rb" [label="simple.rb",shape=box];
|
|
119
|
-
"def_<ID>" -> "file://<PATH>/simple.rb";
|
|
120
|
-
"rubydex:built-in" [label="rubydex:built-in",shape=box];
|
|
121
|
-
"def_<ID>" -> "rubydex:built-in";
|
|
122
|
-
"def_<ID>" -> "rubydex:built-in";
|
|
123
|
-
"def_<ID>" -> "rubydex:built-in";
|
|
124
|
-
"def_<ID>" -> "rubydex:built-in";
|
|
125
|
-
"def_<ID>" -> "rubydex:built-in";
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
"#
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
assert_eq!(normalized, expected);
|
|
75
|
+
rdx(&[context.absolute_path().to_str().unwrap(), "--dot"])
|
|
76
|
+
.success()
|
|
77
|
+
.stdout(predicate::str::contains("digraph rubydex"))
|
|
78
|
+
// Document node
|
|
79
|
+
.stdout(predicate::str::contains("Document"))
|
|
80
|
+
.stdout(predicate::str::contains("simple.rb"))
|
|
81
|
+
// Definition node
|
|
82
|
+
.stdout(predicate::str::contains("ClassDef"))
|
|
83
|
+
.stdout(predicate::str::contains("SimpleClass"))
|
|
84
|
+
// Declaration node
|
|
85
|
+
.stdout(predicate::str::contains("ClassDecl"))
|
|
86
|
+
// Edges
|
|
87
|
+
.stdout(predicate::str::contains("defines"))
|
|
88
|
+
.stdout(predicate::str::contains("declares"));
|
|
133
89
|
});
|
|
134
90
|
}
|
|
135
91
|
|
data/rust/rubydex-mcp/Cargo.toml
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "rubydex-mcp"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.6"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.89.0"
|
|
6
6
|
license = "MIT"
|
|
7
|
+
description = "MCP server exposing Rubydex semantic Ruby code intelligence tools."
|
|
8
|
+
homepage = "https://github.com/Shopify/rubydex"
|
|
9
|
+
repository = "https://github.com/Shopify/rubydex"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
keywords = ["ruby", "mcp", "static-analysis"]
|
|
12
|
+
categories = ["development-tools"]
|
|
7
13
|
|
|
8
14
|
[[bin]]
|
|
9
15
|
name = "rubydex_mcp"
|
|
10
16
|
path = "src/main.rs"
|
|
11
17
|
|
|
12
18
|
[dependencies]
|
|
13
|
-
rubydex = { path = "../rubydex" }
|
|
19
|
+
rubydex = { version = "0.2.6", path = "../rubydex" }
|
|
14
20
|
clap = { version = "4.5.16", features = ["derive"] }
|
|
15
21
|
rmcp = { version = "1.4", features = ["server", "macros", "transport-io", "schemars"] }
|
|
16
22
|
tokio = { version = "1", features = ["macros", "rt", "io-std"] }
|
|
@@ -20,7 +26,7 @@ schemars = "1"
|
|
|
20
26
|
url = "2"
|
|
21
27
|
|
|
22
28
|
[dev-dependencies]
|
|
23
|
-
rubydex = { path = "../rubydex", features = ["test_utils"] }
|
|
29
|
+
rubydex = { version = "0.2.6", path = "../rubydex", features = ["test_utils"] }
|
|
24
30
|
assert_cmd = "2.0"
|
|
25
31
|
serde_json = "1"
|
|
26
32
|
|
data/rust/rubydex-sys/Cargo.toml
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "rubydex-sys"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.6"
|
|
4
4
|
edition = "2024"
|
|
5
|
+
rust-version = "1.89.0"
|
|
5
6
|
license = "MIT"
|
|
7
|
+
description = "C FFI bindings for the Rubydex Ruby code indexing engine."
|
|
8
|
+
homepage = "https://github.com/Shopify/rubydex"
|
|
9
|
+
repository = "https://github.com/Shopify/rubydex"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
keywords = ["ruby", "ffi", "static-analysis"]
|
|
12
|
+
categories = ["development-tools", "external-ffi-bindings"]
|
|
6
13
|
|
|
7
14
|
[lib]
|
|
8
15
|
crate-type = ["cdylib", "staticlib"]
|
|
9
16
|
|
|
10
17
|
[dependencies]
|
|
11
|
-
rubydex = { path = "../rubydex" }
|
|
18
|
+
rubydex = { version = "0.2.6", path = "../rubydex" }
|
|
12
19
|
libc = "0.2.174"
|
|
13
20
|
url = "2.5.4"
|
|
14
21
|
line-index = "0.1.2"
|