srb_lens 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dd55d9e4bc27585199f37ca8cad58be503b058f075365670fb8e4f538ff894c1
4
+ data.tar.gz: 9e7ad5c165dd579c34610ae26d1126cea5a60f63d4cc116997afc6b88361086a
5
+ SHA512:
6
+ metadata.gz: 2395cd8bc8752a2c51dba7b9c40f52da93dc15109a1e112f62344a8cbca6494cd065fe025e97a407bb44ad46e263a5cf369f85c13fa51d81b2a501e636d1fc09
7
+ data.tar.gz: '084dda3726304aac183e8ac07444f5204a32c5d8a14fe0eb9406be5c0404ed0fe3581aca106d6695bc1fe7fd6b43b43d1f33f54d5bc3331ba03d13527c88773b'
@@ -0,0 +1,16 @@
1
+ [package]
2
+ name = "srb_lens"
3
+ version = "0.1.0"
4
+ edition = "2024"
5
+ description = "Ruby native extension for srb-lens"
6
+ license = "MIT"
7
+ repository = "https://github.com/kazzix14/srb-lens"
8
+ authors = ["kazzix14"]
9
+ publish = false
10
+
11
+ [lib]
12
+ crate-type = ["cdylib"]
13
+
14
+ [dependencies]
15
+ magnus = { version = "0.7", features = ["rb-sys"] }
16
+ srb-lens = { version = "0.1.0", path = "../../../../crates/srb-lens" }
@@ -0,0 +1,4 @@
1
+ require "mkmf"
2
+ require "rb_sys/mkmf"
3
+
4
+ create_rust_makefile("srb_lens/srb_lens")
@@ -0,0 +1,489 @@
1
+ use magnus::{
2
+ Error, Ruby, RArray, RHash, Value,
3
+ function, method,
4
+ prelude::*,
5
+ scan_args::scan_args,
6
+ };
7
+ use srb_lens::indexer::{self, SrbCommand};
8
+ use srb_lens::model::{self, Terminator};
9
+ use std::path::Path;
10
+
11
+ fn ruby() -> Ruby {
12
+ Ruby::get().unwrap()
13
+ }
14
+
15
+ fn err(msg: String) -> Error {
16
+ Error::new(magnus::exception::runtime_error(), msg)
17
+ }
18
+
19
+ // ─── Project ────────────────────────────────────────────────────────
20
+
21
+ #[magnus::wrap(class = "SrbLens::Project")]
22
+ struct RbProject {
23
+ inner: model::Project,
24
+ }
25
+
26
+ impl RbProject {
27
+ fn load_from_cache(dir: String) -> Result<Self, Error> {
28
+ let project = indexer::load_from_cache(Path::new(&dir)).map_err(|e| err(e.to_string()))?;
29
+ Ok(Self { inner: project })
30
+ }
31
+
32
+ fn load_or_index(args: &[Value]) -> Result<Self, Error> {
33
+ let parsed = scan_args::<(String,), (), (), (), RHash, ()>(args)?;
34
+ let dir = parsed.required.0;
35
+ let srb_command = if parsed.keywords.is_nil() {
36
+ SrbCommand::default()
37
+ } else {
38
+ let val: Option<String> =
39
+ parsed.keywords.aref(magnus::Symbol::new("srb_command"))?;
40
+ match val {
41
+ Some(cmd) => SrbCommand::new(&cmd),
42
+ None => SrbCommand::default(),
43
+ }
44
+ };
45
+
46
+ let project =
47
+ indexer::load_or_index(Path::new(&dir), &srb_command).map_err(|e| err(e.to_string()))?;
48
+ Ok(Self { inner: project })
49
+ }
50
+
51
+ fn index(args: &[Value]) -> Result<Self, Error> {
52
+ let parsed = scan_args::<(String,), (), (), (), RHash, ()>(args)?;
53
+ let dir = parsed.required.0;
54
+ let srb_command = if parsed.keywords.is_nil() {
55
+ SrbCommand::default()
56
+ } else {
57
+ let val: Option<String> =
58
+ parsed.keywords.aref(magnus::Symbol::new("srb_command"))?;
59
+ match val {
60
+ Some(cmd) => SrbCommand::new(&cmd),
61
+ None => SrbCommand::default(),
62
+ }
63
+ };
64
+
65
+ let project =
66
+ indexer::index(Path::new(&dir), &srb_command).map_err(|e| err(e.to_string()))?;
67
+ Ok(Self { inner: project })
68
+ }
69
+
70
+ fn find_methods(&self, query: String) -> RArray {
71
+ let ruby = ruby();
72
+ let results = self.inner.find_methods(&query);
73
+ let ary = ruby.ary_new_capa(results.len());
74
+ for m in results {
75
+ let _ = ary.push(RbMethodInfo::from_model(m));
76
+ }
77
+ ary
78
+ }
79
+
80
+ fn find_classes(&self, query: String) -> RArray {
81
+ let ruby = ruby();
82
+ let results = self.inner.find_classes(&query);
83
+ let ary = ruby.ary_new_capa(results.len());
84
+ for c in results {
85
+ let _ = ary.push(RbClassInfo::from_model(c));
86
+ }
87
+ ary
88
+ }
89
+ }
90
+
91
+ // ─── MethodInfo ─────────────────────────────────────────────────────
92
+
93
+ #[magnus::wrap(class = "SrbLens::MethodInfo")]
94
+ struct RbMethodInfo {
95
+ fqn: String,
96
+ file_path: Option<String>,
97
+ line: Option<usize>,
98
+ return_type: Option<String>,
99
+ arguments: Vec<model::Argument>,
100
+ calls: Vec<model::MethodCall>,
101
+ ivars: Vec<model::IvarAccess>,
102
+ rescues: Vec<String>,
103
+ uses_block: bool,
104
+ basic_blocks: Vec<RbBasicBlockData>,
105
+ }
106
+
107
+ struct RbBasicBlockData {
108
+ id: usize,
109
+ terminator: String,
110
+ }
111
+
112
+ impl RbMethodInfo {
113
+ fn from_model(m: &model::MethodInfo) -> Self {
114
+ let basic_blocks = m
115
+ .basic_blocks
116
+ .iter()
117
+ .map(|bb| {
118
+ let terminator = match &bb.terminator {
119
+ Terminator::Goto(target) => format!("goto bb{target}"),
120
+ Terminator::Branch {
121
+ condition,
122
+ true_bb,
123
+ false_bb,
124
+ } => format!("branch {condition} ? bb{true_bb} : bb{false_bb}"),
125
+ Terminator::BlockCall { true_bb, false_bb } => {
126
+ format!("block_call bb{true_bb} / bb{false_bb}")
127
+ }
128
+ Terminator::Return => "return".to_string(),
129
+ };
130
+ RbBasicBlockData {
131
+ id: bb.id,
132
+ terminator,
133
+ }
134
+ })
135
+ .collect();
136
+
137
+ Self {
138
+ fqn: m.fqn.to_string(),
139
+ file_path: m.file_path.clone(),
140
+ line: m.line,
141
+ return_type: m.return_type.as_ref().map(|t| t.to_string()),
142
+ arguments: m.arguments.clone(),
143
+ calls: m.calls.clone(),
144
+ ivars: m.ivars.clone(),
145
+ rescues: m.rescues.clone(),
146
+ uses_block: m.uses_block,
147
+ basic_blocks,
148
+ }
149
+ }
150
+
151
+ fn fqn(&self) -> &str {
152
+ &self.fqn
153
+ }
154
+
155
+ fn file_path(&self) -> Option<&str> {
156
+ self.file_path.as_deref()
157
+ }
158
+
159
+ fn line(&self) -> Option<usize> {
160
+ self.line
161
+ }
162
+
163
+ fn return_type(&self) -> Option<&str> {
164
+ self.return_type.as_deref()
165
+ }
166
+
167
+ fn uses_block(&self) -> bool {
168
+ self.uses_block
169
+ }
170
+
171
+ fn arguments(&self) -> RArray {
172
+ let ruby = ruby();
173
+ let ary = ruby.ary_new_capa(self.arguments.len());
174
+ for a in &self.arguments {
175
+ let _ = ary.push(RbArgument::from_model(a));
176
+ }
177
+ ary
178
+ }
179
+
180
+ fn calls(&self) -> RArray {
181
+ let ruby = ruby();
182
+ let ary = ruby.ary_new_capa(self.calls.len());
183
+ for c in &self.calls {
184
+ let _ = ary.push(RbMethodCall::from_model(c));
185
+ }
186
+ ary
187
+ }
188
+
189
+ fn ivars(&self) -> RArray {
190
+ let ruby = ruby();
191
+ let ary = ruby.ary_new_capa(self.ivars.len());
192
+ for iv in &self.ivars {
193
+ let _ = ary.push(RbIvarAccess::from_model(iv));
194
+ }
195
+ ary
196
+ }
197
+
198
+ fn rescues(&self) -> RArray {
199
+ let ruby = ruby();
200
+ let ary = ruby.ary_new_capa(self.rescues.len());
201
+ for r in &self.rescues {
202
+ let _ = ary.push(r.as_str());
203
+ }
204
+ ary
205
+ }
206
+
207
+ fn basic_blocks(&self) -> RArray {
208
+ let ruby = ruby();
209
+ let ary = ruby.ary_new_capa(self.basic_blocks.len());
210
+ for bb in &self.basic_blocks {
211
+ let _ = ary.push(RbBasicBlock {
212
+ id: bb.id,
213
+ terminator: bb.terminator.clone(),
214
+ });
215
+ }
216
+ ary
217
+ }
218
+ }
219
+
220
+ // ─── ClassInfo ──────────────────────────────────────────────────────
221
+
222
+ #[magnus::wrap(class = "SrbLens::ClassInfo")]
223
+ struct RbClassInfo {
224
+ fqn: String,
225
+ is_module: bool,
226
+ super_class: Option<String>,
227
+ mixins: Vec<String>,
228
+ file_path: Option<String>,
229
+ line: Option<usize>,
230
+ }
231
+
232
+ impl RbClassInfo {
233
+ fn from_model(c: &model::ClassInfo) -> Self {
234
+ Self {
235
+ fqn: c.fqn.clone(),
236
+ is_module: c.is_module,
237
+ super_class: c.super_class.clone(),
238
+ mixins: c.mixins.clone(),
239
+ file_path: c.file_path.clone(),
240
+ line: c.line,
241
+ }
242
+ }
243
+
244
+ fn fqn(&self) -> &str {
245
+ &self.fqn
246
+ }
247
+
248
+ fn is_module(&self) -> bool {
249
+ self.is_module
250
+ }
251
+
252
+ fn super_class(&self) -> Option<&str> {
253
+ self.super_class.as_deref()
254
+ }
255
+
256
+ fn mixins(&self) -> RArray {
257
+ let ruby = ruby();
258
+ let ary = ruby.ary_new_capa(self.mixins.len());
259
+ for m in &self.mixins {
260
+ let _ = ary.push(m.as_str());
261
+ }
262
+ ary
263
+ }
264
+
265
+ fn file_path(&self) -> Option<&str> {
266
+ self.file_path.as_deref()
267
+ }
268
+
269
+ fn line(&self) -> Option<usize> {
270
+ self.line
271
+ }
272
+ }
273
+
274
+ // ─── Argument ───────────────────────────────────────────────────────
275
+
276
+ #[magnus::wrap(class = "SrbLens::Argument")]
277
+ struct RbArgument {
278
+ name: String,
279
+ ty: String,
280
+ kind: String,
281
+ }
282
+
283
+ impl RbArgument {
284
+ fn from_model(a: &model::Argument) -> Self {
285
+ let kind = match a.kind {
286
+ model::ArgumentKind::Req => "req",
287
+ model::ArgumentKind::Opt => "opt",
288
+ model::ArgumentKind::Rest => "rest",
289
+ model::ArgumentKind::KeyReq => "keyreq",
290
+ model::ArgumentKind::Key => "key",
291
+ model::ArgumentKind::KeyRest => "keyrest",
292
+ model::ArgumentKind::Block => "block",
293
+ };
294
+ Self {
295
+ name: a.name.clone(),
296
+ ty: a.ty.to_string(),
297
+ kind: kind.to_string(),
298
+ }
299
+ }
300
+
301
+ fn name(&self) -> &str {
302
+ &self.name
303
+ }
304
+
305
+ fn ty(&self) -> &str {
306
+ &self.ty
307
+ }
308
+
309
+ fn kind(&self) -> &str {
310
+ &self.kind
311
+ }
312
+ }
313
+
314
+ // ─── MethodCall ─────────────────────────────────────────────────────
315
+
316
+ #[magnus::wrap(class = "SrbLens::MethodCall")]
317
+ struct RbMethodCall {
318
+ receiver_type: String,
319
+ method_name: String,
320
+ return_type: String,
321
+ conditions: Vec<model::BranchCondition>,
322
+ }
323
+
324
+ impl RbMethodCall {
325
+ fn from_model(c: &model::MethodCall) -> Self {
326
+ Self {
327
+ receiver_type: c.receiver_type.to_string(),
328
+ method_name: c.method_name.clone(),
329
+ return_type: c.return_type.to_string(),
330
+ conditions: c.conditions.clone(),
331
+ }
332
+ }
333
+
334
+ fn receiver_type(&self) -> &str {
335
+ &self.receiver_type
336
+ }
337
+
338
+ fn method_name(&self) -> &str {
339
+ &self.method_name
340
+ }
341
+
342
+ fn return_type(&self) -> &str {
343
+ &self.return_type
344
+ }
345
+
346
+ fn conditions(&self) -> RArray {
347
+ let ruby = ruby();
348
+ let ary = ruby.ary_new_capa(self.conditions.len());
349
+ for cond in &self.conditions {
350
+ let _ = ary.push(RbBranchCondition::from_model(cond));
351
+ }
352
+ ary
353
+ }
354
+ }
355
+
356
+ // ─── BranchCondition ────────────────────────────────────────────────
357
+
358
+ #[magnus::wrap(class = "SrbLens::BranchCondition")]
359
+ struct RbBranchCondition {
360
+ call: String,
361
+ is_true: bool,
362
+ }
363
+
364
+ impl RbBranchCondition {
365
+ fn from_model(c: &model::BranchCondition) -> Self {
366
+ Self {
367
+ call: c.call.clone(),
368
+ is_true: c.is_true,
369
+ }
370
+ }
371
+
372
+ fn call(&self) -> &str {
373
+ &self.call
374
+ }
375
+
376
+ fn is_true(&self) -> bool {
377
+ self.is_true
378
+ }
379
+ }
380
+
381
+ // ─── IvarAccess ─────────────────────────────────────────────────────
382
+
383
+ #[magnus::wrap(class = "SrbLens::IvarAccess")]
384
+ struct RbIvarAccess {
385
+ name: String,
386
+ ty: String,
387
+ }
388
+
389
+ impl RbIvarAccess {
390
+ fn from_model(iv: &model::IvarAccess) -> Self {
391
+ Self {
392
+ name: iv.name.clone(),
393
+ ty: iv.ty.to_string(),
394
+ }
395
+ }
396
+
397
+ fn name(&self) -> &str {
398
+ &self.name
399
+ }
400
+
401
+ fn ty(&self) -> &str {
402
+ &self.ty
403
+ }
404
+ }
405
+
406
+ // ─── BasicBlock ─────────────────────────────────────────────────────
407
+
408
+ #[magnus::wrap(class = "SrbLens::BasicBlock")]
409
+ struct RbBasicBlock {
410
+ id: usize,
411
+ terminator: String,
412
+ }
413
+
414
+ impl RbBasicBlock {
415
+ fn id(&self) -> usize {
416
+ self.id
417
+ }
418
+
419
+ fn terminator(&self) -> &str {
420
+ &self.terminator
421
+ }
422
+ }
423
+
424
+ // ─── Init ───────────────────────────────────────────────────────────
425
+
426
+ #[magnus::init]
427
+ fn init(ruby: &Ruby) -> Result<(), Error> {
428
+ let module = ruby.define_module("SrbLens")?;
429
+
430
+ // Project
431
+ let project = module.define_class("Project", ruby.class_object())?;
432
+ project.define_singleton_method("load_from_cache", function!(RbProject::load_from_cache, 1))?;
433
+ project.define_singleton_method("load_or_index", function!(RbProject::load_or_index, -1))?;
434
+ project.define_singleton_method("index", function!(RbProject::index, -1))?;
435
+ project.define_method("find_methods", method!(RbProject::find_methods, 1))?;
436
+ project.define_method("find_classes", method!(RbProject::find_classes, 1))?;
437
+
438
+ // MethodInfo
439
+ let method_info = module.define_class("MethodInfo", ruby.class_object())?;
440
+ method_info.define_method("fqn", method!(RbMethodInfo::fqn, 0))?;
441
+ method_info.define_method("file_path", method!(RbMethodInfo::file_path, 0))?;
442
+ method_info.define_method("line", method!(RbMethodInfo::line, 0))?;
443
+ method_info.define_method("return_type", method!(RbMethodInfo::return_type, 0))?;
444
+ method_info.define_method("uses_block", method!(RbMethodInfo::uses_block, 0))?;
445
+ method_info.define_method("arguments", method!(RbMethodInfo::arguments, 0))?;
446
+ method_info.define_method("calls", method!(RbMethodInfo::calls, 0))?;
447
+ method_info.define_method("ivars", method!(RbMethodInfo::ivars, 0))?;
448
+ method_info.define_method("rescues", method!(RbMethodInfo::rescues, 0))?;
449
+ method_info.define_method("basic_blocks", method!(RbMethodInfo::basic_blocks, 0))?;
450
+
451
+ // ClassInfo
452
+ let class_info = module.define_class("ClassInfo", ruby.class_object())?;
453
+ class_info.define_method("fqn", method!(RbClassInfo::fqn, 0))?;
454
+ class_info.define_method("is_module", method!(RbClassInfo::is_module, 0))?;
455
+ class_info.define_method("super_class", method!(RbClassInfo::super_class, 0))?;
456
+ class_info.define_method("mixins", method!(RbClassInfo::mixins, 0))?;
457
+ class_info.define_method("file_path", method!(RbClassInfo::file_path, 0))?;
458
+ class_info.define_method("line", method!(RbClassInfo::line, 0))?;
459
+
460
+ // Argument
461
+ let argument = module.define_class("Argument", ruby.class_object())?;
462
+ argument.define_method("name", method!(RbArgument::name, 0))?;
463
+ argument.define_method("type", method!(RbArgument::ty, 0))?;
464
+ argument.define_method("kind", method!(RbArgument::kind, 0))?;
465
+
466
+ // MethodCall
467
+ let method_call = module.define_class("MethodCall", ruby.class_object())?;
468
+ method_call.define_method("receiver_type", method!(RbMethodCall::receiver_type, 0))?;
469
+ method_call.define_method("method_name", method!(RbMethodCall::method_name, 0))?;
470
+ method_call.define_method("return_type", method!(RbMethodCall::return_type, 0))?;
471
+ method_call.define_method("conditions", method!(RbMethodCall::conditions, 0))?;
472
+
473
+ // BranchCondition
474
+ let branch_cond = module.define_class("BranchCondition", ruby.class_object())?;
475
+ branch_cond.define_method("call", method!(RbBranchCondition::call, 0))?;
476
+ branch_cond.define_method("true?", method!(RbBranchCondition::is_true, 0))?;
477
+
478
+ // IvarAccess
479
+ let ivar_access = module.define_class("IvarAccess", ruby.class_object())?;
480
+ ivar_access.define_method("name", method!(RbIvarAccess::name, 0))?;
481
+ ivar_access.define_method("type", method!(RbIvarAccess::ty, 0))?;
482
+
483
+ // BasicBlock
484
+ let basic_block = module.define_class("BasicBlock", ruby.class_object())?;
485
+ basic_block.define_method("id", method!(RbBasicBlock::id, 0))?;
486
+ basic_block.define_method("terminator", method!(RbBasicBlock::terminator, 0))?;
487
+
488
+ Ok(())
489
+ }
data/lib/srb_lens.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative "srb_lens/srb_lens"
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: srb_lens
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - kazzix14
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-03-11 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rb_sys
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 0.9.124
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 0.9.124
26
+ description: Extract method signatures, call graphs, and type information from Sorbet-typed
27
+ Ruby projects
28
+ email:
29
+ - kazzix14@gmail.com
30
+ executables: []
31
+ extensions:
32
+ - ext/srb_lens/extconf.rb
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ext/srb_lens/Cargo.toml
36
+ - ext/srb_lens/extconf.rb
37
+ - ext/srb_lens/src/lib.rs
38
+ - lib/srb_lens.rb
39
+ homepage: https://github.com/kazzix14/srb-lens
40
+ licenses:
41
+ - MIT
42
+ metadata:
43
+ homepage_uri: https://github.com/kazzix14/srb-lens
44
+ source_code_uri: https://github.com/kazzix14/srb-lens
45
+ changelog_uri: https://github.com/kazzix14/srb-lens/blob/main/CHANGELOG.md
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '3.1'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.6.2
61
+ specification_version: 4
62
+ summary: Ruby bindings for srb-lens (Sorbet code analysis)
63
+ test_files: []