Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
<google.re2j>1.8</google.re2j>

<!-- Version of creedengo rules specifications implemented by this plugin -->
<creedengo-rules-specifications.version>2.2.2</creedengo-rules-specifications.version>
<creedengo-rules-specifications.version>1.0-SNAPSHOT</creedengo-rules-specifications.version>

<!-- URL of the Maven repository where sonarqube will be downloaded -->
<test-it.orchestrator.artifactory.url>https://repo1.maven.org/maven2</test-it.orchestrator.artifactory.url>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,4 +548,26 @@ void testGCI94() {
checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_1MIN);
}


@Test
void testGCI1111() {
String filePath = "src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginated.java";
String ruleId = "creedengo-java:GCI1111";
String ruleMsg = "Hibernate queries must be paginated to avoid excessive data loading";
int[] startLines = new int[]{15, 18, 21};
int[] endLines = new int[]{15, 18, 21};

checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_20MIN);
}

@Test
void testGCI1111NoIssue() {
String filePath = "src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginatedNoIssue.java";
String ruleId = "creedengo-java:GCI1111";
String ruleMsg = "Hibernate queries must be paginated to avoid excessive data loading";
int[] startLines = new int[]{};
int[] endLines = new int[]{};
checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_20MIN);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.greencodeinitiative.creedengo.java.checks;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

