From 8d3b2b850737974c59725667e21f2c54c4cc2a0e Mon Sep 17 00:00:00 2001
From: Nassim Amghar <2727128+NassimAMGHAR@users.noreply.github.com.>
Date: Tue, 20 May 2025 16:13:56 +0200
Subject: [PATCH 1/8] test case 1
---
...voidNPlusOneProblemInJPAEntitiesCheck.java | 109 ++++++++++++++++++
1 file changed, 109 insertions(+)
create mode 100644 src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
diff --git a/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
new file mode 100644
index 00000000..8d056dfd
--- /dev/null
+++ b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
@@ -0,0 +1,109 @@
+/*
+ * 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 .
+ */
+package org.greencodeinitiative.creedengo.java.checks;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.*;
+
+public class AvoidNPlusOneProblemInJPAEntitiesCheck {
+
+ @Autowired
+ private AuthorRepository authorRepository;
+
+ public List smellGetAllAuthors() {
+ List authors = authorRepository.findAll();
+ for (Author author : authors) {
+ List books = author.getBooks(); // Cela peut déclencher une requête SQL pour chaque auteur
+ }
+ return authors;
+ }
+
+
+ public class Author {
+
+ private Long id;
+ private String name;
+
+ private List books;
+
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List getBooks() {
+ return books;
+ }
+
+ public void setBooks(List books) {
+ this.books = books;
+ }
+ }
+
+
+ public class Book {
+
+ private Long id;
+ private String title;
+
+ private Author author;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public Author getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(Author author) {
+ this.author = author;
+ }
+ }
+
+ public interface AuthorRepository extends JpaRepository {
+
+ }
+
+}
From 002a140643ed914157eb1a569dcd1d6fda352f06 Mon Sep 17 00:00:00 2001
From: Nassim Amghar <2727128+NassimAMGHAR@users.noreply.github.com.>
Date: Tue, 20 May 2025 16:53:39 +0200
Subject: [PATCH 2/8] Add first test case and failing rule
---
...lusOneProblemInJPAEntitiesCheckIssue.java} | 2 +-
...voidNPlusOneProblemInJPAEntitiesCheck.java | 105 +++++++++++++++++
...PlusOneProblemInJPAEntitiesCheckIssue.java | 109 ++++++++++++++++++
...NPlusOneProblemInJPAEntitiesCheckTest.java | 35 ++++++
4 files changed, 250 insertions(+), 1 deletion(-)
rename src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/{AvoidNPlusOneProblemInJPAEntitiesCheck.java => AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java} (97%)
create mode 100644 src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
create mode 100644 src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
create mode 100644 src/test/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckTest.java
diff --git a/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
similarity index 97%
rename from src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
rename to src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
index 8d056dfd..61f1a325 100644
--- a/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
+++ b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
@@ -22,7 +22,7 @@
import java.util.*;
-public class AvoidNPlusOneProblemInJPAEntitiesCheck {
+public class AvoidNPlusOneProblemInJPAEntitiesCheckIssue {
@Autowired
private AuthorRepository authorRepository;
diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
new file mode 100644
index 00000000..da047021
--- /dev/null
+++ b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
@@ -0,0 +1,105 @@
+package org.greencodeinitiative.creedengo.java.checks;
+
+import org.sonar.check.Rule;
+import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
+import org.sonar.plugins.java.api.semantic.MethodMatchers;
+import org.sonar.plugins.java.api.tree.*;
+import org.sonarsource.analyzer.commons.annotations.DeprecatedRuleKey;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Rule(key = "GRC3")
+@DeprecatedRuleKey(repositoryKey = "ecocode-java", ruleKey = "GRC3")
+@DeprecatedRuleKey(repositoryKey = "greencodeinitiative-java", ruleKey = "GRC3")
+public class AvoidNPlusOneProblemInJPAEntitiesCheck extends IssuableSubscriptionVisitor {
+ protected static final String RULE_MESSAGE = "Avoid N+1 with nested JPA Entities";
+
+ private static final String BASE_STREAM = "java.util.stream.BaseStream";
+ private static final String SPRING_REPOSITORY = "org.springframework.data.repository.Repository";
+
+ private static final MethodMatchers SPRING_REPOSITORY_METHOD =
+ MethodMatchers
+ .create()
+ .ofSubTypes(SPRING_REPOSITORY)
+ .anyName()
+ .withAnyParameters()
+ .build();
+
+ private static final MethodMatchers STREAM_FOREACH_METHOD =
+ MethodMatchers
+ .create()
+ .ofSubTypes(BASE_STREAM)
+ .names("forEach", "forEachOrdered", "map", "peek")
+ .withAnyParameters()
+ .build();
+
+ private final AvoidNPlusOneProblemInJPAEntitiesCheck.AvoidSpringRepositoryCallInLoopCheckVisitor visitorInFile = new AvoidNPlusOneProblemInJPAEntitiesCheck.AvoidSpringRepositoryCallInLoopCheckVisitor();
+ private final AvoidNPlusOneProblemInJPAEntitiesCheck.StreamVisitor streamVisitor = new AvoidNPlusOneProblemInJPAEntitiesCheck.StreamVisitor();
+
+ private final AvoidNPlusOneProblemInJPAEntitiesCheck.AncestorMethodVisitor ancestorMethodVisitor = new AvoidNPlusOneProblemInJPAEntitiesCheck.AncestorMethodVisitor();
+
+ @Override
+ public List nodesToVisit() {
+ return Arrays.asList(
+ Tree.Kind.FOR_EACH_STATEMENT, // loop
+ Tree.Kind.FOR_STATEMENT, // loop
+ Tree.Kind.WHILE_STATEMENT, // loop
+ Tree.Kind.DO_STATEMENT, // loop
+ Tree.Kind.METHOD_INVOCATION // stream
+ );
+ }
+
+ @Override
+ public void visitNode(Tree tree) {
+ if (tree.is(Tree.Kind.METHOD_INVOCATION)) { // stream process
+ MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree;
+ if (STREAM_FOREACH_METHOD.matches(methodInvocationTree)) {
+ tree.accept(streamVisitor);
+ }
+ } else { // loop process
+ tree.accept(visitorInFile);
+ }
+ }
+
+ private class AvoidSpringRepositoryCallInLoopCheckVisitor extends BaseTreeVisitor {
+ @Override
+ public void visitMethodInvocation(MethodInvocationTree tree) {
+ if (SPRING_REPOSITORY_METHOD.matches(tree)) {
+ reportIssue(tree, RULE_MESSAGE);
+ } else {
+ super.visitMethodInvocation(tree);
+ }
+ }
+
+ }
+
+ private class StreamVisitor extends BaseTreeVisitor {
+
+ @Override
+ public void visitLambdaExpression(LambdaExpressionTree tree) {
+ tree.accept(ancestorMethodVisitor);
+ }
+
+ }
+
+ private class AncestorMethodVisitor extends BaseTreeVisitor {
+
+ @Override
+ public void visitMethodInvocation(MethodInvocationTree tree) {
+ // if the method is a spring repository method, report an issue
+ if (SPRING_REPOSITORY_METHOD.matches(tree)) {
+ reportIssue(tree, RULE_MESSAGE);
+ } else { // else, check if the method is a method invocation and check recursively
+ if (tree.methodSelect().is(Tree.Kind.MEMBER_SELECT)) {
+ MemberSelectExpressionTree memberSelectTree = (MemberSelectExpressionTree) tree.methodSelect();
+ if ( memberSelectTree.expression().is(Tree.Kind.METHOD_INVOCATION)) {
+ MethodInvocationTree methodInvocationTree = (MethodInvocationTree) memberSelectTree.expression();
+ methodInvocationTree.accept(ancestorMethodVisitor);
+ }
+ }
+ }
+ }
+
+ }
+}
diff --git a/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java b/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
new file mode 100644
index 00000000..61f1a325
--- /dev/null
+++ b/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
@@ -0,0 +1,109 @@
+/*
+ * 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 .
+ */
+package org.greencodeinitiative.creedengo.java.checks;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.*;
+
+public class AvoidNPlusOneProblemInJPAEntitiesCheckIssue {
+
+ @Autowired
+ private AuthorRepository authorRepository;
+
+ public List smellGetAllAuthors() {
+ List authors = authorRepository.findAll();
+ for (Author author : authors) {
+ List books = author.getBooks(); // Cela peut déclencher une requête SQL pour chaque auteur
+ }
+ return authors;
+ }
+
+
+ public class Author {
+
+ private Long id;
+ private String name;
+
+ private List books;
+
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List getBooks() {
+ return books;
+ }
+
+ public void setBooks(List books) {
+ this.books = books;
+ }
+ }
+
+
+ public class Book {
+
+ private Long id;
+ private String title;
+
+ private Author author;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public Author getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(Author author) {
+ this.author = author;
+ }
+ }
+
+ public interface AuthorRepository extends JpaRepository {
+
+ }
+
+}
diff --git a/src/test/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckTest.java b/src/test/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckTest.java
new file mode 100644
index 00000000..a960c569
--- /dev/null
+++ b/src/test/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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 .
+ */
+package org.greencodeinitiative.creedengo.java.checks;
+
+import org.greencodeinitiative.creedengo.java.utils.FilesUtils;
+import org.junit.jupiter.api.Test;
+import org.sonar.java.checks.verifier.CheckVerifier;
+
+class AvoidNPlusOneProblemInJPAEntitiesCheckTest {
+
+ @Test
+ void test() {
+ CheckVerifier.newVerifier()
+ .onFile("src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java")
+ .withCheck(new AvoidNPlusOneProblemInJPAEntitiesCheck())
+ .withClassPath(FilesUtils.getClassPath("target/test-jars"))
+ .verifyIssues();
+ }
+
+}
From 48eee8c2c6e8edb1b306cfdfbd5d7518406e4a46 Mon Sep 17 00:00:00 2001
From: Nassim Amghar <2727128+NassimAMGHAR@users.noreply.github.com.>
Date: Wed, 21 May 2025 09:42:55 +0200
Subject: [PATCH 3/8] Implement N+1 problem detection for JPA entities with
enhanced reporting
---
...PlusOneProblemInJPAEntitiesCheckIssue.java | 2 +-
.../creedengo/java/JavaCheckRegistrar.java | 3 +-
...voidNPlusOneProblemInJPAEntitiesCheck.java | 124 +++++++++---------
.../creedengo/java/creedengo_way_profile.json | 3 +-
...PlusOneProblemInJPAEntitiesCheckIssue.java | 2 +-
5 files changed, 68 insertions(+), 66 deletions(-)
diff --git a/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
index 61f1a325..f4b51e15 100644
--- a/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
+++ b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
@@ -30,7 +30,7 @@ public class AvoidNPlusOneProblemInJPAEntitiesCheckIssue {
public List smellGetAllAuthors() {
List authors = authorRepository.findAll();
for (Author author : authors) {
- List books = author.getBooks(); // Cela peut déclencher une requête SQL pour chaque auteur
+ List books = author.getBooks(); // // Noncompliant {{ Evitez le N+1 : utilisez un fetch join ou une récupération eager. }}
}
return authors;
}
diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java b/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java
index 791f0cef..0fe301ae 100644
--- a/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java
+++ b/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java
@@ -50,7 +50,8 @@ public class JavaCheckRegistrar implements CheckRegistrar {
FreeResourcesOfAutoCloseableInterface.class,
AvoidMultipleIfElseStatement.class,
UseOptionalOrElseGetVsOrElse.class,
- MakeNonReassignedVariablesConstants.class
+ MakeNonReassignedVariablesConstants.class,
+ AvoidNPlusOneProblemInJPAEntitiesCheck.class
);
/**
diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
index da047021..1d79d64e 100644
--- a/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
+++ b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
@@ -3,103 +3,103 @@
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
+import org.sonar.plugins.java.api.semantic.Symbol;
+import org.sonar.plugins.java.api.semantic.Symbol.VariableSymbol;
import org.sonar.plugins.java.api.tree.*;
import org.sonarsource.analyzer.commons.annotations.DeprecatedRuleKey;
-import java.util.Arrays;
-import java.util.List;
+import java.util.*;
@Rule(key = "GRC3")
@DeprecatedRuleKey(repositoryKey = "ecocode-java", ruleKey = "GRC3")
@DeprecatedRuleKey(repositoryKey = "greencodeinitiative-java", ruleKey = "GRC3")
public class AvoidNPlusOneProblemInJPAEntitiesCheck extends IssuableSubscriptionVisitor {
- protected static final String RULE_MESSAGE = "Avoid N+1 with nested JPA Entities";
- private static final String BASE_STREAM = "java.util.stream.BaseStream";
+ protected static final String RULE_MESSAGE = " Evitez le N+1 : utilisez un fetch join ou une récupération eager. ";
+
private static final String SPRING_REPOSITORY = "org.springframework.data.repository.Repository";
- private static final MethodMatchers SPRING_REPOSITORY_METHOD =
- MethodMatchers
- .create()
+ private static final MethodMatchers SPRING_REPOSITORY_METHOD_FIND_ALL =
+ MethodMatchers.create()
.ofSubTypes(SPRING_REPOSITORY)
- .anyName()
- .withAnyParameters()
- .build();
-
- private static final MethodMatchers STREAM_FOREACH_METHOD =
- MethodMatchers
- .create()
- .ofSubTypes(BASE_STREAM)
- .names("forEach", "forEachOrdered", "map", "peek")
+ .names("findAll")
.withAnyParameters()
.build();
- private final AvoidNPlusOneProblemInJPAEntitiesCheck.AvoidSpringRepositoryCallInLoopCheckVisitor visitorInFile = new AvoidNPlusOneProblemInJPAEntitiesCheck.AvoidSpringRepositoryCallInLoopCheckVisitor();
- private final AvoidNPlusOneProblemInJPAEntitiesCheck.StreamVisitor streamVisitor = new AvoidNPlusOneProblemInJPAEntitiesCheck.StreamVisitor();
-
- private final AvoidNPlusOneProblemInJPAEntitiesCheck.AncestorMethodVisitor ancestorMethodVisitor = new AvoidNPlusOneProblemInJPAEntitiesCheck.AncestorMethodVisitor();
+ private final Map repositoryFindAllVars = new HashMap<>();
@Override
public List nodesToVisit() {
- return Arrays.asList(
- Tree.Kind.FOR_EACH_STATEMENT, // loop
- Tree.Kind.FOR_STATEMENT, // loop
- Tree.Kind.WHILE_STATEMENT, // loop
- Tree.Kind.DO_STATEMENT, // loop
- Tree.Kind.METHOD_INVOCATION // stream
- );
+ return Arrays.asList(Tree.Kind.VARIABLE, Tree.Kind.METHOD_INVOCATION, Tree.Kind.FOR_EACH_STATEMENT);
}
@Override
public void visitNode(Tree tree) {
- if (tree.is(Tree.Kind.METHOD_INVOCATION)) { // stream process
- MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree;
- if (STREAM_FOREACH_METHOD.matches(methodInvocationTree)) {
- tree.accept(streamVisitor);
+ if (tree.is(Tree.Kind.VARIABLE)) {
+ VariableTree variableTree = (VariableTree) tree;
+ ExpressionTree initializer = variableTree.initializer();
+ if (initializer != null && initializer.is(Tree.Kind.METHOD_INVOCATION)) {
+ MethodInvocationTree methodInvocation = (MethodInvocationTree) initializer;
+ if (SPRING_REPOSITORY_METHOD_FIND_ALL.matches(methodInvocation)) {
+ VariableSymbol symbol = (VariableSymbol) variableTree.symbol();
+ repositoryFindAllVars.put(symbol, tree);
+ }
}
- } else { // loop process
- tree.accept(visitorInFile);
}
- }
- private class AvoidSpringRepositoryCallInLoopCheckVisitor extends BaseTreeVisitor {
- @Override
- public void visitMethodInvocation(MethodInvocationTree tree) {
- if (SPRING_REPOSITORY_METHOD.matches(tree)) {
- reportIssue(tree, RULE_MESSAGE);
- } else {
- super.visitMethodInvocation(tree);
+ // Cas d'un foreach sur une variable issue de findAll()
+ if (tree.is(Tree.Kind.FOR_EACH_STATEMENT)) {
+ ForEachStatement forEach = (ForEachStatement) tree;
+ ExpressionTree iterable = forEach.expression();
+ if (iterable.is(Tree.Kind.IDENTIFIER)) {
+ Symbol symbol = ((IdentifierTree) iterable).symbol();
+ if (repositoryFindAllVars.containsKey(symbol)) {
+ // On marque la variable d'itération comme issue d'un findAll()
+ VariableSymbol loopVar = (VariableSymbol) forEach.variable().symbol();
+ repositoryFindAllVars.put(loopVar, tree);
+ }
}
}
- }
-
- private class StreamVisitor extends BaseTreeVisitor {
+ // Détection d'un appel de getter sur une variable issue de findAll()
+ if (tree.is(Tree.Kind.METHOD_INVOCATION)) {
+ MethodInvocationTree methodInvocation = (MethodInvocationTree) tree;
- @Override
- public void visitLambdaExpression(LambdaExpressionTree tree) {
- tree.accept(ancestorMethodVisitor);
- }
+ // Check if the call is something like post.getAuthor() or post.getAuthor().getName()
+ ExpressionTree select = methodInvocation.methodSelect();
+ if (select.is(Tree.Kind.MEMBER_SELECT)) {
+ MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) select;
+ ExpressionTree root = memberSelect.expression();
- }
+ if (root.is(Tree.Kind.IDENTIFIER)) {
+ Symbol symbol = ((IdentifierTree) root).symbol();
+ if (repositoryFindAllVars.containsKey(symbol) && isGetter(memberSelect.identifier().name())) {
+ reportIssue(methodInvocation, RULE_MESSAGE);
+ }
+ }
- private class AncestorMethodVisitor extends BaseTreeVisitor {
-
- @Override
- public void visitMethodInvocation(MethodInvocationTree tree) {
- // if the method is a spring repository method, report an issue
- if (SPRING_REPOSITORY_METHOD.matches(tree)) {
- reportIssue(tree, RULE_MESSAGE);
- } else { // else, check if the method is a method invocation and check recursively
- if (tree.methodSelect().is(Tree.Kind.MEMBER_SELECT)) {
- MemberSelectExpressionTree memberSelectTree = (MemberSelectExpressionTree) tree.methodSelect();
- if ( memberSelectTree.expression().is(Tree.Kind.METHOD_INVOCATION)) {
- MethodInvocationTree methodInvocationTree = (MethodInvocationTree) memberSelectTree.expression();
- methodInvocationTree.accept(ancestorMethodVisitor);
+ // Handle nested getter chains (e.g., post.getAuthor().getName())
+ if (root.is(Tree.Kind.METHOD_INVOCATION)) {
+ MethodInvocationTree rootInvocation = (MethodInvocationTree) root;
+ ExpressionTree deeperSelect = rootInvocation.methodSelect();
+ if (deeperSelect.is(Tree.Kind.MEMBER_SELECT)) {
+ MemberSelectExpressionTree deeperMemberSelect = (MemberSelectExpressionTree) deeperSelect;
+ ExpressionTree deeperRoot = deeperMemberSelect.expression();
+
+ if (deeperRoot.is(Tree.Kind.IDENTIFIER)) {
+ Symbol rootSymbol = ((IdentifierTree) deeperRoot).symbol();
+ if (repositoryFindAllVars.containsKey(rootSymbol) && isGetter(deeperMemberSelect.identifier().name())) {
+ reportIssue(methodInvocation, RULE_MESSAGE);
+ }
+ }
}
}
}
}
+ }
+ // Méthode utilitaire pour détecter un getter
+ private boolean isGetter(String methodName) {
+ return methodName.startsWith("get") && methodName.length() > 3 && Character.isUpperCase(methodName.charAt(3));
}
}
diff --git a/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json b/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json
index 059bf0f5..d044f58c 100644
--- a/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json
+++ b/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json
@@ -18,6 +18,7 @@
"GCI78",
"GCI79",
"GCI82",
- "GCI94"
+ "GCI94",
+ "GRC3"
]
}
diff --git a/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java b/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
index 61f1a325..fd7f479c 100644
--- a/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
+++ b/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
@@ -30,7 +30,7 @@ public class AvoidNPlusOneProblemInJPAEntitiesCheckIssue {
public List smellGetAllAuthors() {
List authors = authorRepository.findAll();
for (Author author : authors) {
- List books = author.getBooks(); // Cela peut déclencher une requête SQL pour chaque auteur
+ List books = author.getBooks(); // Noncompliant {{ Evitez le N+1 : utilisez un fetch join ou une récupération eager. }}
}
return authors;
}
From 4e3fb1be29021e1fc3b255da77a64c59148ea603 Mon Sep 17 00:00:00 2001
From: Nassim Amghar <2727128+NassimAMGHAR@users.noreply.github.com.>
Date: Wed, 21 May 2025 10:02:09 +0200
Subject: [PATCH 4/8] Update N+1 problem detection rule key and adjust related
configurations
---
pom.xml | 2 +-
.../java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java | 6 +++---
.../creedengo/java/creedengo_way_profile.json | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/pom.xml b/pom.xml
index ae43a190..1874414b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,7 +78,7 @@
1.8
- 2.2.2
+ main-snapshot
https://repo1.maven.org/maven2
diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
index 1d79d64e..928faa3d 100644
--- a/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
+++ b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
@@ -10,9 +10,9 @@
import java.util.*;
-@Rule(key = "GRC3")
-@DeprecatedRuleKey(repositoryKey = "ecocode-java", ruleKey = "GRC3")
-@DeprecatedRuleKey(repositoryKey = "greencodeinitiative-java", ruleKey = "GRC3")
+@Rule(key = "GCI604")
+@DeprecatedRuleKey(repositoryKey = "ecocode-java", ruleKey = "GCI604")
+@DeprecatedRuleKey(repositoryKey = "greencodeinitiative-java", ruleKey = "GCI604")
public class AvoidNPlusOneProblemInJPAEntitiesCheck extends IssuableSubscriptionVisitor {
protected static final String RULE_MESSAGE = " Evitez le N+1 : utilisez un fetch join ou une récupération eager. ";
diff --git a/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json b/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json
index d044f58c..299073b9 100644
--- a/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json
+++ b/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json
@@ -19,6 +19,6 @@
"GCI79",
"GCI82",
"GCI94",
- "GRC3"
+ "GCI604"
]
}
From 6f0abb6f389ffa3c5232ea69b7aa149264fbfced Mon Sep 17 00:00:00 2001
From: Nassim Amghar <2727128+NassimAMGHAR@users.noreply.github.com.>
Date: Wed, 21 May 2025 13:05:57 +0200
Subject: [PATCH 5/8] remove deprecated annotations
---
.../java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
index 928faa3d..a37b71fd 100644
--- a/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
+++ b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
@@ -6,13 +6,10 @@
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Symbol.VariableSymbol;
import org.sonar.plugins.java.api.tree.*;
-import org.sonarsource.analyzer.commons.annotations.DeprecatedRuleKey;
import java.util.*;
@Rule(key = "GCI604")
-@DeprecatedRuleKey(repositoryKey = "ecocode-java", ruleKey = "GCI604")
-@DeprecatedRuleKey(repositoryKey = "greencodeinitiative-java", ruleKey = "GCI604")
public class AvoidNPlusOneProblemInJPAEntitiesCheck extends IssuableSubscriptionVisitor {
protected static final String RULE_MESSAGE = " Evitez le N+1 : utilisez un fetch join ou une récupération eager. ";
From 79cd8c7f8865d4625fdfe07e170fffd0692f3711 Mon Sep 17 00:00:00 2001
From: Nassim Amghar <2727128+NassimAMGHAR@users.noreply.github.com.>
Date: Wed, 21 May 2025 15:11:28 +0200
Subject: [PATCH 6/8] Change rule message to english
---
.../java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java | 2 +-
src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
index a37b71fd..2fe0ed3f 100644
--- a/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
+++ b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
@@ -12,7 +12,7 @@
@Rule(key = "GCI604")
public class AvoidNPlusOneProblemInJPAEntitiesCheck extends IssuableSubscriptionVisitor {
- protected static final String RULE_MESSAGE = " Evitez le N+1 : utilisez un fetch join ou une récupération eager. ";
+ protected static final String RULE_MESSAGE = " Avoid the N+1 problem: use a fetch join or eager fetching. ";
private static final String SPRING_REPOSITORY = "org.springframework.data.repository.Repository";
diff --git a/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java b/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
index fd7f479c..c2f30881 100644
--- a/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
+++ b/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
@@ -30,7 +30,7 @@ public class AvoidNPlusOneProblemInJPAEntitiesCheckIssue {
public List smellGetAllAuthors() {
List authors = authorRepository.findAll();
for (Author author : authors) {
- List books = author.getBooks(); // Noncompliant {{ Evitez le N+1 : utilisez un fetch join ou une récupération eager. }}
+ List books = author.getBooks(); // Noncompliant {{ Avoid the N+1 problem: use a fetch join or eager fetching. }}
}
return authors;
}
From 0b9e962075671722e2f02e9d3eb5e2fc7eb64d40 Mon Sep 17 00:00:00 2001
From: Nassim Amghar <2727128+NassimAMGHAR@users.noreply.github.com.>
Date: Mon, 26 May 2025 20:46:55 +0200
Subject: [PATCH 7/8] Update rule message for N+1 problem detection in JPA
entities
---
.../java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java | 2 +-
src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
index 2fe0ed3f..c06c3bf4 100644
--- a/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
+++ b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java
@@ -12,7 +12,7 @@
@Rule(key = "GCI604")
public class AvoidNPlusOneProblemInJPAEntitiesCheck extends IssuableSubscriptionVisitor {
- protected static final String RULE_MESSAGE = " Avoid the N+1 problem: use a fetch join or eager fetching. ";
+ protected static final String RULE_MESSAGE = " Detection of the \"N+1 problem\" on Spring Data JPA repositories ";
private static final String SPRING_REPOSITORY = "org.springframework.data.repository.Repository";
diff --git a/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java b/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
index c2f30881..4c698a6f 100644
--- a/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
+++ b/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
@@ -30,7 +30,7 @@ public class AvoidNPlusOneProblemInJPAEntitiesCheckIssue {
public List smellGetAllAuthors() {
List authors = authorRepository.findAll();
for (Author author : authors) {
- List books = author.getBooks(); // Noncompliant {{ Avoid the N+1 problem: use a fetch join or eager fetching. }}
+ List books = author.getBooks(); // Noncompliant {{ Detection of the "N+1 problem" on Spring Data JPA repositories }}
}
return authors;
}
From 934fe68730442824d2ffa8f5fe0c86f80525d6ff Mon Sep 17 00:00:00 2001
From: Nassim Amghar <2727128+NassimAMGHAR@users.noreply.github.com.>
Date: Mon, 26 May 2025 20:53:23 +0200
Subject: [PATCH 8/8] Update noncompliant message for N+1 problem detection in
JPA entities
---
.../checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
index f4b51e15..4c698a6f 100644
--- a/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
+++ b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java
@@ -30,7 +30,7 @@ public class AvoidNPlusOneProblemInJPAEntitiesCheckIssue {
public List smellGetAllAuthors() {
List authors = authorRepository.findAll();
for (Author author : authors) {
- List books = author.getBooks(); // // Noncompliant {{ Evitez le N+1 : utilisez un fetch join ou une récupération eager. }}
+ List books = author.getBooks(); // Noncompliant {{ Detection of the "N+1 problem" on Spring Data JPA repositories }}
}
return authors;
}