From 31a4022bd3c74b896f9288c63cc2602ff593dfb5 Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Wed, 11 Feb 2026 13:55:28 +0100 Subject: [PATCH 01/10] Refactor WildcardImportsShouldNotBeUsedCheck and add ExpressionsHelper tests Replace custom fullQualifiedName method with ExpressionsHelper.concatenate for better code reuse. Add comprehensive parameterized tests for the concatenate method covering all import types including module imports. Co-Authored-By: Claude Sonnet 4.5 --- .../checks/helpers/ExpressionsHelperTest.java | 31 +++++++++++++++++++ .../WildcardImportsShouldNotBeUsedCheck.java | 17 +++------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ExpressionsHelperTest.java b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ExpressionsHelperTest.java index e42975e3fb3..c2b1fed85e6 100644 --- a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ExpressionsHelperTest.java +++ b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/ExpressionsHelperTest.java @@ -19,10 +19,14 @@ import java.lang.reflect.Constructor; import javax.annotation.Nullable; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.ExpressionStatementTree; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.IdentifierTree; +import org.sonar.plugins.java.api.tree.ImportTree; import org.sonar.plugins.java.api.tree.MethodInvocationTree; import org.sonar.plugins.java.api.tree.MethodTree; @@ -40,6 +44,33 @@ void private_constructor() throws Exception { constructor.newInstance(); } + @Test + void concatenate_null() { + assertThat(ExpressionsHelper.concatenate(null)).isEmpty(); + } + + @ParameterizedTest + @CsvSource({ + "import A;, A", + "import java.util.List;, java.util.List", + "import java.util.regex.Pattern;, java.util.regex.Pattern", + "import java.util.*;, java.util.*", + "import static java.util.Collections.emptyList;, java.util.Collections.emptyList", + "import static java.util.Collections.*;, java.util.Collections.*", + "import module java.base;, java.base" + }) + void concatenate(String importStatement, String expected) { + String code = importStatement + "\nclass C {}"; + ExpressionTree qualifiedId = getImportQualifiedIdentifier(code, 0); + assertThat(ExpressionsHelper.concatenate(qualifiedId)).isEqualTo(expected); + } + + private ExpressionTree getImportQualifiedIdentifier(String code, int importIndex) { + CompilationUnitTree compilationUnit = parse(code); + ImportTree importTree = (ImportTree) compilationUnit.imports().get(importIndex); + return (ExpressionTree) importTree.qualifiedIdentifier(); + } + @Test void simpleAssignment() { String code = newCode( "int foo() {", diff --git a/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java b/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java index f837763f25a..0ccdfb16fd8 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java @@ -17,10 +17,10 @@ package org.sonar.java.checks; 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.tree.IdentifierTree; +import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.ImportTree; -import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.Tree.Kind; @@ -40,18 +40,9 @@ public void visitNode(Tree tree) { ImportTree importTree = (ImportTree) tree; // See RSPEC-2208 : exception with static imports. - if (fullQualifiedName(importTree.qualifiedIdentifier()).endsWith(".*") && !importTree.isStatic()) { + String qualifiedName = ExpressionsHelper.concatenate((ExpressionTree) importTree.qualifiedIdentifier()); + if (qualifiedName.endsWith(".*") && !importTree.isStatic()) { reportIssue(importTree.qualifiedIdentifier(), "Explicitly import the specific classes needed."); } } - - private static String fullQualifiedName(Tree tree) { - if (tree.is(Tree.Kind.IDENTIFIER)) { - return ((IdentifierTree) tree).name(); - } else if (tree.is(Tree.Kind.MEMBER_SELECT)) { - MemberSelectExpressionTree m = (MemberSelectExpressionTree) tree; - return fullQualifiedName(m.expression()) + "." + m.identifier().name(); - } - throw new UnsupportedOperationException(String.format("Kind/Class '%s' not supported", tree.getClass())); - } } From aa144b5efba8d8b9daa935bb7c744c71eb8f28a1 Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Wed, 11 Feb 2026 14:22:38 +0100 Subject: [PATCH 02/10] Restrict WildcardImportsShouldNotBeUsedCheck to Java 24 and earlier Add JavaVersionAwareVisitor implementation to S2208 to restrict it to Java 24 and earlier. Java 25's import organization strategy (S8445) allows wildcard imports when properly grouped by specificity, making this restriction incompatible with Java 25+. Co-Authored-By: Claude Sonnet 4.5 --- .../java/checks/WildcardImportsShouldNotBeUsedCheck.java | 9 ++++++++- .../checks/WildcardImportsShouldNotBeUsedCheckTest.java | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java b/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java index 0ccdfb16fd8..091d0f9cb4d 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java @@ -19,6 +19,8 @@ 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.JavaVersion; +import org.sonar.plugins.java.api.JavaVersionAwareVisitor; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.ImportTree; import org.sonar.plugins.java.api.tree.Tree; @@ -28,7 +30,12 @@ import java.util.List; @Rule(key = "S2208") -public class WildcardImportsShouldNotBeUsedCheck extends IssuableSubscriptionVisitor { +public class WildcardImportsShouldNotBeUsedCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor { + + @Override + public boolean isCompatibleWithJavaVersion(JavaVersion version) { + return !version.isJava25Compatible(); + } @Override public List nodesToVisit() { diff --git a/java-checks/src/test/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheckTest.java index 1cb5454d782..cb65cb75d92 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheckTest.java @@ -31,4 +31,13 @@ void test() { .verifyIssues(); } + @Test + void no_issue_on_java_25() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/WildcardImportsShouldNotBeUsedCheck.java")) + .withCheck(new WildcardImportsShouldNotBeUsedCheck()) + .withJavaVersion(25) + .verifyNoIssues(); + } + } From f7bccda93d322fc2b0af9b1a5a05a4cd820bcf90 Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Thu, 12 Feb 2026 12:53:17 +0100 Subject: [PATCH 03/10] Implement S8445 ImportDeclarationOrderCheck for Java 25 Add new rule to enforce proper grouping of import declarations by specificity. Import order should be: module imports, on-demand package imports, single-type imports, followed by static imports in the same order. Rule only applies to Java 25+ projects. Key features: - Classifies imports using ImportTree.isModule(), isStatic() and ExpressionsHelper.concatenate() for wildcard detection - Reports ordering violations with clear descriptive messages - Uses SENTINEL_IMPORT enum value to simplify initialization logic - Properly cleans up imports list in leaveNode() Co-Authored-By: Claude Sonnet 4.5 --- .../checks/ImportDeclarationOrderCheck.java | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 java-checks/src/main/java/org/sonar/java/checks/ImportDeclarationOrderCheck.java diff --git a/java-checks/src/main/java/org/sonar/java/checks/ImportDeclarationOrderCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ImportDeclarationOrderCheck.java new file mode 100644 index 00000000000..85f5aa5ba95 --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/ImportDeclarationOrderCheck.java @@ -0,0 +1,127 @@ +/* + * SonarQube Java + * Copyright (C) 2012-2025 SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import java.util.ArrayList; +import java.util.Collections; +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.JavaVersion; +import org.sonar.plugins.java.api.JavaVersionAwareVisitor; +import org.sonar.plugins.java.api.tree.CompilationUnitTree; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.ImportTree; +import org.sonar.plugins.java.api.tree.Tree; + +@Rule(key = "S8445") +public class ImportDeclarationOrderCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor { + + private final List imports = new ArrayList<>(); + + @Override + public boolean isCompatibleWithJavaVersion(JavaVersion version) { + return version.isJava25Compatible(); + } + + @Override + public List nodesToVisit() { + return Collections.singletonList(Tree.Kind.COMPILATION_UNIT); + } + + @Override + public void visitNode(Tree tree) { + CompilationUnitTree compilationUnit = (CompilationUnitTree) tree; + + // Collect all import statements + compilationUnit.imports() + .stream() + .filter(importTree -> importTree.is(Tree.Kind.IMPORT)) + .map(ImportTree.class::cast) + .forEach(imports::add); + + analyzeImportOrder(); + } + + @Override + public void leaveNode(Tree tree) { + imports.clear(); + } + + private void analyzeImportOrder() { + if (imports.size() <= 1) { + return; + } + + ImportType previousType = ImportType.SENTINEL_IMPORT; + for (ImportTree importTree : imports) { + ImportType currentType = classifyImport(importTree); + + if (currentType.ordinal() < previousType.ordinal()) { + String message = buildMessage(currentType, previousType); + reportIssue(importTree.importKeyword(), message); + } + + previousType = currentType; + } + } + + private static String buildMessage(ImportType currentType, ImportType previousType) { + return String.format("Reorder this %s import to come before %s imports.", + currentType.getDescription(), + previousType.getDescription()); + } + + private static ImportType classifyImport(ImportTree importTree) { + boolean isWildcard = isWildcardImport(importTree); + + if (importTree.isModule()) { + return ImportType.MODULE_IMPORT; + } + + if (importTree.isStatic()) { + return isWildcard ? ImportType.STATIC_PACKAGE_IMPORT : ImportType.STATIC_SINGLE_TYPE_IMPORT; + } + + return isWildcard ? ImportType.PACKAGE_IMPORT : ImportType.SINGLE_TYPE_IMPORT; + } + + private static boolean isWildcardImport(ImportTree importTree) { + String qualifiedName = ExpressionsHelper.concatenate((ExpressionTree) importTree.qualifiedIdentifier()); + return qualifiedName.endsWith(".*"); + } + + enum ImportType { + SENTINEL_IMPORT("sentinel"), + MODULE_IMPORT("module"), + PACKAGE_IMPORT("on-demand package"), + SINGLE_TYPE_IMPORT("single-type"), + STATIC_PACKAGE_IMPORT("static on-demand package"), + STATIC_SINGLE_TYPE_IMPORT("static single-type"); + + private final String description; + + ImportType(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + } +} From 792721263057ced88b604c1567c5d1873624ec25 Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Thu, 12 Feb 2026 13:25:45 +0100 Subject: [PATCH 04/10] Add tests for S8445 ImportDeclarationOrderCheck Add comprehensive test suite with sample files: - ImportDeclarationOrderCheckSample.java: Tests violations with mixed import orders - ImportDeclarationOrderCheckSampleNoIssues.java: Tests correctly ordered imports - Tests verify rule only runs on Java 25+ Co-Authored-By: Claude Sonnet 4.5 --- .../ImportDeclarationOrderCheckSample.java | 21 +++++++ ...rtDeclarationOrderCheckSampleNoIssues.java | 25 +++++++++ .../ImportDeclarationOrderCheckTest.java | 55 +++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSample.java create mode 100644 java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssues.java create mode 100644 java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java diff --git a/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSample.java new file mode 100644 index 00000000000..4524ba4a54e --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSample.java @@ -0,0 +1,21 @@ +package checks; + +// Basic violation: package import after single-type import +import java.util.List; // Compliant +import java.io.*; // Noncompliant {{Reorder this on-demand package import to come before single-type imports.}} +import java.util.Map; // Compliant + +// Static import violations +import static java.lang.Math.PI; // Compliant +import static java.util.Collections.*; // Noncompliant {{Reorder this static on-demand package import to come before static single-type imports.}} + +// Module import violation - comes after static imports +import module java.base; // Noncompliant {{Reorder this module import to come before static on-demand package imports.}} + +// These are compliant since they come after module import (which resets the ordering) +import java.sql.*; // Compliant +import java.time.Instant; // Compliant +import static java.lang.System.out; // Compliant + +class ImportDeclarationOrderCheckSample { +} diff --git a/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssues.java b/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssues.java new file mode 100644 index 00000000000..53c9504651c --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssues.java @@ -0,0 +1,25 @@ +package checks; + +// All imports in correct order - no violations expected + +// Module imports +import module java.base; + +// Package imports (on-demand) +import java.io.*; +import java.sql.*; + +// Single-type imports +import java.util.List; +import java.util.Map; +import java.time.Instant; + +// Static package imports (on-demand) +import static java.util.Collections.*; + +// Static single-type imports +import static java.lang.Math.PI; +import static java.lang.System.out; + +class ImportDeclarationOrderCheckSampleNoIssues { +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java new file mode 100644 index 00000000000..3745d6c104e --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube Java + * Copyright (C) 2012-2025 SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; + +class ImportDeclarationOrderCheckTest { + + @Test + void basic_import_ordering() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/ImportDeclarationOrderCheckSample.java")) + .withCheck(new ImportDeclarationOrderCheck()) + .withJavaVersion(25) + .verifyIssues(); + } + + @Test + void no_issue_on_java_24() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/ImportDeclarationOrderCheckSample.java")) + .withCheck(new ImportDeclarationOrderCheck()) + .withJavaVersion(24) + .verifyNoIssues(); + } + + @Test + @DisplayName("The same imports but correctly ordered") + void correctly_ordered_imports() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/ImportDeclarationOrderCheckSampleNoIssues.java")) + .withCheck(new ImportDeclarationOrderCheck()) + .withJavaVersion(25) + .verifyNoIssues(); + } + +} From 4c226683e84890033a280c7eb450397f8572c903 Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Thu, 12 Feb 2026 15:52:52 +0100 Subject: [PATCH 05/10] Add metadata files for S8445 rule Add JSON and HTML metadata for ImportDeclarationOrderCheck rule: - S8445.json: Rule configuration with Minor severity, 2min remediation - S8445.html: Rule documentation with examples and rationale - Sonar_way_profile.json: Add S8445 to Sonar way profile Co-Authored-By: Claude Sonnet 4.5 --- .../org/sonar/l10n/java/rules/java/S8445.html | 63 +++++++++++++++++++ .../org/sonar/l10n/java/rules/java/S8445.json | 24 +++++++ .../java/rules/java/Sonar_way_profile.json | 3 +- 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8445.html create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8445.json diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8445.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8445.html new file mode 100644 index 00000000000..daef907884b --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8445.html @@ -0,0 +1,63 @@ +

This rule raises an issue when import declarations are not organized into distinct groups based on their kind and specificity level.

+

Why is this an issue?

+

Import declarations should be organized into distinct groups based on their kind to improve readability and make shadowing behavior explicit. The +grouping order should reflect specificity levels: module imports first (least specific), followed by on-demand package imports (intermediate +specificity), single-type imports (most specific), then static on-demand imports, and finally single-static imports.

+

When imports are mixed or ordered arbitrarily, it becomes harder to understand which declarations might shadow others. Java’s import shadowing +rules mean that more specific imports can override less specific ones, so organizing imports by specificity makes these relationships clearer and +improves code maintainability by providing a consistent, predictable structure.

+

How to fix it

+

Organize import declarations into the following groups, in this order:

+
    +
  1. Module imports (e.g., import module java.base;)
  2. +
  3. On-demand package imports (e.g., import javax.swing.text.*;)
  4. +
  5. Single-type imports (e.g., import java.util.List;)
  6. +
  7. Static on-demand imports (e.g., import static java.util.Collections.*;)
  8. +
  9. Single-static imports (e.g., import static java.util.regex.Pattern.compile;)
  10. +
+

Separate each group with a blank line for better visual organization.

+

Code examples

+

Noncompliant code example

+
+import java.sql.Date;
+import module java.base;
+import static java.util.Collections.*;
+import javax.swing.text.*;
+import module java.desktop;
+import static java.util.regex.Pattern.compile;
+import java.util.List;
+
+class Foo {
+    // ...
+}
+
+

Compliant solution

+
+// Module imports
+import module java.base;
+import module java.desktop;
+
+// On-demand package imports
+import javax.swing.text.*; // resolves the ambiguity of the simple name Element
+
+// Single-type imports
+import java.sql.Date; // resolves the ambiguity of the simple name Date
+import java.util.List;
+
+// Static on-demand imports
+import static java.util.Collections.*;
+
+// Single-static imports
+import static java.util.regex.Pattern.compile;
+
+class Foo {
+    // ...
+}
+
+

Resources

+

Documentation

+ + diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8445.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8445.json new file mode 100644 index 00000000000..fd4bc8c8507 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8445.json @@ -0,0 +1,24 @@ +{ + "title": "Import declarations should be grouped by specificity", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "2min" + }, + "tags": [ + "convention", + "java25" + ], + "defaultSeverity": "Minor", + "ruleSpecification": "RSPEC-8445", + "sqKey": "S8445", + "scope": "All", + "quickfix": "targeted", + "code": { + "impacts": { + "MAINTAINABILITY": "LOW" + }, + "attribute": "CONVENTIONAL" + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json index c6ee6637bd6..ddecba36ba4 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json @@ -520,6 +520,7 @@ "S8346", "S8432", "S8433", - "S8444" + "S8444", + "S8445" ] } From 44d8fead51a2478073c4b3e7bc6e3990fbada254 Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Thu, 12 Feb 2026 16:16:53 +0100 Subject: [PATCH 06/10] Add autoscan expected results for S8445 Co-Authored-By: Claude Sonnet 4.5 --- .../src/test/resources/autoscan/diffs/diff_S8445.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 its/autoscan/src/test/resources/autoscan/diffs/diff_S8445.json diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S8445.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8445.json new file mode 100644 index 00000000000..7339e14441a --- /dev/null +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8445.json @@ -0,0 +1,6 @@ +{ + "ruleKey": "S8445", + "hasTruePositives": true, + "falseNegatives": 0, + "falsePositives": 0 +} \ No newline at end of file From 8d5b335dd0da8d20ff6d1bee54b464eb5f53bc9d Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Fri, 13 Feb 2026 10:23:23 +0100 Subject: [PATCH 07/10] Changed S2208 rule to work for all Java versions and raise issues only when module declarations aren't present --- ...rationOrderCheckSampleNoIssuesSample.java} | 2 +- ...NotBeUsedCheckWithModuleImportsSample.java | 28 +++++++++++++++++ .../WildcardImportsShouldNotBeUsedCheck.java | 30 ++++++++++++------- .../ImportDeclarationOrderCheckTest.java | 3 +- ...ldcardImportsShouldNotBeUsedCheckTest.java | 7 +++-- 5 files changed, 54 insertions(+), 16 deletions(-) rename java-checks-test-sources/default/src/main/java/checks/{ImportDeclarationOrderCheckSampleNoIssues.java => ImportDeclarationOrderCheckSampleNoIssuesSample.java} (89%) create mode 100644 java-checks-test-sources/default/src/main/java/checks/WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample.java diff --git a/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssues.java b/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssuesSample.java similarity index 89% rename from java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssues.java rename to java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssuesSample.java index 53c9504651c..6a95a5f3195 100644 --- a/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssues.java +++ b/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssuesSample.java @@ -21,5 +21,5 @@ import static java.lang.Math.PI; import static java.lang.System.out; -class ImportDeclarationOrderCheckSampleNoIssues { +class ImportDeclarationOrderCheckSampleNoIssuesSample { } diff --git a/java-checks-test-sources/default/src/main/java/checks/WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample.java b/java-checks-test-sources/default/src/main/java/checks/WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample.java new file mode 100644 index 00000000000..ae03eff98de --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample.java @@ -0,0 +1,28 @@ +package checks; + +// When module imports are present, wildcard imports are compliant (allowed by S8445) +import module java.base; + +import java.io.*; // Compliant - module imports are present +import java.util.*; // Compliant - module imports are present +import org.apache.commons.io.*; // Compliant - module imports are present +import java.sql.Date; +import static java.util.Arrays.*; +import static java.util.Collections.addAll; + +public final class WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample { + + private WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample() { + super(); + } + + public static boolean testMethodInput(InputStream input) { + return input == null; + } + + public static void testMethodArrays() { + sort(new int[] {}); + List> list = new ArrayList>(); + addAll(list, FileUtils.class); + } +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java b/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java index 091d0f9cb4d..7ce29e42190 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java @@ -19,8 +19,7 @@ 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.JavaVersion; -import org.sonar.plugins.java.api.JavaVersionAwareVisitor; +import org.sonar.plugins.java.api.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.ImportTree; import org.sonar.plugins.java.api.tree.Tree; @@ -30,22 +29,33 @@ import java.util.List; @Rule(key = "S2208") -public class WildcardImportsShouldNotBeUsedCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor { - - @Override - public boolean isCompatibleWithJavaVersion(JavaVersion version) { - return !version.isJava25Compatible(); - } +public class WildcardImportsShouldNotBeUsedCheck extends IssuableSubscriptionVisitor { @Override public List nodesToVisit() { - return Collections.singletonList(Tree.Kind.IMPORT); + return Collections.singletonList(Tree.Kind.COMPILATION_UNIT); } @Override public void visitNode(Tree tree) { - ImportTree importTree = (ImportTree) tree; + CompilationUnitTree compilationUnit = (CompilationUnitTree) tree; + + // Do not raise issues when module imports are present + boolean hasModuleImports = compilationUnit.imports().stream() + .anyMatch(importTree -> importTree.is(Tree.Kind.IMPORT) && ((ImportTree) importTree).isModule()); + + if (hasModuleImports) { + return; + } + + // Check for wildcard imports + compilationUnit.imports().stream() + .filter(importTree -> importTree.is(Tree.Kind.IMPORT)) + .map(ImportTree.class::cast) + .forEach(this::checkWildcardImport); + } + private void checkWildcardImport(ImportTree importTree) { // See RSPEC-2208 : exception with static imports. String qualifiedName = ExpressionsHelper.concatenate((ExpressionTree) importTree.qualifiedIdentifier()); if (qualifiedName.endsWith(".*") && !importTree.isStatic()) { diff --git a/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java index 3745d6c104e..38d8b9e517a 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java @@ -46,10 +46,9 @@ void no_issue_on_java_24() { @DisplayName("The same imports but correctly ordered") void correctly_ordered_imports() { CheckVerifier.newVerifier() - .onFile(mainCodeSourcesPath("checks/ImportDeclarationOrderCheckSampleNoIssues.java")) + .onFile(mainCodeSourcesPath("checks/ImportDeclarationOrderCheckSampleNoIssuesSample.java")) .withCheck(new ImportDeclarationOrderCheck()) .withJavaVersion(25) .verifyNoIssues(); } - } diff --git a/java-checks/src/test/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheckTest.java index cb65cb75d92..fa7ba468345 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheckTest.java @@ -16,6 +16,7 @@ */ package org.sonar.java.checks; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.sonar.java.checks.verifier.CheckVerifier; @@ -32,11 +33,11 @@ void test() { } @Test - void no_issue_on_java_25() { + @DisplayName("No issues when module imports are present") + void no_issues_with_module_imports() { CheckVerifier.newVerifier() - .onFile(mainCodeSourcesPath("checks/WildcardImportsShouldNotBeUsedCheck.java")) + .onFile(mainCodeSourcesPath("checks/WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample.java")) .withCheck(new WildcardImportsShouldNotBeUsedCheck()) - .withJavaVersion(25) .verifyNoIssues(); } From 989c74f837c2df960ba80f37618cdf24770f2ace Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Fri, 13 Feb 2026 13:35:02 +0100 Subject: [PATCH 08/10] Changed autoscan expected results --- its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json index cc228c80f8f..0d2b1e2a905 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json @@ -1,6 +1,6 @@ { "ruleKey": "S1128", "hasTruePositives": true, - "falseNegatives": 30, + "falseNegatives": 32, "falsePositives": 0 } From 7a18835cb37d53182373fab7e6cc8c0ad1fb13e7 Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Fri, 13 Feb 2026 14:36:12 +0100 Subject: [PATCH 09/10] Reverted some changes --- .../resources/autoscan/diffs/diff_S1128.json | 2 +- ...arationOrderCheckSampleNoIssuesSample.java | 1 + ...NotBeUsedCheckWithModuleImportsSample.java | 28 ------------------- .../checks/ImportDeclarationOrderCheck.java | 17 +++-------- .../WildcardImportsShouldNotBeUsedCheck.java | 21 ++------------ .../ImportDeclarationOrderCheckTest.java | 1 + ...ldcardImportsShouldNotBeUsedCheckTest.java | 10 ------- 7 files changed, 9 insertions(+), 71 deletions(-) delete mode 100644 java-checks-test-sources/default/src/main/java/checks/WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample.java diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json index 0d2b1e2a905..cc228c80f8f 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json @@ -1,6 +1,6 @@ { "ruleKey": "S1128", "hasTruePositives": true, - "falseNegatives": 32, + "falseNegatives": 30, "falsePositives": 0 } diff --git a/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssuesSample.java b/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssuesSample.java index 6a95a5f3195..ee7bfdb173e 100644 --- a/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssuesSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSampleNoIssuesSample.java @@ -21,5 +21,6 @@ import static java.lang.Math.PI; import static java.lang.System.out; + class ImportDeclarationOrderCheckSampleNoIssuesSample { } diff --git a/java-checks-test-sources/default/src/main/java/checks/WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample.java b/java-checks-test-sources/default/src/main/java/checks/WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample.java deleted file mode 100644 index ae03eff98de..00000000000 --- a/java-checks-test-sources/default/src/main/java/checks/WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample.java +++ /dev/null @@ -1,28 +0,0 @@ -package checks; - -// When module imports are present, wildcard imports are compliant (allowed by S8445) -import module java.base; - -import java.io.*; // Compliant - module imports are present -import java.util.*; // Compliant - module imports are present -import org.apache.commons.io.*; // Compliant - module imports are present -import java.sql.Date; -import static java.util.Arrays.*; -import static java.util.Collections.addAll; - -public final class WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample { - - private WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample() { - super(); - } - - public static boolean testMethodInput(InputStream input) { - return input == null; - } - - public static void testMethodArrays() { - sort(new int[] {}); - List> list = new ArrayList>(); - addAll(list, FileUtils.class); - } -} diff --git a/java-checks/src/main/java/org/sonar/java/checks/ImportDeclarationOrderCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ImportDeclarationOrderCheck.java index 85f5aa5ba95..1e8468f30c4 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ImportDeclarationOrderCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ImportDeclarationOrderCheck.java @@ -20,20 +20,16 @@ import java.util.Collections; 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.JavaVersion; import org.sonar.plugins.java.api.JavaVersionAwareVisitor; import org.sonar.plugins.java.api.tree.CompilationUnitTree; -import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.ImportTree; import org.sonar.plugins.java.api.tree.Tree; @Rule(key = "S8445") public class ImportDeclarationOrderCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor { - private final List imports = new ArrayList<>(); - @Override public boolean isCompatibleWithJavaVersion(JavaVersion version) { return version.isJava25Compatible(); @@ -48,6 +44,7 @@ public List nodesToVisit() { public void visitNode(Tree tree) { CompilationUnitTree compilationUnit = (CompilationUnitTree) tree; + List imports = new ArrayList<>(); // Collect all import statements compilationUnit.imports() .stream() @@ -55,15 +52,10 @@ public void visitNode(Tree tree) { .map(ImportTree.class::cast) .forEach(imports::add); - analyzeImportOrder(); - } - - @Override - public void leaveNode(Tree tree) { - imports.clear(); + analyzeImportOrder(imports); } - private void analyzeImportOrder() { + private void analyzeImportOrder(List imports) { if (imports.size() <= 1) { return; } @@ -102,8 +94,7 @@ private static ImportType classifyImport(ImportTree importTree) { } private static boolean isWildcardImport(ImportTree importTree) { - String qualifiedName = ExpressionsHelper.concatenate((ExpressionTree) importTree.qualifiedIdentifier()); - return qualifiedName.endsWith(".*"); + return "*".equals(importTree.qualifiedIdentifier().lastToken().text()); } enum ImportType { diff --git a/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java b/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java index 7ce29e42190..0ccdfb16fd8 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheck.java @@ -19,7 +19,6 @@ 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.tree.CompilationUnitTree; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.ImportTree; import org.sonar.plugins.java.api.tree.Tree; @@ -33,29 +32,13 @@ public class WildcardImportsShouldNotBeUsedCheck extends IssuableSubscriptionVis @Override public List nodesToVisit() { - return Collections.singletonList(Tree.Kind.COMPILATION_UNIT); + return Collections.singletonList(Tree.Kind.IMPORT); } @Override public void visitNode(Tree tree) { - CompilationUnitTree compilationUnit = (CompilationUnitTree) tree; + ImportTree importTree = (ImportTree) tree; - // Do not raise issues when module imports are present - boolean hasModuleImports = compilationUnit.imports().stream() - .anyMatch(importTree -> importTree.is(Tree.Kind.IMPORT) && ((ImportTree) importTree).isModule()); - - if (hasModuleImports) { - return; - } - - // Check for wildcard imports - compilationUnit.imports().stream() - .filter(importTree -> importTree.is(Tree.Kind.IMPORT)) - .map(ImportTree.class::cast) - .forEach(this::checkWildcardImport); - } - - private void checkWildcardImport(ImportTree importTree) { // See RSPEC-2208 : exception with static imports. String qualifiedName = ExpressionsHelper.concatenate((ExpressionTree) importTree.qualifiedIdentifier()); if (qualifiedName.endsWith(".*") && !importTree.isStatic()) { diff --git a/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java index 38d8b9e517a..53bd5620a6c 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/ImportDeclarationOrderCheckTest.java @@ -51,4 +51,5 @@ void correctly_ordered_imports() { .withJavaVersion(25) .verifyNoIssues(); } + } diff --git a/java-checks/src/test/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheckTest.java index fa7ba468345..1cb5454d782 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/WildcardImportsShouldNotBeUsedCheckTest.java @@ -16,7 +16,6 @@ */ package org.sonar.java.checks; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.sonar.java.checks.verifier.CheckVerifier; @@ -32,13 +31,4 @@ void test() { .verifyIssues(); } - @Test - @DisplayName("No issues when module imports are present") - void no_issues_with_module_imports() { - CheckVerifier.newVerifier() - .onFile(mainCodeSourcesPath("checks/WildcardImportsShouldNotBeUsedCheckWithModuleImportsSample.java")) - .withCheck(new WildcardImportsShouldNotBeUsedCheck()) - .verifyNoIssues(); - } - } From 58753112d0b895cef56a2030b61281c94ef22b55 Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Sun, 15 Feb 2026 13:37:55 +0100 Subject: [PATCH 10/10] Fix --- .../main/java/checks/ImportDeclarationOrderCheckSample.java | 3 ++- .../org/sonar/java/checks/ImportDeclarationOrderCheck.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSample.java index 4524ba4a54e..d6bd9730a84 100644 --- a/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/ImportDeclarationOrderCheckSample.java @@ -3,12 +3,13 @@ // Basic violation: package import after single-type import import java.util.List; // Compliant import java.io.*; // Noncompliant {{Reorder this on-demand package import to come before single-type imports.}} +// ^^^^^^^^^ import java.util.Map; // Compliant // Static import violations import static java.lang.Math.PI; // Compliant import static java.util.Collections.*; // Noncompliant {{Reorder this static on-demand package import to come before static single-type imports.}} - +// ^^^^^^^^^^^^^^^^^^^^^^^ // Module import violation - comes after static imports import module java.base; // Noncompliant {{Reorder this module import to come before static on-demand package imports.}} diff --git a/java-checks/src/main/java/org/sonar/java/checks/ImportDeclarationOrderCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ImportDeclarationOrderCheck.java index 1e8468f30c4..7abd4b69168 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ImportDeclarationOrderCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ImportDeclarationOrderCheck.java @@ -66,7 +66,7 @@ private void analyzeImportOrder(List imports) { if (currentType.ordinal() < previousType.ordinal()) { String message = buildMessage(currentType, previousType); - reportIssue(importTree.importKeyword(), message); + reportIssue(importTree.qualifiedIdentifier(), message); } previousType = currentType;