From 2c398d6bc2c46f76759c5473550f557377f61ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Fri, 8 May 2026 10:27:32 +0200 Subject: [PATCH 01/15] start adding joda time to the check --- .../org/sonar/java/checks/DateAndTimesCheck.java | 3 ++- .../src/test/files/checks/DateAndTimesCheck.java | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java index 70a8ed07fd6..750f13381cd 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java @@ -30,7 +30,8 @@ public class DateAndTimesCheck extends AbstractMethodDetection implements JavaVe private static final MethodMatchers METHOD_MATCHERS = MethodMatchers.or( MethodMatchers.create().ofTypes("java.util.Calendar").names("getInstance").withAnyParameters().build(), - MethodMatchers.create().ofTypes("java.util.Date").constructor().withAnyParameters().build()); + MethodMatchers.create().ofTypes("java.util.Date").constructor().withAnyParameters().build(), + MethodMatchers.create().ofSubTypes("org.joda.time").constructor().withAnyParameters().build()); @Override protected MethodMatchers getMethodInvocationMatchers() { diff --git a/java-checks/src/test/files/checks/DateAndTimesCheck.java b/java-checks/src/test/files/checks/DateAndTimesCheck.java index a9198692e08..5c8f0b46283 100644 --- a/java-checks/src/test/files/checks/DateAndTimesCheck.java +++ b/java-checks/src/test/files/checks/DateAndTimesCheck.java @@ -1,14 +1,25 @@ import java.util.Date; import java.util.Locale; import java.util.Calendar; +import org.joda.time.DateTime; class A { - void foo() { + void javaUtil() { Date now = new Date(); // Noncompliant {{Use the Java 8 Date and Time API instead.}} + // ^^^^^^^^^^ now = new Date(1499159427440L); // Noncompliant + // ^^^^^^^^^^^^^^^^^^^^^^^^ DateFormat df = new SimpleDateFormat("dd.MM.yyyy"); + Calendar christmas = Calendar.getInstance(); // Noncompliant + // ^^^^^^^^^^^^^^^^^^^^^^ christmas = Calendar.getInstance(Locale.CANADA); // Noncompliant + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ christmas.setTime(df.parse("25.12.2020")); } + + void jodaTime() { + DateTime dt = new DateTime(); // Noncompliant {{Use the Java 8 Date and Time API instead.}} + // ^^^^^^^^^^^^^^ + } } From e880b8faa244dc059e1e05b5bec485375c5b8e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Fri, 8 May 2026 15:04:54 +0200 Subject: [PATCH 02/15] raise an issue on joda time import --- .../sonar/java/checks/DateAndTimesCheck.java | 24 ++++++++++++++++--- .../test/files/checks/DateAndTimesCheck.java | 9 +++---- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java index 750f13381cd..6aaad0f3540 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java @@ -16,11 +16,15 @@ */ package org.sonar.java.checks; +import java.util.List; import org.sonar.check.Rule; +import org.sonar.java.checks.helpers.ExpressionsHelper; import org.sonar.plugins.java.api.JavaVersionAwareVisitor; import org.sonar.java.checks.methods.AbstractMethodDetection; import org.sonar.plugins.java.api.JavaVersion; import org.sonar.plugins.java.api.semantic.MethodMatchers; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.ImportTree; import org.sonar.plugins.java.api.tree.MethodInvocationTree; import org.sonar.plugins.java.api.tree.NewClassTree; import org.sonar.plugins.java.api.tree.Tree; @@ -30,8 +34,7 @@ public class DateAndTimesCheck extends AbstractMethodDetection implements JavaVe private static final MethodMatchers METHOD_MATCHERS = MethodMatchers.or( MethodMatchers.create().ofTypes("java.util.Calendar").names("getInstance").withAnyParameters().build(), - MethodMatchers.create().ofTypes("java.util.Date").constructor().withAnyParameters().build(), - MethodMatchers.create().ofSubTypes("org.joda.time").constructor().withAnyParameters().build()); + MethodMatchers.create().ofTypes("java.util.Date").constructor().withAnyParameters().build()); @Override protected MethodMatchers getMethodInvocationMatchers() { @@ -50,7 +53,6 @@ protected void onMethodInvocationFound(MethodInvocationTree mit) { private void reportIssue(Tree tree) { reportIssue(tree, "Use the Java 8 Date and Time API instead." + context.getJavaVersion().java8CompatibilityMessage()); - } @Override @@ -58,4 +60,20 @@ public boolean isCompatibleWithJavaVersion(JavaVersion version) { return version.isJava8Compatible(); } + @Override + public List nodesToVisit() { + return List.of(Tree.Kind.IMPORT, Tree.Kind.METHOD_INVOCATION, Tree.Kind.NEW_CLASS); + } + + @Override + public void visitNode(Tree tree) { + if (tree instanceof ImportTree importTree) { + String qualifiedName = ExpressionsHelper.concatenate((ExpressionTree) importTree.qualifiedIdentifier()); + if (qualifiedName.startsWith("org.joda.time")) { + reportIssue(importTree); + } + } + super.visitNode(tree); + } + } diff --git a/java-checks/src/test/files/checks/DateAndTimesCheck.java b/java-checks/src/test/files/checks/DateAndTimesCheck.java index 5c8f0b46283..9a94dc7c91f 100644 --- a/java-checks/src/test/files/checks/DateAndTimesCheck.java +++ b/java-checks/src/test/files/checks/DateAndTimesCheck.java @@ -1,7 +1,9 @@ import java.util.Date; import java.util.Locale; import java.util.Calendar; -import org.joda.time.DateTime; +import org.joda.time.DateTime; // Noncompliant {{Use the Java 8 Date and Time API instead.}} +import org.joda.time.*; // Noncompliant {{Use the Java 8 Date and Time API instead.}} +import java.time; class A { void javaUtil() { @@ -17,9 +19,4 @@ void javaUtil() { // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ christmas.setTime(df.parse("25.12.2020")); } - - void jodaTime() { - DateTime dt = new DateTime(); // Noncompliant {{Use the Java 8 Date and Time API instead.}} - // ^^^^^^^^^^^^^^ - } } From f4b3ee0b423d66e6f853c28d6a8000c2580cb542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Mon, 11 May 2026 10:48:27 +0200 Subject: [PATCH 03/15] apply review bot suggestions --- .../main/java/org/sonar/java/checks/DateAndTimesCheck.java | 6 ++++-- java-checks/src/test/files/checks/DateAndTimesCheck.java | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java index 6aaad0f3540..272b678cd2d 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java @@ -62,14 +62,16 @@ public boolean isCompatibleWithJavaVersion(JavaVersion version) { @Override public List nodesToVisit() { - return List.of(Tree.Kind.IMPORT, Tree.Kind.METHOD_INVOCATION, Tree.Kind.NEW_CLASS); + List kinds = new java.util.ArrayList<>(super.nodesToVisit()); + kinds.add(Tree.Kind.IMPORT); + return kinds; } @Override public void visitNode(Tree tree) { if (tree instanceof ImportTree importTree) { String qualifiedName = ExpressionsHelper.concatenate((ExpressionTree) importTree.qualifiedIdentifier()); - if (qualifiedName.startsWith("org.joda.time")) { + if (qualifiedName.startsWith("org.joda.time.")) { reportIssue(importTree); } } diff --git a/java-checks/src/test/files/checks/DateAndTimesCheck.java b/java-checks/src/test/files/checks/DateAndTimesCheck.java index 9a94dc7c91f..edd46c305e4 100644 --- a/java-checks/src/test/files/checks/DateAndTimesCheck.java +++ b/java-checks/src/test/files/checks/DateAndTimesCheck.java @@ -3,7 +3,7 @@ import java.util.Calendar; import org.joda.time.DateTime; // Noncompliant {{Use the Java 8 Date and Time API instead.}} import org.joda.time.*; // Noncompliant {{Use the Java 8 Date and Time API instead.}} -import java.time; +import java.time.LocalDateTime; class A { void javaUtil() { From 24e2963fdecfd3a675147517f2ea10961681e00d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Mon, 11 May 2026 11:06:44 +0200 Subject: [PATCH 04/15] fix ruling test --- .../resources/sonar-server/java-S2143.json | 364 +++++++++--------- 1 file changed, 186 insertions(+), 178 deletions(-) diff --git a/its/ruling/src/test/resources/sonar-server/java-S2143.json b/its/ruling/src/test/resources/sonar-server/java-S2143.json index 1748cbdbf4f..03034df8398 100644 --- a/its/ruling/src/test/resources/sonar-server/java-S2143.json +++ b/its/ruling/src/test/resources/sonar-server/java-S2143.json @@ -1,180 +1,188 @@ { -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/JwtHttpHandler.java": [ -134, -155 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/JwtSerializer.java": [ -92, -93, -132 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/SsoAuthenticator.java": [ -152, -155 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/batch/ProjectDataLoader.java": [ -91 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/ce/ws/ActivityAction.java": [ -142, -145 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/ce/ws/TaskFormatter.java": [ -91, -128 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/component/ComponentUpdater.java": [ -103 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java": [ -213, -221 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueAssigner.java": [ -68 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculator.java": [ -61 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycle.java": [ -48 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/MovedIssueVisitor.java": [ -60 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortCalculator.java": [ -82 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java": [ -63 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PeriodResolver.java": [ -115, -207 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistComponentsStep.java": [ -372 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java": [ -117, -133 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/ValidateProjectStep.java": [ -153, -153 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/IssueQuery.java": [ -200, -205, -210, -392, -397, -402 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/index/IssueIndex.java": [ -347 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java": [ -202 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/AddCommentAction.java": [ -98 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/AssignAction.java": [ -121 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java": [ -194 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java": [ -99 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java": [ -273 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java": [ -108 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SetTagsAction.java": [ -106 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SetTypeAction.java": [ -108 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java": [ -73, -74 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java": [ -135 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java": [ -201, -226 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/permission/ws/template/CreateTemplateAction.java": [ -130 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/permission/ws/template/UpdateTemplateAction.java": [ -136 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/ServerImpl.java": [ -71 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/StartupMetadataPersister.java": [ -52 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java": [ -100 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java": [ -57, -58 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/plugins/UpdateCenterClient.java": [ -99 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java": [ -81 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/BuiltInQualityProfilesNotificationTemplate.java": [ -66, -68 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/QProfileFactoryImpl.java": [ -90 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/QualityProfile.java": [ -60 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/RuleActivatorContext.java": [ -46 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/rule/CachingRuleFinder.java": [ -172, -173 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/rule/DefaultRuleFinder.java": [ -143, -144 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/source/ws/LinesAction.java": [ -140 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/source/ws/ScmAction.java": [ -126 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/startup/RegisterPermissionTemplates.java": [ -78, -79 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/test/index/TestResultSetIterator.java": [ -81 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/ui/ws/ComponentAction.java": [ -185 -], -"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/usertoken/ws/SearchAction.java": [ -97 -] + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/JwtHttpHandler.java": [ + 134, + 155 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/JwtSerializer.java": [ + 92, + 93, + 132 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/SsoAuthenticator.java": [ + 152, + 155 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/batch/ProjectDataLoader.java": [ + 91 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/ce/ws/ActivityAction.java": [ + 142, + 145 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/ce/ws/TaskFormatter.java": [ + 91, + 128 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/component/ComponentUpdater.java": [ + 103 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java": [ + 213, + 221 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueAssigner.java": [ + 68 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculator.java": [ + 61 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycle.java": [ + 48 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/MovedIssueVisitor.java": [ + 60 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortCalculator.java": [ + 82 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java": [ + 63 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PeriodResolver.java": [ + 115, + 207 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistComponentsStep.java": [ + 372 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java": [ + 117, + 133 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/ValidateProjectStep.java": [ + 153, + 153 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/es/EsUtils.java": [ + 43 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/IssueQuery.java": [ + 200, + 205, + 210, + 392, + 397, + 402 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/IssueQueryFactory.java": [ + 41, + 42 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/index/IssueIndex.java": [ + 56, + 347 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java": [ + 202 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/AddCommentAction.java": [ + 98 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/AssignAction.java": [ + 121 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java": [ + 194 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java": [ + 99 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java": [ + 273 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java": [ + 108 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SetTagsAction.java": [ + 106 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SetTypeAction.java": [ + 108 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java": [ + 73, + 74 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java": [ + 135 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java": [ + 201, + 226 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/permission/ws/template/CreateTemplateAction.java": [ + 130 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/permission/ws/template/UpdateTemplateAction.java": [ + 136 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/ServerImpl.java": [ + 71 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/StartupMetadataPersister.java": [ + 52 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java": [ + 100 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java": [ + 57, + 58 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/plugins/UpdateCenterClient.java": [ + 99 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java": [ + 81 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/BuiltInQualityProfilesNotificationTemplate.java": [ + 66, + 68 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/QProfileFactoryImpl.java": [ + 90 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/QualityProfile.java": [ + 60 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/RuleActivatorContext.java": [ + 46 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/rule/CachingRuleFinder.java": [ + 172, + 173 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/rule/DefaultRuleFinder.java": [ + 143, + 144 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/source/ws/LinesAction.java": [ + 140 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/source/ws/ScmAction.java": [ + 126 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/startup/RegisterPermissionTemplates.java": [ + 78, + 79 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/test/index/TestResultSetIterator.java": [ + 81 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/ui/ws/ComponentAction.java": [ + 185 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/usertoken/ws/SearchAction.java": [ + 97 + ] } From a88de691150972db5f2a1d1ad28c94430410c6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Mon, 11 May 2026 14:56:22 +0200 Subject: [PATCH 05/15] refactor implementation to raise the issue on imports only --- .../sonar/java/checks/DateAndTimesCheck.java | 42 ++++++------------- .../test/files/checks/DateAndTimesCheck.java | 28 ++++++------- 2 files changed, 26 insertions(+), 44 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java index 272b678cd2d..1ca7815324e 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java @@ -19,37 +19,16 @@ import java.util.List; import org.sonar.check.Rule; import org.sonar.java.checks.helpers.ExpressionsHelper; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.JavaVersionAwareVisitor; -import org.sonar.java.checks.methods.AbstractMethodDetection; import org.sonar.plugins.java.api.JavaVersion; -import org.sonar.plugins.java.api.semantic.MethodMatchers; +import org.sonar.plugins.java.api.semantic.Sema; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.ImportTree; -import org.sonar.plugins.java.api.tree.MethodInvocationTree; -import org.sonar.plugins.java.api.tree.NewClassTree; import org.sonar.plugins.java.api.tree.Tree; @Rule(key = "S2143") -public class DateAndTimesCheck extends AbstractMethodDetection implements JavaVersionAwareVisitor { - - private static final MethodMatchers METHOD_MATCHERS = MethodMatchers.or( - MethodMatchers.create().ofTypes("java.util.Calendar").names("getInstance").withAnyParameters().build(), - MethodMatchers.create().ofTypes("java.util.Date").constructor().withAnyParameters().build()); - - @Override - protected MethodMatchers getMethodInvocationMatchers() { - return METHOD_MATCHERS; - } - - @Override - protected void onConstructorFound(NewClassTree newClassTree) { - reportIssue(newClassTree); - } - - @Override - protected void onMethodInvocationFound(MethodInvocationTree mit) { - reportIssue(mit); - } +public class DateAndTimesCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor { private void reportIssue(Tree tree) { reportIssue(tree, "Use the Java 8 Date and Time API instead." + context.getJavaVersion().java8CompatibilityMessage()); @@ -62,20 +41,25 @@ public boolean isCompatibleWithJavaVersion(JavaVersion version) { @Override public List nodesToVisit() { - List kinds = new java.util.ArrayList<>(super.nodesToVisit()); - kinds.add(Tree.Kind.IMPORT); - return kinds; + return List.of(Tree.Kind.IMPORT); } @Override public void visitNode(Tree tree) { if (tree instanceof ImportTree importTree) { String qualifiedName = ExpressionsHelper.concatenate((ExpressionTree) importTree.qualifiedIdentifier()); - if (qualifiedName.startsWith("org.joda.time.")) { + if (qualifiedName.startsWith("org.joda.time.") || isJavaUtilDateSubclass(qualifiedName) || isJavaUtilCalendarSubclass(qualifiedName)) { reportIssue(importTree); } } - super.visitNode(tree); + } + + private boolean isJavaUtilDateSubclass(String qualifiedName) { + return context.getSemanticModel() instanceof Sema semanticModel && semanticModel.getClassType(qualifiedName).isSubtypeOf("java.util.Date"); + } + + private boolean isJavaUtilCalendarSubclass(String qualifiedName) { + return context.getSemanticModel() instanceof Sema semanticModel && semanticModel.getClassType(qualifiedName).isSubtypeOf("java.util.Calendar"); } } diff --git a/java-checks/src/test/files/checks/DateAndTimesCheck.java b/java-checks/src/test/files/checks/DateAndTimesCheck.java index edd46c305e4..30a5aff9a06 100644 --- a/java-checks/src/test/files/checks/DateAndTimesCheck.java +++ b/java-checks/src/test/files/checks/DateAndTimesCheck.java @@ -1,22 +1,20 @@ -import java.util.Date; -import java.util.Locale; -import java.util.Calendar; +import java.util.Date; // Noncompliant {{Use the Java 8 Date and Time API instead.}} +import java.util.Calendar; // Noncompliant + +// java.util.Date and java.util.Calendar subclasses +import java.sql.Date; // Noncompliant +import java.util.GregorianCalendar; // Noncompliant + +import java.util.Locale; // Compliant (locale can be used in other contexts than date and time) + import org.joda.time.DateTime; // Noncompliant {{Use the Java 8 Date and Time API instead.}} import org.joda.time.*; // Noncompliant {{Use the Java 8 Date and Time API instead.}} -import java.time.LocalDateTime; + +import java.time.LocalDateTime; // Compliant class A { void javaUtil() { - Date now = new Date(); // Noncompliant {{Use the Java 8 Date and Time API instead.}} - // ^^^^^^^^^^ - now = new Date(1499159427440L); // Noncompliant - // ^^^^^^^^^^^^^^^^^^^^^^^^ - DateFormat df = new SimpleDateFormat("dd.MM.yyyy"); - - Calendar christmas = Calendar.getInstance(); // Noncompliant - // ^^^^^^^^^^^^^^^^^^^^^^ - christmas = Calendar.getInstance(Locale.CANADA); // Noncompliant - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - christmas.setTime(df.parse("25.12.2020")); + Date now = new Date(); + Calendar christmas = Calendar.getInstance(); } } From 4358715016d918fd87c831fd4de7b262bc0b384c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Mon, 11 May 2026 15:18:09 +0200 Subject: [PATCH 06/15] fix ruling test --- .../commons-beanutils/java-S2143.json | 56 ++++- .../resources/eclipse-jetty/java-S2143.json | 26 ++- .../resources/sonar-server/java-S2143.json | 191 +++++++++++------- 3 files changed, 182 insertions(+), 91 deletions(-) diff --git a/its/ruling/src/test/resources/commons-beanutils/java-S2143.json b/its/ruling/src/test/resources/commons-beanutils/java-S2143.json index 489244212c4..aa45cfdeece 100644 --- a/its/ruling/src/test/resources/commons-beanutils/java-S2143.json +++ b/its/ruling/src/test/resources/commons-beanutils/java-S2143.json @@ -1,11 +1,49 @@ { -"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java": [ -235, -376, -398, -400, -402, -404, -406 -] + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/ConvertUtilsBean.java": [ + 27, + 28 + ], + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/JDBCDynaClass.java": [ + 21, + 25, + 26 + ], + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/LazyDynaBean.java": [ + 24 + ], + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/CalendarConverter.java": [ + 19 + ], + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/DateConverter.java": [ + 19 + ], + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java": [ + 22, + 23 + ], + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/NumberConverter.java": [ + 25, + 26 + ], + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/SqlDateConverter.java": [ + 19 + ], + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/SqlTimeConverter.java": [ + 19 + ], + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/SqlTimestampConverter.java": [ + 19 + ], + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/SqlDateLocaleConverter.java": [ + 20 + ], + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/SqlTimeLocaleConverter.java": [ + 20 + ], + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/SqlTimestampLocaleConverter.java": [ + 20 + ], + "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/StringLocaleConverter.java": [ + 26 + ] } diff --git a/its/ruling/src/test/resources/eclipse-jetty/java-S2143.json b/its/ruling/src/test/resources/eclipse-jetty/java-S2143.json index f90710ae470..fa67646f42e 100644 --- a/its/ruling/src/test/resources/eclipse-jetty/java-S2143.json +++ b/its/ruling/src/test/resources/eclipse-jetty/java-S2143.json @@ -1,9 +1,21 @@ { -"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/RolloverFileOutputStream.java": [ -252, -273 -], -"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java": [ -681 -] + "org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java": [ + 21, + 22 + ], + "org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java": [ + 22 + ], + "org.eclipse.jetty:jetty-project:jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java": [ + 23 + ], + "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java": [ + 25 + ], + "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/RolloverFileOutputStream.java": [ + 29 + ], + "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java": [ + 39 + ] } diff --git a/its/ruling/src/test/resources/sonar-server/java-S2143.json b/its/ruling/src/test/resources/sonar-server/java-S2143.json index 03034df8398..c71bcd66b1e 100644 --- a/its/ruling/src/test/resources/sonar-server/java-S2143.json +++ b/its/ruling/src/test/resources/sonar-server/java-S2143.json @@ -1,188 +1,229 @@ { "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/JwtHttpHandler.java": [ - 134, - 155 + 25 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/JwtSerializer.java": [ - 92, - 93, - 132 + 31 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/SsoAuthenticator.java": [ - 152, - 155 + 25 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/batch/ProjectAction.java": [ + 22 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/batch/ProjectDataLoader.java": [ - 91 + 26 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/ce/ws/ActivityAction.java": [ - 142, - 145 + 27 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/ce/ws/TaskFormatter.java": [ - 91, - 128 + 25 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/component/ComponentUpdater.java": [ - 103 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java": [ - 213, - 221 + 24 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueAssigner.java": [ - 68 + 24 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculator.java": [ - 61 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycle.java": [ - 48 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/MovedIssueVisitor.java": [ - 60 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortCalculator.java": [ - 82 + 27, + 30 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java": [ - 63 + 24 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PeriodResolver.java": [ - 115, - 207 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistComponentsStep.java": [ - 372 + 24 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityProfileEventsStep.java": [ + 25 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java": [ - 117, - 133 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/ValidateProjectStep.java": [ - 153, - 153 + 25 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/es/BaseDoc.java": [ + 22 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/es/EsUtils.java": [ + 25, 43 ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/IssueFieldsSetter.java": [ + 26 + ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/IssueQuery.java": [ - 200, - 205, - 210, - 392, - 397, - 402 + 25 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/IssueQueryFactory.java": [ + 32, 41, 42 ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/IssuesFinderSort.java": [ + 25 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/index/IssueDoc.java": [ + 25 + ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/index/IssueIndex.java": [ - 56, - 347 + 27, + 56 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java": [ - 202 + 31 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/notification/AbstractNewIssuesEmailTemplate.java": [ + 25 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplate.java": [ + 28 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java": [ + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/AddCommentAction.java": [ - 98 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/AssignAction.java": [ - 121 + 24 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java": [ - 194 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java": [ - 99 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java": [ - 273 + 26 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java": [ - 108 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SetTagsAction.java": [ - 106 + 25 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SetTypeAction.java": [ - 108 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java": [ - 73, - 74 + 24 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/measure/index/ProjectMeasuresDoc.java": [ + 24 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java": [ - 135 + 23 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/measure/ws/SearchHistoryAction.java": [ + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java": [ - 201, - 226 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/permission/ws/template/CreateTemplateAction.java": [ - 130 + 22 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/permission/ws/template/UpdateTemplateAction.java": [ - 136 + 22 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/ServerImpl.java": [ - 71 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/StartupMetadataPersister.java": [ - 52 + 22 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java": [ - 100 + 22 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java": [ - 57, - 58 + 27 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/ws/IndexAction.java": [ + 22 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/plugins/UpdateCenterClient.java": [ - 99 + 27 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/project/ws/GhostsAction.java": [ + 23 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/project/ws/ProvisionedAction.java": [ + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java": [ - 81 + 27 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/BuiltInQualityProfilesNotificationTemplate.java": [ - 66, - 68 + 25 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/QProfileFactoryImpl.java": [ - 90 + 24 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/QualityProfile.java": [ - 60 + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/RuleActivatorContext.java": [ - 46 + 24 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/ws/ChangelogAction.java": [ + 22 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/rule/CachingRuleFinder.java": [ - 172, - 173 + 28 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/rule/DefaultRuleFinder.java": [ - 143, - 144 + 28 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/rule/Rule.java": [ + 28 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/rule/ws/RuleQueryFactory.java": [ + 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/source/ws/LinesAction.java": [ - 140 + 24 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/source/ws/ScmAction.java": [ - 126 + 24 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/startup/RegisterPermissionTemplates.java": [ - 78, - 79 + 22 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/test/index/TestResultSetIterator.java": [ - 81 + 30 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/ui/ws/ComponentAction.java": [ - 185 + 24 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/usertoken/ws/SearchAction.java": [ - 97 + 22 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/util/DateCollector.java": [ + 24 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/util/RubyUtils.java": [ + 30 + ], + "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/ws/JsonWriterUtils.java": [ + 23 ] } From 629334bc49cccb2dcbd761a49b3a2460ecf9ca4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Mon, 11 May 2026 15:29:01 +0200 Subject: [PATCH 07/15] fix ruling test for eclipse-jetty-similar-to-main --- .../eclipse-jetty-similar-to-main/java-S2143.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S2143.json diff --git a/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S2143.json b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S2143.json new file mode 100644 index 00000000000..1860250e7ad --- /dev/null +++ b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S2143.json @@ -0,0 +1,9 @@ +{ + "org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java": [ + 21, + 22 + ], + "org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java": [ + 22 + ] +} From b0430a0246a8a03873a8823832148da0a000a5bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Mon, 11 May 2026 16:51:40 +0200 Subject: [PATCH 08/15] flag static imports and remove logic duplication --- .../org/sonar/java/checks/DateAndTimesCheck.java | 12 ++++++------ .../src/test/files/checks/DateAndTimesCheck.java | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java index 1ca7815324e..ca37130160b 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java @@ -48,18 +48,18 @@ public List nodesToVisit() { public void visitNode(Tree tree) { if (tree instanceof ImportTree importTree) { String qualifiedName = ExpressionsHelper.concatenate((ExpressionTree) importTree.qualifiedIdentifier()); - if (qualifiedName.startsWith("org.joda.time.") || isJavaUtilDateSubclass(qualifiedName) || isJavaUtilCalendarSubclass(qualifiedName)) { + if (qualifiedName.startsWith("org.joda.time.") + || isSubclassOrStaticMethodOf(qualifiedName, "java.util.Date") + || isSubclassOrStaticMethodOf(qualifiedName, "java.util.Calendar")) { reportIssue(importTree); } } } - private boolean isJavaUtilDateSubclass(String qualifiedName) { - return context.getSemanticModel() instanceof Sema semanticModel && semanticModel.getClassType(qualifiedName).isSubtypeOf("java.util.Date"); + private boolean isSubclassOrStaticMethodOf(String qualifiedName, String superclass) { + return qualifiedName.startsWith(superclass) + || (context.getSemanticModel() instanceof Sema semanticModel && semanticModel.getClassType(qualifiedName).isSubtypeOf(superclass)); } - private boolean isJavaUtilCalendarSubclass(String qualifiedName) { - return context.getSemanticModel() instanceof Sema semanticModel && semanticModel.getClassType(qualifiedName).isSubtypeOf("java.util.Calendar"); - } } diff --git a/java-checks/src/test/files/checks/DateAndTimesCheck.java b/java-checks/src/test/files/checks/DateAndTimesCheck.java index 30a5aff9a06..7857fbc1960 100644 --- a/java-checks/src/test/files/checks/DateAndTimesCheck.java +++ b/java-checks/src/test/files/checks/DateAndTimesCheck.java @@ -11,10 +11,14 @@ import org.joda.time.*; // Noncompliant {{Use the Java 8 Date and Time API instead.}} import java.time.LocalDateTime; // Compliant +import java.time.Instant; // Compliant + +import static java.util.Date.from; // Noncompliant class A { void javaUtil() { Date now = new Date(); Calendar christmas = Calendar.getInstance(); + Date epochDate = from(Instant.EPOCH); } } From fb86a425f6351363a54ce34c69e6c15abb06cf16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Mon, 11 May 2026 17:55:15 +0200 Subject: [PATCH 09/15] flag some of the usages of Date and Calendar (as they were flagged before) --- .../sonar/java/checks/DateAndTimesCheck.java | 43 +++++++++++++++++-- .../test/files/checks/DateAndTimesCheck.java | 6 ++- .../DateAndTimesCheckWildcardImport.java | 11 +++++ .../java/checks/DateAndTimesCheckTest.java | 9 ++++ 4 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java diff --git a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java index ca37130160b..b7eb745d717 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java @@ -19,16 +19,33 @@ import java.util.List; import org.sonar.check.Rule; import org.sonar.java.checks.helpers.ExpressionsHelper; -import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.java.checks.methods.AbstractMethodDetection; import org.sonar.plugins.java.api.JavaVersionAwareVisitor; import org.sonar.plugins.java.api.JavaVersion; +import org.sonar.plugins.java.api.semantic.MethodMatchers; import org.sonar.plugins.java.api.semantic.Sema; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.ImportTree; +import org.sonar.plugins.java.api.tree.MethodInvocationTree; +import org.sonar.plugins.java.api.tree.NewClassTree; import org.sonar.plugins.java.api.tree.Tree; @Rule(key = "S2143") -public class DateAndTimesCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor { +public class DateAndTimesCheck extends AbstractMethodDetection implements JavaVersionAwareVisitor { + + private static final MethodMatchers CALENDAR_GET_INSTANCE = + MethodMatchers.create() + .ofSubTypes("java.util.Calendar") + .names("getInstance") + .withAnyParameters() + .build(); + + private static final MethodMatchers DATE_CONSTRUCTOR = + MethodMatchers.create() + .ofSubTypes("java.util.Date") + .constructor() + .withAnyParameters() + .build(); private void reportIssue(Tree tree) { reportIssue(tree, "Use the Java 8 Date and Time API instead." + context.getJavaVersion().java8CompatibilityMessage()); @@ -41,7 +58,9 @@ public boolean isCompatibleWithJavaVersion(JavaVersion version) { @Override public List nodesToVisit() { - return List.of(Tree.Kind.IMPORT); + List nodes = new java.util.ArrayList<>(super.nodesToVisit()); + nodes.add(Tree.Kind.IMPORT); + return nodes; } @Override @@ -54,10 +73,26 @@ public void visitNode(Tree tree) { reportIssue(importTree); } } + super.visitNode(tree); + } + + @Override + protected MethodMatchers getMethodInvocationMatchers() { + return MethodMatchers.or(CALENDAR_GET_INSTANCE, DATE_CONSTRUCTOR); + } + + @Override + protected void onConstructorFound(NewClassTree newClassTree) { + reportIssue(newClassTree); + } + + @Override + protected void onMethodInvocationFound(MethodInvocationTree mit) { + reportIssue(mit); } private boolean isSubclassOrStaticMethodOf(String qualifiedName, String superclass) { - return qualifiedName.startsWith(superclass) + return qualifiedName.startsWith(superclass + ".") || (context.getSemanticModel() instanceof Sema semanticModel && semanticModel.getClassType(qualifiedName).isSubtypeOf(superclass)); } diff --git a/java-checks/src/test/files/checks/DateAndTimesCheck.java b/java-checks/src/test/files/checks/DateAndTimesCheck.java index 7857fbc1960..6ced0c2122c 100644 --- a/java-checks/src/test/files/checks/DateAndTimesCheck.java +++ b/java-checks/src/test/files/checks/DateAndTimesCheck.java @@ -17,8 +17,10 @@ class A { void javaUtil() { - Date now = new Date(); - Calendar christmas = Calendar.getInstance(); + Date now = new Date(); // Noncompliant + // ^^^^^^^^^^ + Calendar christmas = Calendar.getInstance(); // Noncompliant + // ^^^^^^^^^^^^^^^^^^^^^^ Date epochDate = from(Instant.EPOCH); } } diff --git a/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java b/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java new file mode 100644 index 00000000000..f9afa1b34e8 --- /dev/null +++ b/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java @@ -0,0 +1,11 @@ +import java.util.*; + +class A { + void javaUtil() { + Date now = new Date(); // Noncompliant + // ^^^^^^^^^^ + Calendar christmas = Calendar.getInstance(); // Noncompliant + // ^^^^^^^^^^^^^^^^^^^^^^ + Timestamp timestamp = new Timestamp(1735689600000L); // Compliant, the rule ignores this case + } +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/DateAndTimesCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/DateAndTimesCheckTest.java index 66fde657a13..f9c4e94ded8 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/DateAndTimesCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/DateAndTimesCheckTest.java @@ -30,4 +30,13 @@ void test() { .verifyIssues(); } + @Test + void test_wildcard_import() { + CheckVerifier.newVerifier() + .onFile("src/test/files/checks/DateAndTimesCheckWildcardImport.java") + .withCheck(new DateAndTimesCheck()) + .withJavaVersion(8) + .verifyIssues(); + } + } From b6bd7ad4a07484bbc27d33af81393603f22283de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Tue, 12 May 2026 08:59:19 +0200 Subject: [PATCH 10/15] add example with calendar subclass --- .../src/test/files/checks/DateAndTimesCheckWildcardImport.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java b/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java index f9afa1b34e8..048a84b3e13 100644 --- a/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java +++ b/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java @@ -7,5 +7,7 @@ void javaUtil() { Calendar christmas = Calendar.getInstance(); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^ Timestamp timestamp = new Timestamp(1735689600000L); // Compliant, the rule ignores this case + GregorianCalendar calendar = GregorianCalendar.getInstance(); // Noncompliant {{Use the Java 8 Date and Time API instead.}} + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ } } From 5bb5499b58897311b0e25635965874d81abe0442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Tue, 12 May 2026 09:05:16 +0200 Subject: [PATCH 11/15] fix ruling test --- .../commons-beanutils/java-S2143.json | 21 ++- .../resources/eclipse-jetty/java-S2143.json | 7 +- .../resources/sonar-server/java-S2143.json | 178 +++++++++++++----- 3 files changed, 148 insertions(+), 58 deletions(-) diff --git a/its/ruling/src/test/resources/commons-beanutils/java-S2143.json b/its/ruling/src/test/resources/commons-beanutils/java-S2143.json index aa45cfdeece..3d5a2818895 100644 --- a/its/ruling/src/test/resources/commons-beanutils/java-S2143.json +++ b/its/ruling/src/test/resources/commons-beanutils/java-S2143.json @@ -19,7 +19,17 @@ ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java": [ 22, - 23 + 23, + 235, + 376, + 381, + 386, + 391, + 398, + 400, + 402, + 404, + 406 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/NumberConverter.java": [ 25, @@ -35,13 +45,16 @@ 19 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/SqlDateLocaleConverter.java": [ - 20 + 20, + 219 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/SqlTimeLocaleConverter.java": [ - 20 + 20, + 218 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/SqlTimestampLocaleConverter.java": [ - 20 + 20, + 217 ], "commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/StringLocaleConverter.java": [ 26 diff --git a/its/ruling/src/test/resources/eclipse-jetty/java-S2143.json b/its/ruling/src/test/resources/eclipse-jetty/java-S2143.json index fa67646f42e..1dd0506d147 100644 --- a/its/ruling/src/test/resources/eclipse-jetty/java-S2143.json +++ b/its/ruling/src/test/resources/eclipse-jetty/java-S2143.json @@ -13,9 +13,12 @@ 25 ], "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/RolloverFileOutputStream.java": [ - 29 + 29, + 252, + 273 ], "org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java": [ - 39 + 39, + 681 ] } diff --git a/its/ruling/src/test/resources/sonar-server/java-S2143.json b/its/ruling/src/test/resources/sonar-server/java-S2143.json index c71bcd66b1e..e301d6d4ef9 100644 --- a/its/ruling/src/test/resources/sonar-server/java-S2143.json +++ b/its/ruling/src/test/resources/sonar-server/java-S2143.json @@ -1,64 +1,92 @@ { "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/JwtHttpHandler.java": [ - 25 + 25, + 134, + 155 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/JwtSerializer.java": [ - 31 + 31, + 92, + 93, + 132 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/authentication/SsoAuthenticator.java": [ - 25 + 25, + 152, + 155 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/batch/ProjectAction.java": [ 22 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/batch/ProjectDataLoader.java": [ - 26 + 26, + 91 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/ce/ws/ActivityAction.java": [ - 27 + 27, + 142, + 145 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/ce/ws/TaskFormatter.java": [ - 25 + 25, + 91, + 128 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/component/ComponentUpdater.java": [ - 23 + 23, + 103 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java": [ - 24 + 24, + 213, + 221 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueAssigner.java": [ - 24 + 24, + 68 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculator.java": [ - 23 + 23, + 61 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueLifecycle.java": [ - 23 + 23, + 48 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/MovedIssueVisitor.java": [ - 23 + 23, + 60 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortCalculator.java": [ 27, - 30 + 30, + 82 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java": [ - 24 + 24, + 63 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PeriodResolver.java": [ - 23 + 23, + 115, + 207 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistComponentsStep.java": [ - 24 + 24, + 372 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityProfileEventsStep.java": [ 25 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java": [ - 23 + 23, + 117, + 133 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/ValidateProjectStep.java": [ - 25 + 25, + 153, + 153 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/es/BaseDoc.java": [ 22 @@ -71,7 +99,13 @@ 26 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/IssueQuery.java": [ - 25 + 25, + 200, + 205, + 210, + 392, + 397, + 402 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/IssueQueryFactory.java": [ 32, @@ -86,10 +120,12 @@ ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/index/IssueIndex.java": [ 27, - 56 + 56, + 347 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java": [ - 31 + 31, + 202 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/notification/AbstractNewIssuesEmailTemplate.java": [ 25 @@ -101,67 +137,88 @@ 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/AddCommentAction.java": [ - 23 + 23, + 98 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/AssignAction.java": [ - 24 + 24, + 121 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java": [ - 23 + 23, + 194 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java": [ - 23 + 23, + 99 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java": [ - 26 + 26, + 273 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java": [ - 23 + 23, + 108 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SetTagsAction.java": [ - 25 + 25, + 106 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/issue/ws/SetTypeAction.java": [ - 23 + 23, + 108 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java": [ - 24 + 24, + 73, + 74 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/measure/index/ProjectMeasuresDoc.java": [ 24 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java": [ - 23 + 23, + 135 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/measure/ws/SearchHistoryAction.java": [ 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java": [ - 23 + 23, + 201, + 226 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/permission/ws/template/CreateTemplateAction.java": [ - 22 + 22, + 130 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/permission/ws/template/UpdateTemplateAction.java": [ - 22 + 22, + 136 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/ServerImpl.java": [ - 23 + 23, + 71 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/StartupMetadataPersister.java": [ - 22 + 22, + 52 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java": [ - 22 + 22, + 100 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java": [ - 27 + 27, + 57, + 58 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/platform/ws/IndexAction.java": [ 22 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/plugins/UpdateCenterClient.java": [ - 27 + 27, + 99 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/project/ws/GhostsAction.java": [ 23 @@ -170,28 +227,38 @@ 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java": [ - 27 + 27, + 81 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/BuiltInQualityProfilesNotificationTemplate.java": [ - 25 + 25, + 66, + 68 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/QProfileFactoryImpl.java": [ - 24 + 24, + 90 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/QualityProfile.java": [ - 23 + 23, + 60 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/RuleActivatorContext.java": [ - 24 + 24, + 46 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/ws/ChangelogAction.java": [ 22 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/rule/CachingRuleFinder.java": [ - 28 + 28, + 172, + 173 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/rule/DefaultRuleFinder.java": [ - 28 + 28, + 143, + 144 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/rule/Rule.java": [ 28 @@ -200,22 +267,29 @@ 23 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/source/ws/LinesAction.java": [ - 24 + 24, + 140 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/source/ws/ScmAction.java": [ - 24 + 24, + 126 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/startup/RegisterPermissionTemplates.java": [ - 22 + 22, + 78, + 79 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/test/index/TestResultSetIterator.java": [ - 30 + 30, + 81 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/ui/ws/ComponentAction.java": [ - 24 + 24, + 185 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/usertoken/ws/SearchAction.java": [ - 22 + 22, + 97 ], "org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/util/DateCollector.java": [ 24 From c9877657c5dcd327f297aa1fdd60ea37fa4891b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Tue, 12 May 2026 10:13:17 +0200 Subject: [PATCH 12/15] improve check samples --- .../java/org/sonar/java/checks/DateAndTimesCheck.java | 3 ++- java-checks/src/test/files/checks/DateAndTimesCheck.java | 2 ++ .../files/checks/DateAndTimesCheckWildcardImport.java | 9 ++++----- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java index b7eb745d717..d81595370b3 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java @@ -16,6 +16,7 @@ */ package org.sonar.java.checks; +import java.util.ArrayList; import java.util.List; import org.sonar.check.Rule; import org.sonar.java.checks.helpers.ExpressionsHelper; @@ -58,7 +59,7 @@ public boolean isCompatibleWithJavaVersion(JavaVersion version) { @Override public List nodesToVisit() { - List nodes = new java.util.ArrayList<>(super.nodesToVisit()); + List nodes = new ArrayList<>(super.nodesToVisit()); nodes.add(Tree.Kind.IMPORT); return nodes; } diff --git a/java-checks/src/test/files/checks/DateAndTimesCheck.java b/java-checks/src/test/files/checks/DateAndTimesCheck.java index 6ced0c2122c..8a44c1120a5 100644 --- a/java-checks/src/test/files/checks/DateAndTimesCheck.java +++ b/java-checks/src/test/files/checks/DateAndTimesCheck.java @@ -14,11 +14,13 @@ import java.time.Instant; // Compliant import static java.util.Date.from; // Noncompliant +import static java.sql.Date.from; // Compliant (limitation) class A { void javaUtil() { Date now = new Date(); // Noncompliant // ^^^^^^^^^^ + Date date = new Date(1499159427440L); // Noncompliant Calendar christmas = Calendar.getInstance(); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^ Date epochDate = from(Instant.EPOCH); diff --git a/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java b/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java index 048a84b3e13..0295992743e 100644 --- a/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java +++ b/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java @@ -1,13 +1,12 @@ -import java.util.*; +import java.util.*; // Compliant, java.util.* is not directly flagged by the rule class A { void javaUtil() { - Date now = new Date(); // Noncompliant + Date now = new Date(); // Noncompliant {{Use the Java 8 Date and Time API instead.}} // ^^^^^^^^^^ Calendar christmas = Calendar.getInstance(); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^ - Timestamp timestamp = new Timestamp(1735689600000L); // Compliant, the rule ignores this case - GregorianCalendar calendar = GregorianCalendar.getInstance(); // Noncompliant {{Use the Java 8 Date and Time API instead.}} - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Calendar gregorianCalendar = GregorianCalendar.getInstance(); // Noncompliant + Timestamp timestamp = new Timestamp(1735689600000L); // Compliant, the rule ignores this case (limitation) } } From 166b7caa9cea69608e9d9053ee045f17afe64a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Tue, 12 May 2026 10:32:06 +0200 Subject: [PATCH 13/15] cleaner handling of static imports --- .../org/sonar/java/checks/DateAndTimesCheck.java | 12 ++++++------ .../src/test/files/checks/DateAndTimesCheck.java | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java index d81595370b3..e15b435f452 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java @@ -67,10 +67,11 @@ public List nodesToVisit() { @Override public void visitNode(Tree tree) { if (tree instanceof ImportTree importTree) { - String qualifiedName = ExpressionsHelper.concatenate((ExpressionTree) importTree.qualifiedIdentifier()); + String concatenatedName = ExpressionsHelper.concatenate((ExpressionTree) importTree.qualifiedIdentifier()); + String qualifiedName = importTree.isStatic() ? concatenatedName.substring(0, concatenatedName.lastIndexOf('.')) : concatenatedName; if (qualifiedName.startsWith("org.joda.time.") - || isSubclassOrStaticMethodOf(qualifiedName, "java.util.Date") - || isSubclassOrStaticMethodOf(qualifiedName, "java.util.Calendar")) { + || isSubclassOf(qualifiedName, "java.util.Date") + || isSubclassOf(qualifiedName, "java.util.Calendar")) { reportIssue(importTree); } } @@ -92,9 +93,8 @@ protected void onMethodInvocationFound(MethodInvocationTree mit) { reportIssue(mit); } - private boolean isSubclassOrStaticMethodOf(String qualifiedName, String superclass) { - return qualifiedName.startsWith(superclass + ".") - || (context.getSemanticModel() instanceof Sema semanticModel && semanticModel.getClassType(qualifiedName).isSubtypeOf(superclass)); + private boolean isSubclassOf(String qualifiedName, String superclass) { + return context.getSemanticModel() instanceof Sema semanticModel && semanticModel.getClassType(qualifiedName).isSubtypeOf(superclass); } diff --git a/java-checks/src/test/files/checks/DateAndTimesCheck.java b/java-checks/src/test/files/checks/DateAndTimesCheck.java index 8a44c1120a5..4a08ca99f41 100644 --- a/java-checks/src/test/files/checks/DateAndTimesCheck.java +++ b/java-checks/src/test/files/checks/DateAndTimesCheck.java @@ -14,7 +14,8 @@ import java.time.Instant; // Compliant import static java.util.Date.from; // Noncompliant -import static java.sql.Date.from; // Compliant (limitation) +import static java.sql.Date.from; // Noncompliant +import static org.joda.time.Minutes.minutesBetween; // Noncompliant class A { void javaUtil() { From 8f38888be18f5f9205cda55811e463f5492b2e57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Tue, 12 May 2026 12:48:12 +0200 Subject: [PATCH 14/15] explicitly add test without semantic --- .../checks/DateAndTimesCheckNoSemantic.java | 29 +++++++++++++++++++ .../java/checks/DateAndTimesCheckTest.java | 10 +++++++ 2 files changed, 39 insertions(+) create mode 100644 java-checks/src/test/files/checks/DateAndTimesCheckNoSemantic.java diff --git a/java-checks/src/test/files/checks/DateAndTimesCheckNoSemantic.java b/java-checks/src/test/files/checks/DateAndTimesCheckNoSemantic.java new file mode 100644 index 00000000000..ef24e04721f --- /dev/null +++ b/java-checks/src/test/files/checks/DateAndTimesCheckNoSemantic.java @@ -0,0 +1,29 @@ +import java.util.Date; +import java.util.Calendar; + +// java.util.Date and java.util.Calendar subclasses +import java.sql.Date; +import java.util.GregorianCalendar; + +import java.util.Locale; + +import org.joda.time.DateTime; // Noncompliant {{Use the Java 8 Date and Time API instead.}} +import org.joda.time.*; // Noncompliant {{Use the Java 8 Date and Time API instead.}} + +import java.time.LocalDateTime; +import java.time.Instant; + +import static java.util.Date.from; +import static java.sql.Date.from; +import static org.joda.time.Minutes.minutesBetween; // Noncompliant + +class A { + void javaUtil() { + Date now = new Date(); // Noncompliant + // ^^^^^^^^^^ + Date date = new Date(1499159427440L); // Noncompliant + Calendar christmas = Calendar.getInstance(); // Noncompliant + // ^^^^^^^^^^^^^^^^^^^^^^ + Date epochDate = from(Instant.EPOCH); + } +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/DateAndTimesCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/DateAndTimesCheckTest.java index f9c4e94ded8..c5a90508960 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/DateAndTimesCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/DateAndTimesCheckTest.java @@ -39,4 +39,14 @@ void test_wildcard_import() { .verifyIssues(); } + @Test + void test_without_semantic() { + CheckVerifier.newVerifier() + .onFile("src/test/files/checks/DateAndTimesCheckNoSemantic.java") + .withCheck(new DateAndTimesCheck()) + .withJavaVersion(8) + .withoutSemantic() + .verifyIssues(); + } + } From 491c8632d27141311fd5d6823a7d9f3fe49f266e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mie=20B=C3=A9nard?= Date: Tue, 12 May 2026 14:10:46 +0200 Subject: [PATCH 15/15] modify the check to use string parsing instead of semantic model --- .../sonar/java/checks/DateAndTimesCheck.java | 16 +++++----- .../checks/DateAndTimesCheckNoSemantic.java | 29 ------------------- .../DateAndTimesCheckWildcardImport.java | 2 +- .../java/checks/DateAndTimesCheckTest.java | 2 +- 4 files changed, 10 insertions(+), 39 deletions(-) delete mode 100644 java-checks/src/test/files/checks/DateAndTimesCheckNoSemantic.java diff --git a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java index e15b435f452..c6aeb01d644 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DateAndTimesCheck.java @@ -18,13 +18,13 @@ import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.sonar.check.Rule; import org.sonar.java.checks.helpers.ExpressionsHelper; import org.sonar.java.checks.methods.AbstractMethodDetection; import org.sonar.plugins.java.api.JavaVersionAwareVisitor; import org.sonar.plugins.java.api.JavaVersion; import org.sonar.plugins.java.api.semantic.MethodMatchers; -import org.sonar.plugins.java.api.semantic.Sema; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.ImportTree; import org.sonar.plugins.java.api.tree.MethodInvocationTree; @@ -48,6 +48,12 @@ public class DateAndTimesCheck extends AbstractMethodDetection implements JavaVe .withAnyParameters() .build(); + private static final Set KNOWN_JAVA_UTIL_DATE_TIME_CLASSES = Set.of( + "java.util.Date", "java.util.Calendar", + "java.sql.Date", "java.sql.Time", "java.sql.Timestamp", + "java.util.GregorianCalendar" + ); + private void reportIssue(Tree tree) { reportIssue(tree, "Use the Java 8 Date and Time API instead." + context.getJavaVersion().java8CompatibilityMessage()); } @@ -70,8 +76,7 @@ public void visitNode(Tree tree) { String concatenatedName = ExpressionsHelper.concatenate((ExpressionTree) importTree.qualifiedIdentifier()); String qualifiedName = importTree.isStatic() ? concatenatedName.substring(0, concatenatedName.lastIndexOf('.')) : concatenatedName; if (qualifiedName.startsWith("org.joda.time.") - || isSubclassOf(qualifiedName, "java.util.Date") - || isSubclassOf(qualifiedName, "java.util.Calendar")) { + || KNOWN_JAVA_UTIL_DATE_TIME_CLASSES.stream().anyMatch(qualifiedName::startsWith)){ reportIssue(importTree); } } @@ -93,9 +98,4 @@ protected void onMethodInvocationFound(MethodInvocationTree mit) { reportIssue(mit); } - private boolean isSubclassOf(String qualifiedName, String superclass) { - return context.getSemanticModel() instanceof Sema semanticModel && semanticModel.getClassType(qualifiedName).isSubtypeOf(superclass); - } - - } diff --git a/java-checks/src/test/files/checks/DateAndTimesCheckNoSemantic.java b/java-checks/src/test/files/checks/DateAndTimesCheckNoSemantic.java deleted file mode 100644 index ef24e04721f..00000000000 --- a/java-checks/src/test/files/checks/DateAndTimesCheckNoSemantic.java +++ /dev/null @@ -1,29 +0,0 @@ -import java.util.Date; -import java.util.Calendar; - -// java.util.Date and java.util.Calendar subclasses -import java.sql.Date; -import java.util.GregorianCalendar; - -import java.util.Locale; - -import org.joda.time.DateTime; // Noncompliant {{Use the Java 8 Date and Time API instead.}} -import org.joda.time.*; // Noncompliant {{Use the Java 8 Date and Time API instead.}} - -import java.time.LocalDateTime; -import java.time.Instant; - -import static java.util.Date.from; -import static java.sql.Date.from; -import static org.joda.time.Minutes.minutesBetween; // Noncompliant - -class A { - void javaUtil() { - Date now = new Date(); // Noncompliant - // ^^^^^^^^^^ - Date date = new Date(1499159427440L); // Noncompliant - Calendar christmas = Calendar.getInstance(); // Noncompliant - // ^^^^^^^^^^^^^^^^^^^^^^ - Date epochDate = from(Instant.EPOCH); - } -} diff --git a/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java b/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java index 0295992743e..5ad4ceb7547 100644 --- a/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java +++ b/java-checks/src/test/files/checks/DateAndTimesCheckWildcardImport.java @@ -7,6 +7,6 @@ void javaUtil() { Calendar christmas = Calendar.getInstance(); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^ Calendar gregorianCalendar = GregorianCalendar.getInstance(); // Noncompliant - Timestamp timestamp = new Timestamp(1735689600000L); // Compliant, the rule ignores this case (limitation) + Timestamp timestamp = new Timestamp(1735689600000L); // Compliant, the rule ignores this case (known limitation) } } diff --git a/java-checks/src/test/java/org/sonar/java/checks/DateAndTimesCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/DateAndTimesCheckTest.java index c5a90508960..700e731b5b6 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/DateAndTimesCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/DateAndTimesCheckTest.java @@ -42,7 +42,7 @@ void test_wildcard_import() { @Test void test_without_semantic() { CheckVerifier.newVerifier() - .onFile("src/test/files/checks/DateAndTimesCheckNoSemantic.java") + .onFile("src/test/files/checks/DateAndTimesCheck.java") .withCheck(new DateAndTimesCheck()) .withJavaVersion(8) .withoutSemantic()