use crate::model::Language;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct MockReference {
pub name: String,
pub line: u32,
pub kind: MockKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MockKind {
Infrastructure,
Usage,
}
#[derive(Debug, Clone, Default)]
pub struct MockAnalysis {
pub total_usage: u32,
pub references: Vec<MockReference>,
}
pub fn detect_mocks(
source: &str,
language: &Language,
extra_patterns: &[String],
_path: &Path,
) -> MockAnalysis {
let patterns = match language {
Language::Rust => rust_patterns(),
Language::Python => python_patterns(),
Language::JavaScript | Language::TypeScript => typescript_patterns(),
Language::Java => java_patterns(),
Language::Go => go_patterns(),
Language::Ruby => ruby_patterns(),
Language::C | Language::Cpp => c_cpp_patterns(),
_ => return MockAnalysis::default(),
};
let mut analysis = MockAnalysis::default();
let lines: Vec<&str> = source.lines().collect();
for (line_idx, line) in lines.iter().enumerate() {
let line_num = (line_idx + 1) as u32;
let trimmed = line.trim();
for pattern in &patterns.infrastructure {
if trimmed.contains(pattern) {
analysis.references.push(MockReference {
name: pattern.to_string(),
line: line_num,
kind: MockKind::Infrastructure,
});
}
}
for pattern in &patterns.usage {
if trimmed.contains(pattern) {
analysis.references.push(MockReference {
name: pattern.to_string(),
line: line_num,
kind: MockKind::Usage,
});
analysis.total_usage += 1;
}
}
for pattern in extra_patterns {
if trimmed.contains(pattern) {
analysis.references.push(MockReference {
name: pattern.to_string(),
line: line_num,
kind: MockKind::Usage,
});
analysis.total_usage += 1;
}
}
}
analysis
}
struct LangMockPatterns {
infrastructure: Vec<String>,
usage: Vec<String>,
}
fn rust_patterns() -> LangMockPatterns {
LangMockPatterns {
infrastructure: vec![
"use mockall".into(),
"use mockito".into(),
"use mock".into(),
"#[mock".into(),
"#[automock".into(),
"mockall::".into(),
"mockito::".into(),
],
usage: vec![
"Mock::new()".into(),
"Mock::default()".into(),
"mock!".into(),
"expect_".into(),
".expect(".into(),
"mockito::mock".into(),
"mockito::Matcher".into(),
".create_mock(".into(),
],
}
}
fn python_patterns() -> LangMockPatterns {
LangMockPatterns {
infrastructure: vec![
"import mock".into(),
"from mock".into(),
"import unittest.mock".into(),
"from unittest.mock".into(),
"import pytest_mock".into(),
],
usage: vec![
"MagicMock".into(),
"Mock()".into(),
"patch(".into(),
"patch.object(".into(),
"mock.patch(".into(),
"mock.MagicMock".into(),
"mock.Mock".into(),
"mocker.patch".into(),
"mocker.MagicMock".into(),
".return_value".into(),
".side_effect".into(),
],
}
}
fn typescript_patterns() -> LangMockPatterns {
LangMockPatterns {
infrastructure: vec![
"jest.mock".into(),
"vi.mock".into(),
"import sinon".into(),
"from sinon".into(),
],
usage: vec![
"jest.fn()".into(),
"jest.spyOn(".into(),
"vi.fn()".into(),
"vi.spyOn(".into(),
"sinon.stub(".into(),
"sinon.mock(".into(),
"sinon.spy(".into(),
"sinon.fake(".into(),
".mockImplementation(".into(),
".mockReturnValue(".into(),
".mockResolvedValue(".into(),
".mockRejectedValue(".into(),
],
}
}
fn java_patterns() -> LangMockPatterns {
LangMockPatterns {
infrastructure: vec![
"import org.mockito".into(),
"import org.easymock".into(),
"import org.powermock".into(),
"@Mock".into(),
"@MockBean".into(),
"@InjectMocks".into(),
],
usage: vec![
"Mockito.mock(".into(),
"Mockito.when(".into(),
"Mockito.verify(".into(),
"Mockito.any(".into(),
"Mockito.eq(".into(),
"EasyMock.mock(".into(),
"EasyMock.expect(".into(),
"EasyMock.verify(".into(),
"PowerMock.mock(".into(),
".when(".into(),
".verify(".into(),
],
}
}
fn go_patterns() -> LangMockPatterns {
LangMockPatterns {
infrastructure: vec![
"import \"github.com/stretchr/testify/mock\"".into(),
"import \"github.com/golang/mock".into(),
"mock.Controller".into(),
],
usage: vec![
"mock.Mock".into(),
"mock.Anything".into(),
".On(".into(),
".Return(".into(),
".AssertExpectations(".into(),
"ctrl := gomock.NewController".into(),
"NewMock".into(),
],
}
}
fn ruby_patterns() -> LangMockPatterns {
LangMockPatterns {
infrastructure: vec![
"require 'rspec/mocks'".into(),
"require 'mocha'".into(),
"require 'minitest/mock'".into(),
"include RSpec::Mocks".into(),
],
usage: vec![
"double(".into(),
"mock(".into(),
"stub(".into(),
"expect(".into(),
"allow(".into(),
"receive(".into(),
"and_return(".into(),
"and_raise(".into(),
],
}
}
fn c_cpp_patterns() -> LangMockPatterns {
LangMockPatterns {
infrastructure: vec![
"#include <gmock".into(),
"#include \"gmock".into(),
"#include <fake".into(),
"#include \"fake".into(),
],
usage: vec![
"MOCK_METHOD".into(),
"EXPECT_CALL".into(),
"ON_CALL".into(),
"FAKE_VALUE_FUNC".into(),
"FAKE_VOID_FUNC".into(),
],
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_rust_mock_usage() {
let source = r#"
use mockall::automock;
use mockito;
#[automock]
trait MyTrait {
fn do_something(&self);
}
fn test() {
let mock = MockMyTrait::new();
mock.expect_do_something().returning(|| ());
mockito::mock("GET", "/api").create();
}
"#;
let analysis = detect_mocks(source, &Language::Rust, &[], Path::new("test.rs"));
assert!(analysis.total_usage > 0);
assert!(analysis
.references
.iter()
.any(|r| r.kind == MockKind::Infrastructure));
assert!(analysis
.references
.iter()
.any(|r| r.kind == MockKind::Usage));
}
#[test]
fn detects_python_mock_usage() {
let source = r#"
from unittest.mock import MagicMock, patch
def test_something():
mock = MagicMock()
mock.some_method.return_value = 42
with patch("module.function") as p:
p.return_value = "mocked"
"#;
let analysis = detect_mocks(source, &Language::Python, &[], Path::new("test.py"));
assert!(analysis.total_usage > 0);
}
#[test]
fn empty_file_no_mocks() {
let source = "fn foo() { 1 + 1 }";
let analysis = detect_mocks(source, &Language::Rust, &[], Path::new("test.rs"));
assert_eq!(analysis.total_usage, 0);
}
#[test]
fn unsupported_language_no_mocks() {
let source = "some content";
let analysis = detect_mocks(source, &Language::Julia, &[], Path::new("test.jl"));
assert_eq!(analysis.total_usage, 0);
}
#[test]
fn extra_patterns_are_detected() {
let source = "custom_mock_helper()";
let analysis = detect_mocks(
source,
&Language::Rust,
&["custom_mock_helper".to_string()],
Path::new("test.rs"),
);
assert_eq!(analysis.total_usage, 1);
}
}