rubydex 0.2.5 → 0.2.7
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 +45 -12
- data/exe/rdx +2 -0
- data/ext/rubydex/declaration.c +115 -106
- data/ext/rubydex/definition.c +123 -72
- data/ext/rubydex/diagnostic.c +5 -0
- data/ext/rubydex/document.c +51 -23
- data/ext/rubydex/extconf.rb +1 -1
- data/ext/rubydex/graph.c +253 -66
- data/ext/rubydex/handle.h +34 -5
- data/ext/rubydex/location.c +5 -0
- data/ext/rubydex/reference.c +51 -46
- data/ext/rubydex/rubydex.c +5 -0
- data/ext/rubydex/signature.c +5 -0
- data/ext/rubydex/utils.c +13 -0
- data/ext/rubydex/utils.h +4 -0
- data/lib/rubydex/bin/rubydex_mcp.exe +0 -0
- data/lib/rubydex/errors.rb +11 -0
- data/lib/rubydex/graph.rb +9 -28
- data/lib/rubydex/location.rb +24 -0
- data/lib/rubydex/version.rb +1 -1
- data/lib/rubydex.rb +1 -0
- data/rbi/rubydex.rbi +40 -15
- data/rust/Cargo.lock +122 -17
- data/rust/rubydex/Cargo.toml +26 -2
- data/rust/rubydex/benches/graph_memory.rs +46 -0
- data/rust/rubydex/src/config.rs +275 -0
- data/rust/rubydex/src/dot.rs +609 -0
- data/rust/rubydex/src/errors.rs +2 -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 +8 -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 +13 -51
- 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 +88 -27
- 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 +18 -20
- 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 +74 -6
- metadata +6 -4
- data/rust/rubydex/src/visualization/dot.rs +0 -192
- data/rust/rubydex/src/visualization.rs +0 -6
data/rust/Cargo.lock
CHANGED
|
@@ -180,7 +180,7 @@ dependencies = [
|
|
|
180
180
|
"serde_json",
|
|
181
181
|
"syn",
|
|
182
182
|
"tempfile",
|
|
183
|
-
"toml",
|
|
183
|
+
"toml 0.8.23",
|
|
184
184
|
]
|
|
185
185
|
|
|
186
186
|
[[package]]
|
|
@@ -536,9 +536,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
|
|
536
536
|
|
|
537
537
|
[[package]]
|
|
538
538
|
name = "hashbrown"
|
|
539
|
-
version = "0.
|
|
539
|
+
version = "0.17.1"
|
|
540
540
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
541
|
-
checksum = "
|
|
541
|
+
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
|
542
542
|
|
|
543
543
|
[[package]]
|
|
544
544
|
name = "heck"
|
|
@@ -685,9 +685,9 @@ dependencies = [
|
|
|
685
685
|
|
|
686
686
|
[[package]]
|
|
687
687
|
name = "indexmap"
|
|
688
|
-
version = "2.
|
|
688
|
+
version = "2.14.0"
|
|
689
689
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
690
|
-
checksum = "
|
|
690
|
+
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
|
691
691
|
dependencies = [
|
|
692
692
|
"equivalent",
|
|
693
693
|
"hashbrown",
|
|
@@ -823,6 +823,12 @@ version = "1.70.1"
|
|
|
823
823
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
824
824
|
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
|
825
825
|
|
|
826
|
+
[[package]]
|
|
827
|
+
name = "paste"
|
|
828
|
+
version = "1.0.15"
|
|
829
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
830
|
+
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
|
831
|
+
|
|
826
832
|
[[package]]
|
|
827
833
|
name = "pastey"
|
|
828
834
|
version = "0.2.1"
|
|
@@ -1048,7 +1054,7 @@ dependencies = [
|
|
|
1048
1054
|
|
|
1049
1055
|
[[package]]
|
|
1050
1056
|
name = "rubydex"
|
|
1051
|
-
version = "0.
|
|
1057
|
+
version = "0.2.6"
|
|
1052
1058
|
dependencies = [
|
|
1053
1059
|
"assert_cmd",
|
|
1054
1060
|
"bitflags",
|
|
@@ -1065,14 +1071,18 @@ dependencies = [
|
|
|
1065
1071
|
"ruby-prism",
|
|
1066
1072
|
"ruby-rbs",
|
|
1067
1073
|
"rubydex",
|
|
1074
|
+
"serde",
|
|
1068
1075
|
"tempfile",
|
|
1076
|
+
"tikv-jemalloc-ctl",
|
|
1077
|
+
"tikv-jemallocator",
|
|
1078
|
+
"toml 1.1.2+spec-1.1.0",
|
|
1069
1079
|
"url",
|
|
1070
1080
|
"xxhash-rust",
|
|
1071
1081
|
]
|
|
1072
1082
|
|
|
1073
1083
|
[[package]]
|
|
1074
1084
|
name = "rubydex-mcp"
|
|
1075
|
-
version = "0.
|
|
1085
|
+
version = "0.2.6"
|
|
1076
1086
|
dependencies = [
|
|
1077
1087
|
"assert_cmd",
|
|
1078
1088
|
"clap",
|
|
@@ -1087,7 +1097,7 @@ dependencies = [
|
|
|
1087
1097
|
|
|
1088
1098
|
[[package]]
|
|
1089
1099
|
name = "rubydex-sys"
|
|
1090
|
-
version = "0.
|
|
1100
|
+
version = "0.2.6"
|
|
1091
1101
|
dependencies = [
|
|
1092
1102
|
"cbindgen",
|
|
1093
1103
|
"libc",
|
|
@@ -1155,18 +1165,28 @@ dependencies = [
|
|
|
1155
1165
|
|
|
1156
1166
|
[[package]]
|
|
1157
1167
|
name = "serde"
|
|
1158
|
-
version = "1.0.
|
|
1168
|
+
version = "1.0.228"
|
|
1159
1169
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1160
|
-
checksum = "
|
|
1170
|
+
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
1171
|
+
dependencies = [
|
|
1172
|
+
"serde_core",
|
|
1173
|
+
"serde_derive",
|
|
1174
|
+
]
|
|
1175
|
+
|
|
1176
|
+
[[package]]
|
|
1177
|
+
name = "serde_core"
|
|
1178
|
+
version = "1.0.228"
|
|
1179
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1180
|
+
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
1161
1181
|
dependencies = [
|
|
1162
1182
|
"serde_derive",
|
|
1163
1183
|
]
|
|
1164
1184
|
|
|
1165
1185
|
[[package]]
|
|
1166
1186
|
name = "serde_derive"
|
|
1167
|
-
version = "1.0.
|
|
1187
|
+
version = "1.0.228"
|
|
1168
1188
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1169
|
-
checksum = "
|
|
1189
|
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
1170
1190
|
dependencies = [
|
|
1171
1191
|
"proc-macro2",
|
|
1172
1192
|
"quote",
|
|
@@ -1205,6 +1225,15 @@ dependencies = [
|
|
|
1205
1225
|
"serde",
|
|
1206
1226
|
]
|
|
1207
1227
|
|
|
1228
|
+
[[package]]
|
|
1229
|
+
name = "serde_spanned"
|
|
1230
|
+
version = "1.1.1"
|
|
1231
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1232
|
+
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
|
|
1233
|
+
dependencies = [
|
|
1234
|
+
"serde_core",
|
|
1235
|
+
]
|
|
1236
|
+
|
|
1208
1237
|
[[package]]
|
|
1209
1238
|
name = "serde_yaml"
|
|
1210
1239
|
version = "0.9.34+deprecated"
|
|
@@ -1315,6 +1344,37 @@ dependencies = [
|
|
|
1315
1344
|
"syn",
|
|
1316
1345
|
]
|
|
1317
1346
|
|
|
1347
|
+
[[package]]
|
|
1348
|
+
name = "tikv-jemalloc-ctl"
|
|
1349
|
+
version = "0.7.0"
|
|
1350
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1351
|
+
checksum = "3a184c43b8ab2f41df2733b55556e3f5f632f4aeaa205b1bb018f574b7f5f142"
|
|
1352
|
+
dependencies = [
|
|
1353
|
+
"libc",
|
|
1354
|
+
"paste",
|
|
1355
|
+
"tikv-jemalloc-sys",
|
|
1356
|
+
]
|
|
1357
|
+
|
|
1358
|
+
[[package]]
|
|
1359
|
+
name = "tikv-jemalloc-sys"
|
|
1360
|
+
version = "0.7.1+5.3.1-0-g81034ce1f1373e37dc865038e1bc8eeecf559ce8"
|
|
1361
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1362
|
+
checksum = "1a2825c78386b4ae0314074867860ba9577875de945f05992c38815cbec327f0"
|
|
1363
|
+
dependencies = [
|
|
1364
|
+
"cc",
|
|
1365
|
+
"libc",
|
|
1366
|
+
]
|
|
1367
|
+
|
|
1368
|
+
[[package]]
|
|
1369
|
+
name = "tikv-jemallocator"
|
|
1370
|
+
version = "0.7.0"
|
|
1371
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1372
|
+
checksum = "249f09e49ab1609436f34c776e84231bead18d6a955f119f939bdc1d847561bd"
|
|
1373
|
+
dependencies = [
|
|
1374
|
+
"libc",
|
|
1375
|
+
"tikv-jemalloc-sys",
|
|
1376
|
+
]
|
|
1377
|
+
|
|
1318
1378
|
[[package]]
|
|
1319
1379
|
name = "tinystr"
|
|
1320
1380
|
version = "0.8.1"
|
|
@@ -1367,11 +1427,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
1367
1427
|
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
|
1368
1428
|
dependencies = [
|
|
1369
1429
|
"serde",
|
|
1370
|
-
"serde_spanned",
|
|
1371
|
-
"toml_datetime",
|
|
1430
|
+
"serde_spanned 0.6.9",
|
|
1431
|
+
"toml_datetime 0.6.11",
|
|
1372
1432
|
"toml_edit",
|
|
1373
1433
|
]
|
|
1374
1434
|
|
|
1435
|
+
[[package]]
|
|
1436
|
+
name = "toml"
|
|
1437
|
+
version = "1.1.2+spec-1.1.0"
|
|
1438
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1439
|
+
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
|
|
1440
|
+
dependencies = [
|
|
1441
|
+
"indexmap",
|
|
1442
|
+
"serde_core",
|
|
1443
|
+
"serde_spanned 1.1.1",
|
|
1444
|
+
"toml_datetime 1.1.1+spec-1.1.0",
|
|
1445
|
+
"toml_parser",
|
|
1446
|
+
"toml_writer",
|
|
1447
|
+
"winnow 1.0.3",
|
|
1448
|
+
]
|
|
1449
|
+
|
|
1375
1450
|
[[package]]
|
|
1376
1451
|
name = "toml_datetime"
|
|
1377
1452
|
version = "0.6.11"
|
|
@@ -1381,6 +1456,15 @@ dependencies = [
|
|
|
1381
1456
|
"serde",
|
|
1382
1457
|
]
|
|
1383
1458
|
|
|
1459
|
+
[[package]]
|
|
1460
|
+
name = "toml_datetime"
|
|
1461
|
+
version = "1.1.1+spec-1.1.0"
|
|
1462
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1463
|
+
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
|
|
1464
|
+
dependencies = [
|
|
1465
|
+
"serde_core",
|
|
1466
|
+
]
|
|
1467
|
+
|
|
1384
1468
|
[[package]]
|
|
1385
1469
|
name = "toml_edit"
|
|
1386
1470
|
version = "0.22.27"
|
|
@@ -1389,10 +1473,19 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
|
|
1389
1473
|
dependencies = [
|
|
1390
1474
|
"indexmap",
|
|
1391
1475
|
"serde",
|
|
1392
|
-
"serde_spanned",
|
|
1393
|
-
"toml_datetime",
|
|
1476
|
+
"serde_spanned 0.6.9",
|
|
1477
|
+
"toml_datetime 0.6.11",
|
|
1394
1478
|
"toml_write",
|
|
1395
|
-
"winnow",
|
|
1479
|
+
"winnow 0.7.12",
|
|
1480
|
+
]
|
|
1481
|
+
|
|
1482
|
+
[[package]]
|
|
1483
|
+
name = "toml_parser"
|
|
1484
|
+
version = "1.1.2+spec-1.1.0"
|
|
1485
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1486
|
+
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
|
1487
|
+
dependencies = [
|
|
1488
|
+
"winnow 1.0.3",
|
|
1396
1489
|
]
|
|
1397
1490
|
|
|
1398
1491
|
[[package]]
|
|
@@ -1401,6 +1494,12 @@ version = "0.1.2"
|
|
|
1401
1494
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1402
1495
|
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
|
1403
1496
|
|
|
1497
|
+
[[package]]
|
|
1498
|
+
name = "toml_writer"
|
|
1499
|
+
version = "1.1.1+spec-1.1.0"
|
|
1500
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1501
|
+
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
|
|
1502
|
+
|
|
1404
1503
|
[[package]]
|
|
1405
1504
|
name = "tracing"
|
|
1406
1505
|
version = "0.1.44"
|
|
@@ -1751,6 +1850,12 @@ dependencies = [
|
|
|
1751
1850
|
"memchr",
|
|
1752
1851
|
]
|
|
1753
1852
|
|
|
1853
|
+
[[package]]
|
|
1854
|
+
name = "winnow"
|
|
1855
|
+
version = "1.0.3"
|
|
1856
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1857
|
+
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
|
|
1858
|
+
|
|
1754
1859
|
[[package]]
|
|
1755
1860
|
name = "wit-bindgen-rt"
|
|
1756
1861
|
version = "0.39.0"
|
data/rust/rubydex/Cargo.toml
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "rubydex"
|
|
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 = "High-performance Ruby code indexing and static analysis library."
|
|
8
|
+
homepage = "https://github.com/Shopify/rubydex"
|
|
9
|
+
repository = "https://github.com/Shopify/rubydex"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
keywords = ["ruby", "indexing", "static-analysis"]
|
|
12
|
+
categories = ["development-tools"]
|
|
7
13
|
|
|
8
14
|
[[bin]]
|
|
9
15
|
name = "rubydex_cli"
|
|
@@ -13,6 +19,11 @@ path = "src/main.rs"
|
|
|
13
19
|
crate-type = ["rlib"]
|
|
14
20
|
|
|
15
21
|
[features]
|
|
22
|
+
jemalloc = ["dep:tikv-jemallocator"]
|
|
23
|
+
# The Ruby native extension uses a dlopen to load the rubydex shared object. In these scenarios, jemalloc must be linked
|
|
24
|
+
# dynamically or else the process crashes. This has a small performance cost, which we don't need to pay when using the
|
|
25
|
+
# Rust crate by itself, so we gate it with a feature
|
|
26
|
+
jemalloc_dylib = ["jemalloc", "tikv-jemallocator/disable_initial_exec_tls"]
|
|
16
27
|
test_utils = ["dep:tempfile"]
|
|
17
28
|
|
|
18
29
|
[dependencies]
|
|
@@ -26,17 +37,30 @@ bitflags = "2.9"
|
|
|
26
37
|
bytecount = "0.6.9"
|
|
27
38
|
libc = "0.2"
|
|
28
39
|
line-index = "0.1.2"
|
|
40
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
29
41
|
tempfile = { version = "3.0", optional = true }
|
|
30
42
|
crossbeam-deque = "0.8"
|
|
31
43
|
crossbeam-utils = "0.8"
|
|
32
44
|
crossbeam-channel = "0.5"
|
|
45
|
+
toml = "1.1.2"
|
|
46
|
+
|
|
47
|
+
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
|
48
|
+
tikv-jemallocator = { version = "0.7.0", optional = true }
|
|
33
49
|
|
|
34
50
|
[dev-dependencies]
|
|
35
|
-
rubydex = { path = ".", features = ["test_utils"] }
|
|
51
|
+
rubydex = { path = ".", features = ["test_utils", "jemalloc"] }
|
|
36
52
|
tempfile = "3.0"
|
|
37
53
|
assert_cmd = "2.0"
|
|
38
54
|
predicates = "3.1"
|
|
39
55
|
regex = "1.10"
|
|
40
56
|
|
|
57
|
+
[target.'cfg(not(target_os = "windows"))'.dev-dependencies]
|
|
58
|
+
tikv-jemallocator = { version = "0.7.0", features = ["stats"] }
|
|
59
|
+
tikv-jemalloc-ctl = { version = "0.7.0", features = ["stats"] }
|
|
60
|
+
|
|
61
|
+
[[bench]]
|
|
62
|
+
name = "graph_memory"
|
|
63
|
+
harness = false
|
|
64
|
+
|
|
41
65
|
[lints]
|
|
42
66
|
workspace = true
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#[cfg(all(feature = "jemalloc", not(target_os = "windows")))]
|
|
2
|
+
mod imp {
|
|
3
|
+
use std::collections::HashSet;
|
|
4
|
+
|
|
5
|
+
use rubydex::{
|
|
6
|
+
indexing::{self, IndexerBackend},
|
|
7
|
+
listing,
|
|
8
|
+
model::graph::Graph,
|
|
9
|
+
resolution::Resolver,
|
|
10
|
+
};
|
|
11
|
+
use tikv_jemalloc_ctl::{epoch, stats};
|
|
12
|
+
|
|
13
|
+
/// Advance the jemalloc epoch (stats are cached between epochs) and read the
|
|
14
|
+
/// number of bytes currently allocated by the application.
|
|
15
|
+
fn allocated_bytes() -> usize {
|
|
16
|
+
epoch::advance().expect("failed to advance jemalloc epoch");
|
|
17
|
+
stats::allocated::read().expect("failed to read stats.allocated (is the `stats` feature on?)")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub fn run() {
|
|
21
|
+
let paths: Vec<String> = std::env::args().skip(1).collect();
|
|
22
|
+
let paths = if paths.is_empty() { vec![".".to_string()] } else { paths };
|
|
23
|
+
let (file_paths, _) = listing::collect_file_paths(paths, &HashSet::new());
|
|
24
|
+
|
|
25
|
+
let mut graph = Graph::new();
|
|
26
|
+
let _ = indexing::index_files(&mut graph, file_paths, IndexerBackend::RubyIndexer);
|
|
27
|
+
Resolver::new(&mut graph).resolve();
|
|
28
|
+
|
|
29
|
+
// Compare the total memory used in the allocator before and after dropping the graph
|
|
30
|
+
let before_drop = allocated_bytes();
|
|
31
|
+
drop(graph);
|
|
32
|
+
let after_drop = allocated_bytes();
|
|
33
|
+
|
|
34
|
+
let total_graph_memory = before_drop.saturating_sub(after_drop);
|
|
35
|
+
|
|
36
|
+
#[allow(clippy::cast_precision_loss)]
|
|
37
|
+
let mega_bytes = total_graph_memory as f64 / 1024.0 / 1024.0;
|
|
38
|
+
|
|
39
|
+
println!("Total graph memory: {mega_bytes:.2} MB");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn main() {
|
|
44
|
+
#[cfg(all(feature = "jemalloc", not(target_os = "windows")))]
|
|
45
|
+
imp::run();
|
|
46
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
use crate::assert_mem_size;
|
|
2
|
+
use crate::errors::Errors;
|
|
3
|
+
use serde::Deserialize;
|
|
4
|
+
use std::collections::HashSet;
|
|
5
|
+
use std::fs;
|
|
6
|
+
use std::io::ErrorKind;
|
|
7
|
+
use std::path::{Path, PathBuf};
|
|
8
|
+
|
|
9
|
+
pub const DEFAULT_EXCLUDED_DIRECTORIES: &[&str] = &[
|
|
10
|
+
".bundle",
|
|
11
|
+
".claude",
|
|
12
|
+
".git",
|
|
13
|
+
".github",
|
|
14
|
+
".ruby-lsp",
|
|
15
|
+
".vscode",
|
|
16
|
+
"log",
|
|
17
|
+
"node_modules",
|
|
18
|
+
"tmp",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/// Configuration coming from a config file
|
|
22
|
+
#[derive(Debug, Default, Deserialize)]
|
|
23
|
+
struct ConfigFile {
|
|
24
|
+
/// Paths to exclude from file discovery during indexing.
|
|
25
|
+
#[serde(default)]
|
|
26
|
+
exclude: Vec<PathBuf>,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Project configuration
|
|
30
|
+
#[derive(Debug)]
|
|
31
|
+
pub struct Config {
|
|
32
|
+
/// Path to the workspace being analyzed
|
|
33
|
+
workspace_path: Box<Path>,
|
|
34
|
+
/// Paths to exclude from file discovery during indexing.
|
|
35
|
+
excluded_paths: HashSet<PathBuf>,
|
|
36
|
+
}
|
|
37
|
+
assert_mem_size!(Config, 64);
|
|
38
|
+
|
|
39
|
+
impl Default for Config {
|
|
40
|
+
fn default() -> Self {
|
|
41
|
+
Self::new()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
impl Config {
|
|
46
|
+
/// Creates a configuration whose workspace path defaults to the current working directory.
|
|
47
|
+
#[must_use]
|
|
48
|
+
pub fn new() -> Self {
|
|
49
|
+
Self {
|
|
50
|
+
workspace_path: std::env::current_dir()
|
|
51
|
+
.unwrap_or_else(|_| PathBuf::from("."))
|
|
52
|
+
.into_boxed_path(),
|
|
53
|
+
excluded_paths: DEFAULT_EXCLUDED_DIRECTORIES.iter().map(PathBuf::from).collect(),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Returns the root directory of the workspace being analyzed.
|
|
58
|
+
#[must_use]
|
|
59
|
+
pub fn workspace_path(&self) -> &Path {
|
|
60
|
+
&self.workspace_path
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Sets the root directory of the workspace being analyzed.
|
|
64
|
+
pub fn set_workspace_path(&mut self, workspace_path: PathBuf) {
|
|
65
|
+
self.workspace_path = workspace_path.into_boxed_path();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Adds paths to exclude from file discovery during indexing. Excluded directories will be skipped entirely during
|
|
69
|
+
/// directory traversal.
|
|
70
|
+
pub fn exclude_paths(&mut self, paths: impl IntoIterator<Item = PathBuf>) {
|
|
71
|
+
self.excluded_paths.extend(paths);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Returns the set of paths excluded from file discovery
|
|
75
|
+
#[must_use]
|
|
76
|
+
pub fn excluded_paths(&self) -> HashSet<PathBuf> {
|
|
77
|
+
self.excluded_paths
|
|
78
|
+
.iter()
|
|
79
|
+
.map(|path| self.workspace_path.join(path))
|
|
80
|
+
.collect()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Merges the default `rubydex.toml` configuration file from the workspace root into this config, if present.
|
|
84
|
+
///
|
|
85
|
+
/// The default config file is optional, so a missing `rubydex.toml` is silently ignored. Any other failure (an
|
|
86
|
+
/// unreadable or malformed file) is still reported.
|
|
87
|
+
///
|
|
88
|
+
/// # Errors
|
|
89
|
+
///
|
|
90
|
+
/// Will error if the config file exists but cannot be read or has invalid syntax.
|
|
91
|
+
pub fn load_default(&mut self) -> Result<(), Errors> {
|
|
92
|
+
let config_path = self.workspace_path.join("rubydex.toml");
|
|
93
|
+
|
|
94
|
+
match self.load_file(&config_path) {
|
|
95
|
+
Err(Errors::ConfigNotFound(_)) => Ok(()),
|
|
96
|
+
other => other,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// Merges the configuration at `config_path` into this config
|
|
101
|
+
///
|
|
102
|
+
/// # Errors
|
|
103
|
+
///
|
|
104
|
+
/// Returns [`Errors::ConfigNotFound`] if the file does not exist or [`Errors::ConfigError`] if it cannot otherwise
|
|
105
|
+
/// be read or has invalid syntax.
|
|
106
|
+
pub fn load_file(&mut self, config_path: &Path) -> Result<(), Errors> {
|
|
107
|
+
let content = match fs::read_to_string(config_path) {
|
|
108
|
+
Ok(content) => content,
|
|
109
|
+
Err(error) if error.kind() == ErrorKind::NotFound => {
|
|
110
|
+
return Err(Errors::ConfigNotFound(format!(
|
|
111
|
+
"Config file `{}` does not exist",
|
|
112
|
+
config_path.display()
|
|
113
|
+
)));
|
|
114
|
+
}
|
|
115
|
+
Err(error) => {
|
|
116
|
+
return Err(Errors::ConfigError(format!(
|
|
117
|
+
"Failed to read config file `{}`: {error}",
|
|
118
|
+
config_path.display()
|
|
119
|
+
)));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
let parsed = Self::parse(&content).map_err(|error| {
|
|
124
|
+
Errors::ConfigError(format!("Invalid config file `{}`: {error}", config_path.display()))
|
|
125
|
+
})?;
|
|
126
|
+
|
|
127
|
+
self.excluded_paths.extend(parsed.exclude);
|
|
128
|
+
Ok(())
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Parses the content into a [`ConfigFile`]
|
|
132
|
+
fn parse(content: &str) -> Result<ConfigFile, toml::de::Error> {
|
|
133
|
+
toml::from_str(content)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[cfg(test)]
|
|
138
|
+
mod tests {
|
|
139
|
+
use super::*;
|
|
140
|
+
use std::path::Path;
|
|
141
|
+
|
|
142
|
+
#[test]
|
|
143
|
+
fn excluded_paths_are_resolved_against_the_workspace_path() {
|
|
144
|
+
let mut config = Config::new();
|
|
145
|
+
config.set_workspace_path(PathBuf::from("/workspace"));
|
|
146
|
+
config.exclude_paths([PathBuf::from("vendor"), PathBuf::from("/absolute/path")]);
|
|
147
|
+
|
|
148
|
+
let excluded = config.excluded_paths();
|
|
149
|
+
|
|
150
|
+
// Relative entries (including the defaults) are joined with the workspace path.
|
|
151
|
+
assert!(excluded.contains(Path::new("/workspace/vendor")));
|
|
152
|
+
assert!(excluded.contains(Path::new("/workspace/.git")));
|
|
153
|
+
// Absolute entries pass through unchanged.
|
|
154
|
+
assert!(excluded.contains(Path::new("/absolute/path")));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#[test]
|
|
158
|
+
fn new_seeds_the_default_excluded_directories() {
|
|
159
|
+
let config = Config::new();
|
|
160
|
+
|
|
161
|
+
for default in DEFAULT_EXCLUDED_DIRECTORIES {
|
|
162
|
+
assert!(
|
|
163
|
+
config.excluded_paths.contains(Path::new(default)),
|
|
164
|
+
"expected `{default}` to be excluded by default"
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[test]
|
|
170
|
+
fn load_file_merges_excluded_paths_and_leaves_the_workspace_path_untouched() {
|
|
171
|
+
let dir = tempfile::tempdir().expect("failed to create temp dir");
|
|
172
|
+
let config_path = dir.path().join("rubydex.toml");
|
|
173
|
+
fs::write(&config_path, "exclude = [\"vendor\", \"generated\"]\n").unwrap();
|
|
174
|
+
|
|
175
|
+
let mut config = Config::new();
|
|
176
|
+
config.set_workspace_path(PathBuf::from("/workspace"));
|
|
177
|
+
|
|
178
|
+
config
|
|
179
|
+
.load_file(&config_path)
|
|
180
|
+
.expect("expected the config file to load");
|
|
181
|
+
|
|
182
|
+
let excluded = config.excluded_paths();
|
|
183
|
+
// Entries from the file are merged in and resolved against the workspace path.
|
|
184
|
+
assert!(excluded.contains(Path::new("/workspace/vendor")));
|
|
185
|
+
assert!(excluded.contains(Path::new("/workspace/generated")));
|
|
186
|
+
// Defaults seeded at construction survive the merge.
|
|
187
|
+
assert!(excluded.contains(Path::new("/workspace/node_modules")));
|
|
188
|
+
// A config file cannot override the programmatically-set workspace path.
|
|
189
|
+
assert_eq!(config.workspace_path(), Path::new("/workspace"));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#[test]
|
|
193
|
+
fn load_file_accumulates_exclusions_across_multiple_loads() {
|
|
194
|
+
let dir = tempfile::tempdir().expect("failed to create temp dir");
|
|
195
|
+
fs::write(dir.path().join("a.toml"), "exclude = [\"vendor\"]\n").unwrap();
|
|
196
|
+
fs::write(dir.path().join("b.toml"), "exclude = [\"generated\"]\n").unwrap();
|
|
197
|
+
|
|
198
|
+
let mut config = Config::new();
|
|
199
|
+
config.set_workspace_path(PathBuf::from("/workspace"));
|
|
200
|
+
config
|
|
201
|
+
.load_file(&dir.path().join("a.toml"))
|
|
202
|
+
.expect("expected the first file to load");
|
|
203
|
+
config
|
|
204
|
+
.load_file(&dir.path().join("b.toml"))
|
|
205
|
+
.expect("expected the second file to load");
|
|
206
|
+
|
|
207
|
+
let excluded = config.excluded_paths();
|
|
208
|
+
assert!(excluded.contains(Path::new("/workspace/vendor")));
|
|
209
|
+
assert!(excluded.contains(Path::new("/workspace/generated")));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#[test]
|
|
213
|
+
fn load_file_errors_when_the_file_is_missing() {
|
|
214
|
+
let dir = tempfile::tempdir().expect("failed to create temp dir");
|
|
215
|
+
let mut config = Config::new();
|
|
216
|
+
|
|
217
|
+
let error = config
|
|
218
|
+
.load_file(&dir.path().join("does_not_exist.toml"))
|
|
219
|
+
.expect_err("an explicitly requested missing file must be an error");
|
|
220
|
+
|
|
221
|
+
assert!(
|
|
222
|
+
matches!(error, Errors::ConfigNotFound(_)),
|
|
223
|
+
"unexpected error: {error:?}"
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#[test]
|
|
228
|
+
fn load_default_ignores_a_missing_config_file() {
|
|
229
|
+
let dir = tempfile::tempdir().expect("failed to create temp dir");
|
|
230
|
+
let mut config = Config::new();
|
|
231
|
+
config.set_workspace_path(dir.path().to_path_buf());
|
|
232
|
+
|
|
233
|
+
config
|
|
234
|
+
.load_default()
|
|
235
|
+
.expect("a missing rubydex.toml must not be an error");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#[test]
|
|
239
|
+
fn load_default_loads_an_existing_config_file() {
|
|
240
|
+
let dir = tempfile::tempdir().expect("failed to create temp dir");
|
|
241
|
+
fs::write(dir.path().join("rubydex.toml"), "exclude = [\"vendor\"]\n").unwrap();
|
|
242
|
+
|
|
243
|
+
let mut config = Config::new();
|
|
244
|
+
config.set_workspace_path(dir.path().to_path_buf());
|
|
245
|
+
config.load_default().expect("expected rubydex.toml to load");
|
|
246
|
+
|
|
247
|
+
assert!(config.excluded_paths().contains(&dir.path().join("vendor")));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#[test]
|
|
251
|
+
fn load_default_propagates_malformed_config_errors() {
|
|
252
|
+
let dir = tempfile::tempdir().expect("failed to create temp dir");
|
|
253
|
+
fs::write(dir.path().join("rubydex.toml"), "exclude = [\n").unwrap();
|
|
254
|
+
|
|
255
|
+
let mut config = Config::new();
|
|
256
|
+
config.set_workspace_path(dir.path().to_path_buf());
|
|
257
|
+
|
|
258
|
+
let error = config
|
|
259
|
+
.load_default()
|
|
260
|
+
.expect_err("a malformed default config must still be an error");
|
|
261
|
+
|
|
262
|
+
assert!(matches!(error, Errors::ConfigError(_)), "unexpected error: {error:?}");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
#[test]
|
|
266
|
+
fn parse_defaults_the_excluded_paths_to_empty_when_the_key_is_absent() {
|
|
267
|
+
let file = Config::parse("").expect("an empty config is valid");
|
|
268
|
+
assert!(file.exclude.is_empty());
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
#[test]
|
|
272
|
+
fn parse_rejects_an_exclude_value_of_the_wrong_type() {
|
|
273
|
+
Config::parse("exclude = \"vendor\"").expect_err("exclude must be an array of strings, not a string");
|
|
274
|
+
}
|
|
275
|
+
}
|