scannersNotRequiringParsing = new ArrayList<>();
var fileScannerContext = createScannerContext(sonarComponents, inputFile, javaVersion, inAndroidContext, cacheContext);
- for (var scanner: scannersThatCannotBeSkipped) {
+ for (var scanner : scannersThatCannotBeSkipped) {
boolean exceptionIsBlownUp = false;
PerformanceMeasure.Duration scannerDuration = PerformanceMeasure.start(scanner);
try {
@@ -288,7 +301,8 @@ private void runScanner(Runnable action, JavaFileScanner scanner) throws CheckFa
}
String message = String.format(
- "Unable to run check %s - %s on file '%s', To help improve the SonarSource Java Analyzer, please report this problem to SonarSource: see https://community.sonarsource.com/",
+ "Unable to run check %s - %s on file '%s', To help improve the SonarSource Java Analyzer, please report this problem to SonarSource: see https://community.sonarsource" +
+ ".com/",
scanner.getClass(), ruleKey(scanner), currentFile);
LOG.error(message, e);
diff --git a/java-frontend/src/main/java/org/sonar/plugins/java/api/DependencyVersionAware.java b/java-frontend/src/main/java/org/sonar/plugins/java/api/DependencyVersionAware.java
new file mode 100644
index 0000000000..e40d5a3cde
--- /dev/null
+++ b/java-frontend/src/main/java/org/sonar/plugins/java/api/DependencyVersionAware.java
@@ -0,0 +1,51 @@
+/*
+ * SonarQube Java
+ * Copyright (C) 2012-2025 SonarSource SA
+ * 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.plugins.java.api;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Implementing this interface allows a check to be executed - or not - during analysis, depending on the
+ * version of desired libraries among dependencies of the current module.
+ *
+ * For example, if a rule is to be used only on modules of a project that use spring-web version at least 4.3,
+ * it should implement {@link DependencyVersionAware} with the following method:
+ *
+ * {@literal @}Override
+ * public boolean isCompatibleWithDependencies(Function> dependencyFinder) {
+ * return dependencyFinder.apply("spring-web")
+ * .map(v -> v.isGreaterThanOrEqualTo("4.3"))
+ * .orElse(false);
+ * }
+ *
+ */
+public interface DependencyVersionAware {
+
+ /**
+ * Control whether the check is compatible with the dependencies of the project being analysed.
+ *
+ * @param dependencyFinder is a function that takes in the name of an artifact that may be found in the classpath
+ * of the project. It returns the version of that artifact that was detected, or an empty
+ * optional if it is not detected in the classpath.
+ * Note that we cannot guarantee that a dependency will always be detected in the case were
+ * the classpath doesn't come from a standard build system like maven or gradle.
+ * @return true if the check is compatible with the detected dependencies and should be executed on sources, false otherwise.
+ */
+ boolean isCompatibleWithDependencies(Function> dependencyFinder);
+
+}
diff --git a/java-frontend/src/main/java/org/sonar/plugins/java/api/Version.java b/java-frontend/src/main/java/org/sonar/plugins/java/api/Version.java
new file mode 100644
index 0000000000..b54814192a
--- /dev/null
+++ b/java-frontend/src/main/java/org/sonar/plugins/java/api/Version.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube Java
+ * Copyright (C) 2012-2025 SonarSource SA
+ * 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.plugins.java.api;
+
+/** Versions of libraries. This provides methods to compare the version with other ones. */
+public interface Version {
+
+ Integer major();
+ Integer minor();
+ Integer patch();
+ String qualifier();
+
+ boolean isGreaterThanOrEqualTo(Version version);
+ boolean isGreaterThanOrEqualTo(String version);
+ boolean isGreaterThan(Version version);
+ boolean isLowerThanOrEqualTo(Version version);
+ boolean isLowerThan(Version version);
+ boolean isGreaterThan(String version);
+ boolean isLowerThanOrEqualTo(String version);
+ boolean isLowerThan(String version);
+}
diff --git a/java-frontend/src/test/java/org/sonar/java/classpath/DependencyVersionInferenceTest.java b/java-frontend/src/test/java/org/sonar/java/classpath/DependencyVersionInferenceTest.java
new file mode 100644
index 0000000000..720c0996ad
--- /dev/null
+++ b/java-frontend/src/test/java/org/sonar/java/classpath/DependencyVersionInferenceTest.java
@@ -0,0 +1,61 @@
+/*
+ * SonarQube Java
+ * Copyright (C) 2012-2025 SonarSource SA
+ * 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.classpath;
+
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import org.sonar.java.test.classpath.TestClasspathUtils;
+import org.sonar.plugins.java.api.Version;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class DependencyVersionInferenceTest {
+
+ @Test
+ void inferLombok() {
+ // Arrange
+ List lombokClasspath = TestClasspathUtils
+ .loadFromFile("../java-checks-test-sources/default/target/test-classpath.txt");
+
+ // Act
+ Optional version = new DependencyVersionInference().infer("lombok", lombokClasspath);
+
+ // Assert
+ Assertions.assertTrue(version.isPresent());
+ assertEquals(new VersionImpl(1, 18, 30, null), version.get());
+ }
+
+
+ @Test
+ void inferenceSpringBoot() {
+ // Arrange
+ List classpath = TestClasspathUtils
+ .loadFromFile("../java-checks-test-sources/spring-3.2/target/test-classpath.txt");
+
+ // Act
+ Optional version =
+ new DependencyVersionInference().infer("spring-boot", classpath);
+
+ // Assert
+ Assertions.assertTrue(version.isPresent());
+ assertEquals(new VersionImpl(3, 2, 4, null), version.get());
+ }
+}
diff --git a/java-frontend/src/test/java/org/sonar/java/classpath/VersionTest.java b/java-frontend/src/test/java/org/sonar/java/classpath/VersionTest.java
new file mode 100644
index 0000000000..ffe97716ec
--- /dev/null
+++ b/java-frontend/src/test/java/org/sonar/java/classpath/VersionTest.java
@@ -0,0 +1,100 @@
+/*
+ * SonarQube Java
+ * Copyright (C) 2012-2025 SonarSource SA
+ * 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.classpath;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class VersionTest {
+
+ @Test
+ void testCompareTo() {
+ VersionImpl version = VersionImpl.parse("3.2.6-rc1");
+
+ assertTrue(version.isGreaterThanOrEqualTo("3.2"));
+ assertTrue(version.isGreaterThanOrEqualTo("3.2.5"));
+ assertTrue(version.isGreaterThanOrEqualTo("2.9.4-rc2"));
+ assertFalse(version.isGreaterThanOrEqualTo("3.2.11"));
+
+ assertTrue(version.isLowerThan("4.0.0.RELEASE"));
+ assertTrue(version.isLowerThan("3.3.0"));
+ assertFalse(version.isLowerThan("3.2.6-rc1"));
+
+ assertTrue(version.isGreaterThan("3.0.77"));
+ assertFalse(version.isGreaterThan("3.2"));
+ assertFalse(version.isGreaterThan("3.2.6-rc1"));
+
+ assertTrue(version.isLowerThanOrEqualTo("3.2.6-rc1"));
+ assertFalse(version.isLowerThanOrEqualTo("3.2.5"));
+ }
+
+ @Test
+ void testCompareTo_withNoPatchNumber() {
+ VersionImpl version = VersionImpl.parse("3.2");
+
+ assertTrue(version.isGreaterThanOrEqualTo("3.2"));
+ assertTrue(version.isGreaterThanOrEqualTo("3.2.5"));
+ assertTrue(version.isGreaterThanOrEqualTo("2.9.4-rc2"));
+ assertTrue(version.isGreaterThanOrEqualTo("3.2.11"));
+ assertFalse(version.isGreaterThanOrEqualTo("3.3.11"));
+ assertFalse(version.isGreaterThanOrEqualTo("4.0"));
+
+ assertTrue(version.isLowerThan("4.0.0.RELEASE"));
+ assertTrue(version.isLowerThan("3.3.0"));
+ assertFalse(version.isLowerThan("3.2.6-rc1"));
+
+ assertTrue(version.isGreaterThan("3.0.77"));
+ assertFalse(version.isGreaterThan("3.2"));
+ assertFalse(version.isGreaterThan("3.2.6-rc1"));
+
+ assertTrue(version.isLowerThanOrEqualTo("3.2.5"));
+ assertFalse(version.isLowerThanOrEqualTo("3.1"));
+ }
+
+ @Test
+ void testParse() {
+ assertThrows(IllegalArgumentException.class, () -> VersionImpl.parse("foo"));
+ }
+
+ @Test
+ void testToString() {
+ assertEquals("5.4.3-rc1", VersionImpl.parse("5.4.3-rc1").toString());
+ assertEquals("5.43-rc1", VersionImpl.parse("5.43-rc1").toString());
+ assertEquals("5.431", VersionImpl.parse("5.431").toString());
+ }
+
+ @Test
+ void testEqualsAndHashCode() {
+ VersionImpl version1 = VersionImpl.parse("1.2");
+ VersionImpl version2 = VersionImpl.parse("1.2");
+ VersionImpl version3 = VersionImpl.parse("1.2.3");
+
+ assertEquals(version1, version2);
+ assertEquals(version1.hashCode(), version2.hashCode());
+ assertNotEquals(version1, version3);
+ assertNotEquals(version3, version1);
+ assertNotEquals(version1.hashCode(), version3.hashCode());
+ assertFalse(version1.equals("foo"));
+ assertFalse(version1.equals(null));
+
+ VersionImpl version23 = VersionImpl.parse("2.3");
+ VersionImpl version24 = VersionImpl.parse("2.4");
+ assertNotEquals(version1, version23);
+ assertNotEquals(version23, version24);
+ }
+}
diff --git a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java
index 010b226456..7d03eb6068 100644
--- a/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java
+++ b/java-frontend/src/test/java/org/sonar/java/model/VisitorsBridgeTest.java
@@ -23,6 +23,8 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
import org.assertj.core.api.Fail;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -42,6 +44,7 @@
import org.sonar.java.exceptions.ApiMismatchException;
import org.sonar.java.notchecks.VisitorNotInChecksPackage;
import org.sonar.java.testing.ThreadLocalLogTester;
+import org.sonar.plugins.java.api.DependencyVersionAware;
import org.sonar.plugins.java.api.InputFileScannerContext;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScanner;
@@ -49,6 +52,7 @@
import org.sonar.plugins.java.api.JavaVersion;
import org.sonar.plugins.java.api.JavaVersionAwareVisitor;
import org.sonar.plugins.java.api.ModuleScannerContext;
+import org.sonar.plugins.java.api.Version;
import org.sonar.plugins.java.api.caching.CacheContext;
import org.sonar.plugins.java.api.internal.EndOfAnalysis;
import org.sonar.plugins.java.api.tree.CompilationUnitTree;
@@ -283,6 +287,43 @@ public void endOfAnalysis(ModuleScannerContext context) {
assertThat(trace).containsExactly("RuleForAllJavaVersion", "RuleForJava15", "SubscriptionVisitorForJava10");
}
+ @Test
+ void testfilterScanner_byDependencies() {
+ List trace = new ArrayList<>();
+ class SubscriptionVisitorForSpring8 extends IssuableSubscriptionVisitor implements DependencyVersionAware, EndOfAnalysis {
+
+ @Override
+ public List nodesToVisit() {
+ return List.of();
+ }
+
+ @Override
+ public boolean isCompatibleWithDependencies(Function> dependencyFinder) {
+ return dependencyFinder.apply("spring-core")
+ .map(v -> v.isGreaterThanOrEqualTo("8.0"))
+ .orElse(false);
+ }
+
+ @Override
+ public void endOfAnalysis(ModuleScannerContext context) {
+ trace.add(this.getClass().getSimpleName());
+ }
+ }
+
+ List visitors = Collections.singletonList(new SubscriptionVisitorForSpring8());
+ VisitorsBridge visitorsBridge = new VisitorsBridge(visitors, Collections.emptyList(), null);
+ visitorsBridge.endOfAnalysis();
+ assertThat(trace).isEmpty();
+ trace.clear();
+
+
+ visitorsBridge = new VisitorsBridge(visitors, Collections.singletonList(
+ new File("/home/user/.m2/path/spring-core-8.9.12.jar")), null);
+ visitorsBridge.endOfAnalysis();
+ assertThat(trace).containsExactly("SubscriptionVisitorForSpring8");
+ trace.clear();
+ }
+
@Test
void canSkipScanningOfUnchangedFiles_returns_false_by_default() {
VisitorsBridge vb = visitorsBridge(Collections.emptyList(), true);
diff --git a/sonar-java-plugin/src/main/resources/static/documentation.md b/sonar-java-plugin/src/main/resources/static/documentation.md
index 5050d0706e..71d76bfc6d 100644
--- a/sonar-java-plugin/src/main/resources/static/documentation.md
+++ b/sonar-java-plugin/src/main/resources/static/documentation.md
@@ -144,6 +144,11 @@ The tutorial [Writing Custom Java Rules 101](https://redirect.sonarsource.com/do
### API changes
+#### **8.12**
+
+* New type: `Version` This will allow comparing different versions of the same artifact, and is used by the new `DependencyVersionAware` interface.
+* New interface: `DependencyVersionAware`. Implementations of `JavaCheck` that implement this interface will be activated or deactivated depending on the version of dependencies available in the project.
+
#### **8.10**
* New method: `IssuableSubscriptionVisitor#reportIssue(Tree startTree, Tree endTree, String message, List flow, @Nullable Integer cost)`