// L'interface doit être package-private (pas "public") pour rester dans ce fichier
@Repository
interface DataInHibernateMustBePaginated extends JpaRepository<Object, Long> {

List<Object> findAll(); // Noncompliant {{Hibernate queries must be paginated to avoid excessive data loading}}

@Query(value = "SELECT * FROM users", nativeQuery = true)
List<Object> loadAll(); // Noncompliant {{Hibernate queries must be paginated to avoid excessive data loading}}

@Query("SELECT u FROM User u")
List<Object> getUsers(); // Noncompliant {{Hibernate queries must be paginated to avoid excessive data loading}}

Page<Object> findAll(Pageable pageable); // OK

@Query(value = "SELECT * FROM users", countQuery = "SELECT count(*) FROM users", nativeQuery = true)
Page<Object> findAllNative(Pageable pageable); // OK
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import java.util.List;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Page;

public interface DataInHibernateMustBePaginatedNoIssue {

List<Object> findAll();

@Query(value = "SELECT * FROM users", nativeQuery = true)
List<Object> loadAll();

@Query("SELECT u FROM User u")
List<Object> getUsers();

Page<Object> findAll(Pageable pageable); // OK

@Query(value = "SELECT * FROM users", countQuery = "SELECT count(*) FROM users", nativeQuery = true)
Page<Object> findAllNative(Pageable pageable); // OK
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ public class JavaCheckRegistrar implements CheckRegistrar {
FreeResourcesOfAutoCloseableInterface.class,
AvoidMultipleIfElseStatement.class,
UseOptionalOrElseGetVsOrElse.class,
MakeNonReassignedVariablesConstants.class
MakeNonReassignedVariablesConstants.class,
DataInHibernateMustBePaginated.class
);

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package org.greencodeinitiative.creedengo.java.checks;

import java.util.*;

import org.sonar.check.Rule;
import org.sonar.plugins.java.api.InputFileScannerContext;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.tree.*;
import org.sonar.plugins.java.api.tree.Tree.Kind;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Type;


import java.util.Collections;
import java.util.List;

import org.sonar.check.Rule;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.tree.*;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Type;

import java.util.Collections;
import java.util.List;

Comment on lines +14 to +25
Copy link

Copilot AI Jul 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Duplicate import of Collections detected; remove redundant imports (Collections and List) to clean up the imports section.

Suggested change
import java.util.Collections;
import java.util.List;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.tree.*;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Type;
import java.util.Collections;
import java.util.List;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.tree.*;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Type;

Copilot uses AI. Check for mistakes.
@Rule(key = "GCI1111")
public class DataInHibernateMustBePaginated extends IssuableSubscriptionVisitor {

private static final String MESSAGE = "Hibernate queries must be paginated to avoid excessive data loading";

@Override
public List<Tree.Kind> nodesToVisit() {
return Collections.singletonList(Kind.METHOD);
}

@Override
public void visitNode(Tree tree) {
MethodTree methodTree = (MethodTree) tree;
ClassTree enclosingClass = getEnclosingClass(tree);

if (enclosingClass == null || !isRepository(enclosingClass)) {
return; // Ne rien faire si ce n’est pas un Repository
}


// Récupérer le nom de la méthode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please translate comments to english

String methodName = methodTree.simpleName().name();

// Récupérer le type de retour
String returnType = methodTree.returnType().symbolType().toString();

// Vérifier s’il retourne une collection entière (sans pagination)
boolean returnsAllData = retrournAllData(returnType);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo error in method name (please use something like "isToto" because it returns a boolean


// Vérifier s’il utilise la pagination
boolean hasPaginationParam = isHasPaginationParam(methodTree);

// Vérifier la présence de l’annotation @Query
boolean usesQueryAnnotation = isUsesQueryAnnotation(methodTree);

extracted(returnsAllData, hasPaginationParam, methodTree, usesQueryAnnotation);

}

private void extracted(boolean returnsAllData, boolean hasPaginationParam, MethodTree methodTree, boolean usesQueryAnnotation) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use a method name more explicit ... when I read the method name I must understand what it will do

// Déclencher la règle
if (returnsAllData && !hasPaginationParam) {
reportIssue(methodTree.simpleName(),
MESSAGE);
} else if (usesQueryAnnotation && returnsAllData && !hasPaginationParam) {
reportIssue(methodTree.simpleName(),
MESSAGE);
Comment on lines +67 to +72
Copy link

Copilot AI Jul 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This else if condition is redundant because the previous if (returnsAllData && !hasPaginationParam) already covers the same logic; you can simplify by merging these branches.

Suggested change
if (returnsAllData && !hasPaginationParam) {
reportIssue(methodTree.simpleName(),
MESSAGE);
} else if (usesQueryAnnotation && returnsAllData && !hasPaginationParam) {
reportIssue(methodTree.simpleName(),
MESSAGE);
if ((returnsAllData && !hasPaginationParam) || (usesQueryAnnotation && returnsAllData && !hasPaginationParam)) {
reportIssue(methodTree.simpleName(),
MESSAGE);

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please correct this Copilot feedback

}
}

private static boolean isUsesQueryAnnotation(MethodTree methodTree) {
boolean usesQueryAnnotation = methodTree.modifiers().annotations().stream()
.anyMatch(ann -> ann.annotationType().toString().equals("Query"));
Copy link

Copilot AI Jul 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using annotationType().toString() may not reliably match fully qualified annotation names; consider checking the annotation's symbol (e.g., ann.annotationType().symbolType().fullyQualifiedName()) for robustness.

Suggested change
.anyMatch(ann -> ann.annotationType().toString().equals("Query"));
.anyMatch(ann -> "org.springframework.data.jpa.repository.Query".equals(ann.annotationType().symbolType().fullyQualifiedName()));

Copilot uses AI. Check for mistakes.
return usesQueryAnnotation;
}

private static boolean isHasPaginationParam(MethodTree methodTree) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please delete "is" prefix because "has" prefix is enough

boolean hasPaginationParam = methodTree.parameters().stream()
.anyMatch(param -> {
String type = param.type().toString();
return type.contains("Pageable") || type.contains("PageRequest");
});
return hasPaginationParam;
}

public boolean retrournAllData(String returnType) {
return returnType.startsWith("List")
|| returnType.startsWith("Set")
|| returnType.startsWith("Collection")
|| returnType.startsWith("Iterable");
}

private ClassTree getEnclosingClass(Tree tree) {
Tree parent = tree.parent();
while (parent != null && !(parent instanceof ClassTree)) {
parent = parent.parent();
}
return (ClassTree) parent;
}

private boolean isRepository(ClassTree classTree) {
// Vérifie si la classe implémente une interface Repository (comme JpaRepository)
Symbol.TypeSymbol symbol = classTree.symbol();
if (symbol == null) {
return false;
}

for (Type iface : symbol.type().symbol().interfaces()) {
String ifaceName = iface.fullyQualifiedName();
if (ifaceName.contains("JpaRepository") || ifaceName.contains("CrudRepository") || ifaceName.contains("PagingAndSortingRepository")) {
return true;
}
}

// Ou si l'annotation Repository est présente
return classTree.modifiers().annotations().stream()
.anyMatch(ann -> ann.annotationType().toString().endsWith("Repository"));
}




}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"GCI78",
"GCI79",
"GCI82",
"GCI94"
"GCI94",
"GCI1111"
]
}
23 changes: 23 additions & 0 deletions src/test/files/DataInHibernateMustBePaginated.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import java.util.List;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Page;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

List<User> findAll(); // Noncompliant {{Hibernate queries must be paginated to avoid excessive data loading}}

@Query(value = "SELECT * FROM users", nativeQuery = true)
List<User> loadAll(); // Noncompliant {{Hibernate queries must be paginated to avoid excessive data loading}}

@Query("SELECT u FROM User u")
List<User> getUsers(); // Noncompliant {{Hibernate queries must be paginated to avoid excessive data loading}}

Page<User> findAll(Pageable pageable); // OK

@Query(value = "SELECT * FROM users", countQuery = "SELECT count(*) FROM users", nativeQuery = true)
Page<User> findAllNative(Pageable pageable); // OK
}
22 changes: 22 additions & 0 deletions src/test/files/DataInHibernateMustBePaginatedNoIssue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import java.util.List;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Page;

public interface UserRepository {

List<User> findAll();

@Query(value = "SELECT * FROM users", nativeQuery = true)
List<User> loadAll();

@Query("SELECT u FROM User u")
List<User> getUsers();

Page<User> findAll(Pageable pageable); // OK

@Query(value = "SELECT * FROM users", countQuery = "SELECT count(*) FROM users", nativeQuery = true)
Page<User> findAllNative(Pageable pageable); // OK
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* creedengo - Java language - Provides rules to reduce the environmental footprint of your Java programs
* Copyright © 2024 Green Code Initiative (https://green-code-initiative.org/)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.greencodeinitiative.creedengo.java.checks;

import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;

class DataInHibernateMustBePaginatedTest {
@Test
void test() {
CheckVerifier.newVerifier()
.onFile("src/test/files/DataInHibernateMustBePaginated.java")
.withCheck(new DataInHibernateMustBePaginated())
.verifyIssues();
}

@Test
void testNoIssue() {
CheckVerifier.newVerifier()
.onFile("src/test/files/DataInHibernateMustBePaginatedNoIssue.java")
.withCheck(new DataInHibernateMustBePaginated())
.verifyNoIssues();
}
}
Loading