mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-08 03:08:13 +00:00
test: integration tests for all Phase 6 language gaps + fix Rust param pattern field
Integration test fixtures and tests (30 new tests, all with exact match + negative):
Rust for-loop (5 tests):
- for user in &users with Vec<User> → User#save, negative Repo#save
- for repo in &repos with Vec<Repo> → Repo#save, negative User#save
Rust match arm (5 tests):
- match opt { Some(user) => user.save() } → User#save, negative Repo#save
- if let Ok(repo) = res → Repo#save, negative User#save
C# var foreach (5 tests):
- foreach (var user in users) with List<User> → User#Save, negative Repo#Save
- foreach (var repo in repos) with List<Repo> → Repo#Save
C# switch pattern (4 tests):
- is User user → User#Save, case Repo repo → Repo#Save
Kotlin unannotated for (4 tests):
- for (user in users) with List<User> → user.save, negative repo.save
Go map range (3 tests):
- for _, user := range userMap with map[string]User → User#Save, negative
TypeScript readonly (4 tests):
- for (const user of users) with readonly User[] → user.save, negative
Bug fix: type-env.ts parameter branch now falls back to childForFieldName('pattern')
for Rust parameters (Rust uses 'pattern' not 'name' for parameter names)
This commit is contained in:
parent
caa3310714
commit
f1df9a12c1
29 changed files with 552 additions and 3 deletions
|
|
@ -317,11 +317,12 @@ export const buildTypeEnv = (
|
|||
const extractTypeBinding = (node: SyntaxNode, scopeEnv: Map<string, string>, scope: string): void => {
|
||||
// This guard eliminates 90%+ of calls before any language dispatch.
|
||||
if (TYPED_PARAMETER_TYPES.has(node.type)) {
|
||||
// Capture the raw type annotation BEFORE extractParameter — parameters
|
||||
// consistently expose 'name' and 'type' fields across all languages.
|
||||
// Capture the raw type annotation BEFORE extractParameter.
|
||||
// Most languages use 'name' field; Rust uses 'pattern'; TS uses 'pattern' for some param types.
|
||||
const typeNode = node.childForFieldName('type');
|
||||
if (typeNode) {
|
||||
const nameNode = node.childForFieldName('name');
|
||||
const nameNode = node.childForFieldName('name')
|
||||
?? node.childForFieldName('pattern');
|
||||
if (nameNode) {
|
||||
const varName = extractVarName(nameNode);
|
||||
if (varName && !declarationTypeNodes.has(`${scope}\0${varName}`)) {
|
||||
|
|
|
|||
6
gitnexus/test/fixtures/lang-resolution/csharp-switch-pattern/Models/Repo.cs
vendored
Normal file
6
gitnexus/test/fixtures/lang-resolution/csharp-switch-pattern/Models/Repo.cs
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace Models;
|
||||
|
||||
public class Repo
|
||||
{
|
||||
public bool Save() { return false; }
|
||||
}
|
||||
6
gitnexus/test/fixtures/lang-resolution/csharp-switch-pattern/Models/User.cs
vendored
Normal file
6
gitnexus/test/fixtures/lang-resolution/csharp-switch-pattern/Models/User.cs
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace Models;
|
||||
|
||||
public class User
|
||||
{
|
||||
public bool Save() { return true; }
|
||||
}
|
||||
21
gitnexus/test/fixtures/lang-resolution/csharp-switch-pattern/Program.cs
vendored
Normal file
21
gitnexus/test/fixtures/lang-resolution/csharp-switch-pattern/Program.cs
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
using Models;
|
||||
|
||||
namespace App;
|
||||
|
||||
public class AppService
|
||||
{
|
||||
public void Process(object obj)
|
||||
{
|
||||
if (obj is User user)
|
||||
{
|
||||
user.Save();
|
||||
}
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
case Repo repo:
|
||||
repo.Save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/csharp-switch-pattern/SwitchPattern.csproj
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/csharp-switch-pattern/SwitchPattern.csproj
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
6
gitnexus/test/fixtures/lang-resolution/csharp-var-foreach/Models/Repo.cs
vendored
Normal file
6
gitnexus/test/fixtures/lang-resolution/csharp-var-foreach/Models/Repo.cs
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace Models;
|
||||
|
||||
public class Repo
|
||||
{
|
||||
public bool Save() { return false; }
|
||||
}
|
||||
6
gitnexus/test/fixtures/lang-resolution/csharp-var-foreach/Models/User.cs
vendored
Normal file
6
gitnexus/test/fixtures/lang-resolution/csharp-var-foreach/Models/User.cs
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace Models;
|
||||
|
||||
public class User
|
||||
{
|
||||
public bool Save() { return true; }
|
||||
}
|
||||
29
gitnexus/test/fixtures/lang-resolution/csharp-var-foreach/Program.cs
vendored
Normal file
29
gitnexus/test/fixtures/lang-resolution/csharp-var-foreach/Program.cs
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace App;
|
||||
|
||||
public class AppService
|
||||
{
|
||||
public void ProcessUsers(List<User> users)
|
||||
{
|
||||
foreach (var user in users)
|
||||
{
|
||||
user.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessRepos(List<Repo> repos)
|
||||
{
|
||||
foreach (var repo in repos)
|
||||
{
|
||||
repo.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public void Direct(User u, Repo r)
|
||||
{
|
||||
u.Save();
|
||||
r.Save();
|
||||
}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/csharp-var-foreach/VarForeach.csproj
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/csharp-var-foreach/VarForeach.csproj
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
9
gitnexus/test/fixtures/lang-resolution/go-map-range/main.go
vendored
Normal file
9
gitnexus/test/fixtures/lang-resolution/go-map-range/main.go
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package main
|
||||
|
||||
import "models"
|
||||
|
||||
func processMap(userMap map[string]models.User) {
|
||||
for _, user := range userMap {
|
||||
user.Save()
|
||||
}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/go-map-range/models/repo.go
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/go-map-range/models/repo.go
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package models
|
||||
|
||||
type Repo struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func (r Repo) Save() {}
|
||||
7
gitnexus/test/fixtures/lang-resolution/go-map-range/models/user.go
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/go-map-range/models/user.go
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package models
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (u User) Save() {}
|
||||
16
gitnexus/test/fixtures/lang-resolution/kotlin-var-foreach/app.kt
vendored
Normal file
16
gitnexus/test/fixtures/lang-resolution/kotlin-var-foreach/app.kt
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package app
|
||||
|
||||
import models.User
|
||||
import models.Repo
|
||||
|
||||
fun processUsers(users: List<User>) {
|
||||
for (user in users) {
|
||||
user.save()
|
||||
}
|
||||
}
|
||||
|
||||
fun processRepos(repos: List<Repo>) {
|
||||
for (repo in repos) {
|
||||
repo.save()
|
||||
}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/kotlin-var-foreach/models/Repo.kt
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/kotlin-var-foreach/models/Repo.kt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package models
|
||||
|
||||
class Repo {
|
||||
fun save() {}
|
||||
}
|
||||
5
gitnexus/test/fixtures/lang-resolution/kotlin-var-foreach/models/User.kt
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/kotlin-var-foreach/models/User.kt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package models
|
||||
|
||||
class User {
|
||||
fun save() {}
|
||||
}
|
||||
18
gitnexus/test/fixtures/lang-resolution/rust-for-loop/src/main.rs
vendored
Normal file
18
gitnexus/test/fixtures/lang-resolution/rust-for-loop/src/main.rs
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
mod user;
|
||||
mod repo;
|
||||
use crate::user::User;
|
||||
use crate::repo::Repo;
|
||||
|
||||
fn process_users(users: Vec<User>) {
|
||||
for user in &users {
|
||||
user.save();
|
||||
}
|
||||
}
|
||||
|
||||
fn process_repos(repos: Vec<Repo>) {
|
||||
for repo in &repos {
|
||||
repo.save();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
7
gitnexus/test/fixtures/lang-resolution/rust-for-loop/src/repo.rs
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/rust-for-loop/src/repo.rs
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub struct Repo {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl Repo {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/rust-for-loop/src/user.rs
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/rust-for-loop/src/user.rs
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub struct User {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
19
gitnexus/test/fixtures/lang-resolution/rust-match-unwrap/src/main.rs
vendored
Normal file
19
gitnexus/test/fixtures/lang-resolution/rust-match-unwrap/src/main.rs
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
mod user;
|
||||
mod repo;
|
||||
use crate::user::User;
|
||||
use crate::repo::Repo;
|
||||
|
||||
fn process(opt: Option<User>) {
|
||||
match opt {
|
||||
Some(user) => user.save(),
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn check(res: Result<Repo, String>) {
|
||||
if let Ok(repo) = res {
|
||||
repo.save();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
7
gitnexus/test/fixtures/lang-resolution/rust-match-unwrap/src/repo.rs
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/rust-match-unwrap/src/repo.rs
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub struct Repo {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl Repo {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
7
gitnexus/test/fixtures/lang-resolution/rust-match-unwrap/src/user.rs
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/rust-match-unwrap/src/user.rs
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub struct User {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn save(&self) {}
|
||||
}
|
||||
14
gitnexus/test/fixtures/lang-resolution/ts-readonly-foreach/app.ts
vendored
Normal file
14
gitnexus/test/fixtures/lang-resolution/ts-readonly-foreach/app.ts
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { User } from './models/user';
|
||||
import { Repo } from './models/repo';
|
||||
|
||||
function processUsers(users: readonly User[]) {
|
||||
for (const user of users) {
|
||||
user.save();
|
||||
}
|
||||
}
|
||||
|
||||
function processRepos(repos: readonly Repo[]) {
|
||||
for (const repo of repos) {
|
||||
repo.save();
|
||||
}
|
||||
}
|
||||
3
gitnexus/test/fixtures/lang-resolution/ts-readonly-foreach/models/repo.ts
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/ts-readonly-foreach/models/repo.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export class Repo {
|
||||
save() {}
|
||||
}
|
||||
3
gitnexus/test/fixtures/lang-resolution/ts-readonly-foreach/models/user.ts
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/ts-readonly-foreach/models/user.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export class User {
|
||||
save() {}
|
||||
}
|
||||
|
|
@ -982,3 +982,98 @@ describe('C# chained method call resolution', () => {
|
|||
expect(repoSave).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// C# var foreach Tier 1c: foreach (var user in users) with List<User> param
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('C# var foreach type resolution (Tier 1c)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'csharp-var-foreach'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('detects User and Repo classes, both with Save methods', () => {
|
||||
expect(getNodesByLabel(result, 'Class')).toContain('User');
|
||||
expect(getNodesByLabel(result, 'Class')).toContain('Repo');
|
||||
});
|
||||
|
||||
it('detects methods on both classes', () => {
|
||||
const methods = getNodesByLabel(result, 'Method');
|
||||
expect(methods.filter(m => m === 'Save').length).toBe(2);
|
||||
expect(methods).toContain('ProcessUsers');
|
||||
expect(methods).toContain('ProcessRepos');
|
||||
});
|
||||
|
||||
it('resolves direct calls with explicit parameter types (u.Save, r.Save)', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const directUserSave = calls.find(c =>
|
||||
c.target === 'Save' && c.source === 'Direct' && c.targetFilePath?.includes('User.cs'),
|
||||
);
|
||||
const directRepoSave = calls.find(c =>
|
||||
c.target === 'Save' && c.source === 'Direct' && c.targetFilePath?.includes('Repo.cs'),
|
||||
);
|
||||
expect(directUserSave).toBeDefined();
|
||||
expect(directRepoSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves user.Save() in var foreach to User#Save via Tier 1c', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const userSave = calls.find(c =>
|
||||
c.target === 'Save' && c.source === 'ProcessUsers' && c.targetFilePath?.includes('User.cs'),
|
||||
);
|
||||
expect(userSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves repo.Save() in var foreach to Repo#Save via Tier 1c', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const repoSave = calls.find(c =>
|
||||
c.target === 'Save' && c.source === 'ProcessRepos' && c.targetFilePath?.includes('Repo.cs'),
|
||||
);
|
||||
expect(repoSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('does NOT cross-resolve user.Save() to Repo#Save', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const wrong = calls.find(c =>
|
||||
c.target === 'Save' && c.source === 'ProcessUsers' && c.targetFilePath?.includes('Repo.cs'),
|
||||
);
|
||||
expect(wrong).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// C# switch pattern: switch (obj) { case User user: user.Save(); }
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('C# switch pattern type resolution', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'csharp-switch-pattern'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('detects User and Repo classes', () => {
|
||||
expect(getNodesByLabel(result, 'Class')).toContain('User');
|
||||
expect(getNodesByLabel(result, 'Class')).toContain('Repo');
|
||||
});
|
||||
|
||||
it('resolves user.Save() via is-pattern to User#Save', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const userSave = calls.find(c => c.target === 'Save' && c.targetFilePath === 'Models/User.cs');
|
||||
expect(userSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves repo.Save() via switch case pattern to Repo#Save', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const repoSave = calls.find(c => c.target === 'Save' && c.targetFilePath === 'Models/Repo.cs');
|
||||
expect(repoSave).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -845,3 +845,42 @@ describe('Go chained method call resolution', () => {
|
|||
expect(repoSave).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Go map range: for _, user := range userMap where map[string]User
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Go map range type resolution (Tier 1c)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'go-map-range'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('detects User and Repo structs with Save methods in separate files', () => {
|
||||
const structs = getNodesByLabel(result, 'Struct');
|
||||
expect(structs).toContain('User');
|
||||
expect(structs).toContain('Repo');
|
||||
const methods = getNodesByLabel(result, 'Method');
|
||||
expect(methods.filter(m => m === 'Save').length).toBe(2);
|
||||
});
|
||||
|
||||
it('resolves user.Save() in map range to User#Save via map_type value', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const userSave = calls.find(c =>
|
||||
c.target === 'Save' && c.source === 'processMap' && c.targetFilePath?.includes('user.go'),
|
||||
);
|
||||
expect(userSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('does NOT resolve user.Save() to Repo#Save (negative disambiguation)', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const wrongSave = calls.find(c =>
|
||||
c.target === 'Save' && c.source === 'processMap' && c.targetFilePath?.includes('repo.go'),
|
||||
);
|
||||
expect(wrongSave).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -869,3 +869,47 @@ describe('Kotlin chained method call resolution (Phase 5 review fix)', () => {
|
|||
expect(repoSave).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Kotlin unannotated for-loop Tier 1c: for (user in users) with List<User>
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Kotlin unannotated for-loop type resolution (Tier 1c)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'kotlin-var-foreach'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('detects User and Repo classes with save methods', () => {
|
||||
expect(getNodesByLabel(result, 'Class')).toContain('User');
|
||||
expect(getNodesByLabel(result, 'Class')).toContain('Repo');
|
||||
});
|
||||
|
||||
it('resolves user.save() in unannotated for to User#save via Tier 1c', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const userSave = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'processUsers' && c.targetFilePath?.includes('User.kt'),
|
||||
);
|
||||
expect(userSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves repo.save() in unannotated for to Repo#save via Tier 1c', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const repoSave = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'processRepos' && c.targetFilePath?.includes('Repo.kt'),
|
||||
);
|
||||
expect(repoSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('does NOT cross-resolve user.save() to Repo#save', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const wrong = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'processUsers' && c.targetFilePath?.includes('Repo.kt'),
|
||||
);
|
||||
expect(wrong).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1069,3 +1069,111 @@ describe('Rust chained method call resolution', () => {
|
|||
expect(repoSave).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Rust for-loop Tier 1c: for user in &users with Vec<User> parameter
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust for-loop type resolution (Tier 1c)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'rust-for-loop'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('detects User and Repo structs with save functions', () => {
|
||||
expect(getNodesByLabel(result, 'Struct')).toContain('User');
|
||||
expect(getNodesByLabel(result, 'Struct')).toContain('Repo');
|
||||
const saveFns = getNodesByLabel(result, 'Function').filter(f => f === 'save');
|
||||
expect(saveFns.length).toBe(2);
|
||||
});
|
||||
|
||||
it('resolves user.save() in for-loop to User#save via Tier 1c', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const userSave = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'process_users' && c.targetFilePath?.includes('user.rs'),
|
||||
);
|
||||
expect(userSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('does NOT resolve user.save() to Repo#save (negative)', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const wrongSave = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'process_users' && c.targetFilePath?.includes('repo.rs'),
|
||||
);
|
||||
expect(wrongSave).toBeUndefined();
|
||||
});
|
||||
|
||||
it('resolves repo.save() in for-loop to Repo#save via Tier 1c', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const repoSave = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'process_repos' && c.targetFilePath?.includes('repo.rs'),
|
||||
);
|
||||
expect(repoSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('does NOT resolve repo.save() to User#save (negative)', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const wrongSave = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'process_repos' && c.targetFilePath?.includes('user.rs'),
|
||||
);
|
||||
expect(wrongSave).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Rust match arm: match opt { Some(user) => user.save() }
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Rust match arm type resolution', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'rust-match-unwrap'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('detects User and Repo structs with save functions', () => {
|
||||
expect(getNodesByLabel(result, 'Struct')).toContain('User');
|
||||
expect(getNodesByLabel(result, 'Struct')).toContain('Repo');
|
||||
const saveFns = getNodesByLabel(result, 'Function').filter(f => f === 'save');
|
||||
expect(saveFns.length).toBe(2);
|
||||
});
|
||||
|
||||
it('resolves user.save() inside match Some(user) to User#save', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const userSave = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'process' && c.targetFilePath?.includes('user.rs'),
|
||||
);
|
||||
expect(userSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('does NOT resolve user.save() in match to Repo#save (negative)', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const wrongSave = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'process' && c.targetFilePath?.includes('repo.rs'),
|
||||
);
|
||||
expect(wrongSave).toBeUndefined();
|
||||
});
|
||||
|
||||
it('resolves repo.save() inside if-let Ok(repo) to Repo#save', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const repoSave = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'check' && c.targetFilePath?.includes('repo.rs'),
|
||||
);
|
||||
expect(repoSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('does NOT resolve repo.save() in if-let to User#save (negative)', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const wrongSave = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'check' && c.targetFilePath?.includes('user.rs'),
|
||||
);
|
||||
expect(wrongSave).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1427,3 +1427,47 @@ describe('TypeScript static class-name chain call resolution (Phase 5 review fix
|
|||
expect(repoSave).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TS readonly User[] for-loop: for (const user of users) with readonly User[]
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('TypeScript readonly array for-loop resolution (Tier 1c)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'ts-readonly-foreach'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('detects User and Repo classes with save methods', () => {
|
||||
expect(getNodesByLabel(result, 'Class')).toContain('User');
|
||||
expect(getNodesByLabel(result, 'Class')).toContain('Repo');
|
||||
});
|
||||
|
||||
it('resolves user.save() in readonly array for-of to User#save', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const userSave = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'processUsers' && c.targetFilePath?.includes('user'),
|
||||
);
|
||||
expect(userSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves repo.save() in readonly array for-of to Repo#save', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const repoSave = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'processRepos' && c.targetFilePath?.includes('repo'),
|
||||
);
|
||||
expect(repoSave).toBeDefined();
|
||||
});
|
||||
|
||||
it('does NOT cross-resolve user.save() to Repo#save', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const wrong = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'processUsers' && c.targetFilePath?.includes('repo'),
|
||||
);
|
||||
expect(wrong).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue