Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
package checks.spring;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

// === Stereotype redundancy ===

@Component // Noncompliant {{Remove this "@Component" annotation, already implied by "@Service".}}
@Service
class ComponentWithService {
}

@Component // Noncompliant {{Remove this "@Component" annotation, already implied by "@Repository".}}
@Repository
class ComponentWithRepository {
}

@Component // Noncompliant {{Remove this "@Component" annotation, already implied by "@Controller".}}
@Controller
class ComponentWithController {
}

@Component // Noncompliant {{Remove this "@Component" annotation, already implied by "@Configuration".}}
@Configuration
class ComponentWithConfiguration {
}

// === RestController composition ===

@Controller // Noncompliant {{Remove this "@Controller" annotation, already implied by "@RestController".}}
@RestController
class ControllerWithRestController {
}

@ResponseBody // Noncompliant {{Remove this "@ResponseBody" annotation, already implied by "@RestController".}}
@RestController
class ResponseBodyWithRestController {
}

// === SpringBootApplication composition ===

@Configuration // Noncompliant {{Remove this "@Configuration" annotation, already implied by "@SpringBootApplication".}}
@SpringBootApplication
class ConfigurationWithSpringBootApp {
}

@EnableAutoConfiguration // Noncompliant {{Remove this "@EnableAutoConfiguration" annotation, already implied by "@SpringBootApplication".}}
@SpringBootApplication
class EnableAutoConfigWithSpringBootApp {
}

@ComponentScan // Noncompliant {{Remove this "@ComponentScan" annotation, already implied by "@SpringBootApplication".}}
@SpringBootApplication
class ComponentScanWithSpringBootApp {
}

@SpringBootConfiguration // Noncompliant {{Remove this "@SpringBootConfiguration" annotation, already implied by "@SpringBootApplication".}}
@SpringBootApplication
class SpringBootConfigWithSpringBootApp {
}

// === Multiple redundant annotations on same class ===

@Configuration // Noncompliant {{Remove this "@Configuration" annotation, already implied by "@SpringBootApplication".}}
@EnableAutoConfiguration // Noncompliant {{Remove this "@EnableAutoConfiguration" annotation, already implied by "@SpringBootApplication".}}
@ComponentScan // Noncompliant {{Remove this "@ComponentScan" annotation, already implied by "@SpringBootApplication".}}
@SpringBootApplication
class AllRedundantWithSpringBootApp {
}

// === Spring Test redundancy ===

@ExtendWith(SpringExtension.class) // Noncompliant {{Remove this "@ExtendWith" annotation, already implied by "@SpringBootTest".}}
@SpringBootTest
class ExtendWithSpringExtAndSpringBootTest {
@Test
void test() {
}
}

@ExtendWith(SpringExtension.class) // Noncompliant {{Remove this "@ExtendWith" annotation, already implied by "@WebMvcTest".}}
@WebMvcTest
class ExtendWithSpringExtAndWebMvcTest {
}

@ExtendWith(SpringExtension.class) // Noncompliant {{Remove this "@ExtendWith" annotation, already implied by "@DataJpaTest".}}
@DataJpaTest
class ExtendWithSpringExtAndDataJpaTest {
}

@ExtendWith(SpringExtension.class) // Noncompliant {{Remove this "@ExtendWith" annotation, already implied by "@WebFluxTest".}}
@WebFluxTest
class ExtendWithSpringExtAndWebFluxTest {
}

@Transactional // Noncompliant {{Remove this "@Transactional" annotation, already implied by "@DataJpaTest".}}
@DataJpaTest
class TransactionalWithDataJpaTest {
}

@ExtendWith(SpringExtension.class) // Noncompliant {{Remove this "@ExtendWith" annotation, already implied by "@DataJpaTest".}}
@Transactional // Noncompliant {{Remove this "@Transactional" annotation, already implied by "@DataJpaTest".}}
@DataJpaTest
class MultipleRedundantWithDataJpaTest {
}

// === Compliant cases ===

@Service
class ServiceAlone {
}

@Component
class ComponentAlone {
}

@RestController
class RestControllerAlone {
}

@SpringBootApplication
class SpringBootAppAlone {
}

@Controller
@ResponseBody
class ControllerWithResponseBody {
@GetMapping("/foo")
public String get() {
return "foo";
}
}

@SpringBootApplication
@ComponentScan(basePackages = "com.example.custom")
class SpringBootAppWithCustomComponentScan {
}

@SpringBootTest
class SpringBootTestAlone {
@Test
void test() {
}
}

@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)
@SpringBootTest
class ExtendWithMockitoAndSpringBootTest {
@Test
void test() {
}
}

@SpringBootTest
@Transactional
class TransactionalWithSpringBootTest {
}

@DataJpaTest
class DataJpaTestAlone {
}

@ExtendWith({SpringExtension.class, org.mockito.junit.jupiter.MockitoExtension.class})
@SpringBootTest
class MixedExtensionsWithSpringBootTest {
}

// === Compliant: @Transactional with custom attributes + @DataJpaTest ===

@Transactional(readOnly = true)
@DataJpaTest
class TransactionalReadOnlyWithDataJpaTest {
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
@DataJpaTest
class TransactionalNotSupportedWithDataJpaTest {
}

// === Compliant: @ComponentScan with filters/other attributes + @SpringBootApplication ===

@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.example.excluded"))
@SpringBootApplication
class ComponentScanWithExcludeFiltersAndSpringBootApp {
}

@ComponentScan(lazyInit = true)
@SpringBootApplication
class ComponentScanWithLazyInitAndSpringBootApp {
}

@ComponentScan(useDefaultFilters = false)
@SpringBootApplication
class ComponentScanWithUseDefaultFiltersAndSpringBootApp {
}

// === Compliant: @Component with explicit attributes (bean name) ===

@Component("orders")
@Service
class ComponentWithBeanNameAndService {
}

// === Compliant: @Configuration with explicit attributes + @SpringBootApplication ===

@Configuration(proxyBeanMethods = false)
@SpringBootApplication
class ConfigurationWithProxyBeanMethodsAndSpringBootApp {
}

// === Compliant: @EnableAutoConfiguration with explicit attributes + @SpringBootApplication ===

@EnableAutoConfiguration(exclude = Configuration.class)
@SpringBootApplication
class EnableAutoConfigWithExcludeAndSpringBootApp {
}

// === Compliant: Repeatable @ExtendWith — only SpringExtension instance reported, not MockitoExtension ===

@ExtendWith(SpringExtension.class) // Noncompliant {{Remove this "@ExtendWith" annotation, already implied by "@SpringBootTest".}}
@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)
@SpringBootTest
class RepeatableExtendWithSpringBootTest {
}

// === Compliant: Repeatable @ComponentScan with custom attributes — neither reported ===

@ComponentScan("com.example.pkg1")
@ComponentScan("com.example.pkg2")
@SpringBootApplication
class RepeatableComponentScanWithSpringBootApp {
}

// === Records with redundant annotations ===

@Component // Noncompliant {{Remove this "@Component" annotation, already implied by "@Service".}}
@Service
record OrderServiceRecord(String name) {
}
Loading
Loading