Skip to content

Commit e589e40

Browse files
prosdevclaude
andcommitted
fix(core): address Rust expert review — mod blocks, nested generics
W1: Remove source_file anchoring from functions query so functions inside mod blocks are captured. Filter impl methods by checking parent chain (declaration_list > impl_item), not just declaration_list. W3: Fix greedy generic stripping — use split('<')[0] instead of regex replace. Handles nested generics like Wrapper<Option<T>>. 2 new tests: functions inside mod blocks (pub + private), nested generic type param stripping. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3c5066f commit e589e40

4 files changed

Lines changed: 55 additions & 6 deletions

File tree

packages/core/src/scanner/__fixtures__/rust-complex.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,25 @@ pub fn read_server_host(s: &Server) -> String {
6060
let _host = s.host.clone();
6161
s.host.to_uppercase()
6262
}
63+
64+
// Tests mod block support — functions inside mod blocks must be captured
65+
mod handlers {
66+
pub fn handle_request(data: &str) -> String {
67+
data.to_uppercase()
68+
}
69+
70+
fn internal_helper() -> bool {
71+
true
72+
}
73+
}
74+
75+
// Tests nested generic stripping
76+
pub struct Wrapper<T> {
77+
inner: Option<T>,
78+
}
79+
80+
impl<T: fmt::Display> Wrapper<Option<T>> {
81+
pub fn unwrap_display(&self) -> String {
82+
format!("{:?}", self.inner)
83+
}
84+
}

packages/core/src/scanner/__tests__/rust.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,27 @@ describe('RustScanner', () => {
379379
expect(calleeNames.some((n) => n.includes('transform'))).toBe(true);
380380
});
381381

382+
it('should extract functions inside mod blocks', () => {
383+
const handleReq = docs.find(
384+
(d) => d.metadata.name === 'handle_request' && d.type === 'function'
385+
);
386+
expect(handleReq).toBeDefined();
387+
expect(handleReq!.metadata.exported).toBe(true);
388+
389+
const helper = docs.find((d) => d.metadata.name === 'internal_helper');
390+
expect(helper).toBeDefined();
391+
expect(helper!.metadata.exported).toBe(false);
392+
});
393+
394+
it('should strip nested generic type params from impl', () => {
395+
// impl<T: Display> Wrapper<Option<T>> → Wrapper, not Wrapper<Option<T>> or Wrapper>
396+
const method = docs.find((d) => d.metadata.name === 'Wrapper.unwrap_display');
397+
expect(method).toBeDefined();
398+
expect(method!.metadata.name).toBe('Wrapper.unwrap_display');
399+
expect(method!.metadata.name).not.toContain('<');
400+
expect(method!.metadata.name).not.toContain('>');
401+
});
402+
382403
it('should NOT include macros in callees', () => {
383404
// process_request calls format!() — should NOT be in callees
384405
const processReq = docs.find((d) => d.metadata.name === 'Server.process_request');

packages/core/src/scanner/rust-queries.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
*/
88

99
export const RUST_QUERIES = {
10-
// Free functions (top-level, not inside impl blocks)
10+
// All function_item nodes at any depth (including inside mod blocks).
11+
// Methods inside impl blocks are filtered out in the scanner code
12+
// by checking if the parent is a declaration_list (impl body).
1113
functions: `
12-
(source_file
13-
(function_item
14-
name: (identifier) @name) @definition)
14+
(function_item
15+
name: (identifier) @name) @definition
1516
`,
1617

1718
// Struct definitions

packages/core/src/scanner/rust.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ export class RustScanner implements Scanner {
170170
const defCapture = match.captures.find((c) => c.name === 'definition');
171171
if (!nameCapture || !defCapture) continue;
172172

173+
// Skip functions inside impl blocks — those are captured by extractMethods.
174+
// Functions inside mod blocks (mod_item > declaration_list) should be kept.
175+
const parent = defCapture.node.parent;
176+
if (parent?.type === 'declaration_list' && parent.parent?.type === 'impl_item') continue;
177+
173178
const name = nameCapture.node.text;
174179
const node = defCapture.node;
175180
const startLine = node.startPosition.row + 1;
@@ -327,8 +332,8 @@ export class RustScanner implements Scanner {
327332
const defCapture = match.captures.find((c) => c.name === 'definition');
328333
if (!receiverCapture || !nameCapture || !defCapture) continue;
329334

330-
// Strip generic type params: Container<T> → Container
331-
const receiverType = receiverCapture.node.text.replace(/<.*>/, '');
335+
// Strip generic type params: Container<T> → Container, HashMap<String, Vec<u8>> → HashMap
336+
const receiverType = receiverCapture.node.text.split('<')[0];
332337
const methodName = nameCapture.node.text;
333338
const qualifiedName = `${receiverType}.${methodName}`;
334339
const node = defCapture.node;

0 commit comments

Comments
 (0)