GitNexus/gitnexus/test/integration/resolvers/rust-f70.test.ts
Sparsh bb3642ad5f
fix(rust): F70 — replace struct_expression name:(_) with three specific patterns (#2051)
* fix(rust): F70 — replace struct_expression name:(_) with 3 specific patterns

* fix(rust): F70 — cover scoped+turbofish struct literals (foo::Bar::<T> {})

The three patterns enumerate struct_expression.name as type_identifier /
scoped_type_identifier / generic_type_with_turbofish, but
generic_type_with_turbofish.type can itself be a scoped_identifier
(e.g. foo::Bar::<i32> {}), which the turbofish pattern — requiring
type:(type_identifier) — did not match. That dropped the constructor
reference entirely (verified: emitRustScopeCaptures returns 0 ctors for
foo::Bar::<i32> {} and a:🅱️:Bar::<i32> {}).

Add a fourth pattern that captures the trailing identifier of the scoped
turbofish path (scoped_identifier.name is an identifier, not a
type_identifier), and correct the comment that claimed all cases were
covered.

Strengthen rust-f70.test.ts: assert exactly one constructor per case, add
negative assertions guarding against the old full-path capture, and add
the scoped+turbofish and crate:: cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Gergo Magyar <gergomagyar@icloud.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:46:07 +01:00

56 lines
2.6 KiB
TypeScript

/**
* Tests for F70 — struct literal constructor calls (issue #1934).
*/
import { describe, it, expect } from 'vitest';
import { emitRustScopeCaptures } from '../../../src/core/ingestion/languages/rust/index.js';
import type { CaptureMatch } from 'gitnexus-shared';
describe('F70 — struct literal constructor calls', () => {
it('bare struct Foo {} captures Foo as @reference.name', () => {
const src = `fn f() { let _ = Foo { x: 1 }; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const ctors = matches.filter((m) => m['@reference.call.constructor']);
expect(ctors.length).toBe(1);
expect(ctors[0]['@reference.name'].text).toBe('Foo');
});
it('scoped struct foo::bar::Baz {} captures Baz (not the full path)', () => {
const src = `fn f() { let _ = foo::bar::Baz { x: 1 }; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const ctors = matches.filter((m) => m['@reference.call.constructor']);
const names = ctors.map((m) => m['@reference.name']?.text);
expect(ctors.length).toBe(1);
expect(names).toContain('Baz');
// Guard against regressing to the old wildcard, which captured the path.
expect(names).not.toContain('foo::bar::Baz');
});
it('turbofish struct Foo::<i32> {} captures Foo (not Foo::<i32>)', () => {
const src = `fn f() { let _ = Foo::<i32> { x: 1 }; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const ctors = matches.filter((m) => m['@reference.call.constructor']);
const names = ctors.map((m) => m['@reference.name']?.text);
expect(ctors.length).toBe(1);
expect(names).toContain('Foo');
expect(names).not.toContain('Foo::<i32>');
});
it('scoped + turbofish struct foo::Bar::<i32> {} captures Bar', () => {
const src = `fn f() { let _ = foo::Bar::<i32> { x: 1 }; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const ctors = matches.filter((m) => m['@reference.call.constructor']);
const names = ctors.map((m) => m['@reference.name']?.text);
expect(ctors.length).toBe(1);
expect(names).toContain('Bar');
expect(names).not.toContain('foo::Bar::<i32>');
});
it('crate-scoped struct crate::Foo {} captures Foo', () => {
const src = `fn f() { let _ = crate::Foo { x: 1 }; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const ctors = matches.filter((m) => m['@reference.call.constructor']);
const names = ctors.map((m) => m['@reference.name']?.text);
expect(ctors.length).toBe(1);
expect(names).toContain('Foo');
});
});