Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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;

import javax.annotation.CheckForNull;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.config.Configuration;
import org.sonar.java.model.JavaVersionImpl;
import org.sonar.plugins.java.api.JavaVersion;
import org.sonar.plugins.java.api.internal.ModuleMetadata;

import static org.sonar.java.SonarComponents.SONAR_IGNORE_UNNAMED_MODULE_FOR_SPLIT_PACKAGE;

public class DefaultModuleMetadata implements ModuleMetadata {

private final JavaVersion javaVersion;
private final ProjectDefinition projectDefinition;
private final boolean ignoreUnnamedModuleForSplitPackage;

public DefaultModuleMetadata(ProjectDefinition projectDefinition, Configuration configuration) {
this.javaVersion = JavaVersionImpl.readFromConfiguration(configuration);
this.projectDefinition = projectDefinition;
this.ignoreUnnamedModuleForSplitPackage = configuration.getBoolean(SONAR_IGNORE_UNNAMED_MODULE_FOR_SPLIT_PACKAGE).orElse(false);
}

@Override
public JavaVersion javaVersion() {
return javaVersion;
}

@Override
public String moduleKey() {
var root = getRootProject();
if (root != null && projectDefinition != null) {
var rootBase = root.getBaseDir().toPath();
var moduleBase = projectDefinition.getBaseDir().toPath();
return rootBase.relativize(moduleBase).toString().replace('\\', '/');
}
return "";
}

@Override
public boolean shouldIgnoreUnnamedModuleForSplitPackage() {
return ignoreUnnamedModuleForSplitPackage;
}

@CheckForNull
private ProjectDefinition getRootProject() {
ProjectDefinition current = projectDefinition;
if (current == null) {
return null;
}
while (current.getParent() != null) {
current = current.getParent();
}
return current;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package org.sonar.java.model;

import java.util.Locale;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.config.Configuration;
import org.sonar.plugins.java.api.JavaVersion;

public class JavaVersionImpl implements JavaVersion {
Expand Down Expand Up @@ -230,4 +232,22 @@ private static int convertJavaVersionString(String javaVersion) {
return Integer.parseInt(cleanedVersion);
}

public static JavaVersion readFromConfiguration(Configuration config) {
Optional<String> javaVersionAsString = config.get(SOURCE_VERSION);
if (!javaVersionAsString.isPresent()) {
return new JavaVersionImpl();
}
String enablePreviewAsString = config.get(ENABLE_PREVIEW).orElse("false");

JavaVersion javaVersion = fromString(javaVersionAsString.get(), enablePreviewAsString);
if (javaVersion.arePreviewFeaturesEnabled() && javaVersion.asInt() < MAX_SUPPORTED) {
LOG.warn("sonar.java.enablePreview is set but will be discarded as the Java version is less than the max" +
" supported version ({} < {})", javaVersion.asInt(), MAX_SUPPORTED);
javaVersion = new JavaVersionImpl(javaVersion.asInt(), false);
}
LOG.info("Configured Java source version ({}): {}, preview features enabled ({}): {}",
SOURCE_VERSION, javaVersion.asInt(), ENABLE_PREVIEW, javaVersion.arePreviewFeaturesEnabled());
return javaVersion;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.internal;

import org.sonar.api.batch.ScannerSide;
import org.sonar.java.annotations.Beta;
import org.sonar.plugins.java.api.JavaVersion;


/**
* Interface to access metadata about the module being analyzed by a Sensor.
* For internal use only, this API will not be supported for custom plugins.
*/
@Beta
@ScannerSide
public interface ModuleMetadata {

/**
* Returns the Java version of the module being analyzed.
*/
JavaVersion javaVersion();

/**
* Returns the module key of the module being analyzed.
*/
String moduleKey();

/**
* Describes whether input files should be parsed while ignoring unnamed split modules.
*/
boolean shouldIgnoreUnnamedModuleForSplitPackage();

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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;

import java.io.File;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.config.Configuration;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;

class DefaultModuleMetadataTest {

@Test
void test() {
var projectDefinition = mockProjectDefinition();
var config = mockConfiguration();
var defaultModuleMetadata = new DefaultModuleMetadata(projectDefinition, config);

assertThat(defaultModuleMetadata.moduleKey()).isEqualTo("pmodule/cmodule");
assertThat(defaultModuleMetadata.javaVersion().asInt()).isEqualTo(-1);
assertThat(defaultModuleMetadata.shouldIgnoreUnnamedModuleForSplitPackage()).isFalse();
}

@Test
void testWithJavaVersion() {
var projectDefinition = mockProjectDefinition();
var config = mockConfiguration("sonar.java.source", "11");
var defaultModuleMetadata = new DefaultModuleMetadata(projectDefinition, config);

assertThat(defaultModuleMetadata.moduleKey()).isEqualTo("pmodule/cmodule");
assertThat(defaultModuleMetadata.javaVersion().asInt()).isEqualTo(11);
}

@Test
void testWithShouldIgnoreUnnamed() {
var projectDefinition = mockProjectDefinition();
var config = mockConfiguration("sonar.java.ignoreUnnamedModuleForSplitPackage", "true");
var defaultModuleMetadata = new DefaultModuleMetadata(projectDefinition, config);

assertThat(defaultModuleMetadata.moduleKey()).isEqualTo("pmodule/cmodule");
assertThat(defaultModuleMetadata.shouldIgnoreUnnamedModuleForSplitPackage()).isTrue();
}

private ProjectDefinition mockProjectDefinition() {
var rootProj = mock(ProjectDefinition.class);
doReturn(new File("/foo/bar/proj")).when(rootProj).getBaseDir();
var childModule = mock(ProjectDefinition.class);
doReturn(new File("/foo/bar/proj/pmodule/cmodule")).when(childModule).getBaseDir();
doReturn(rootProj).when(childModule).getParent();

return childModule;
}

private Configuration mockConfiguration(String... keysAndValues) {
Configuration configuration = mock(Configuration.class);
for (int i = 0; i < keysAndValues.length; i++) {
String key = keysAndValues[i++];
String value = keysAndValues[i];
doReturn(Optional.of(value)).when(configuration).get(key);
if(value.equals("true") || value.equals("false")) {
doReturn(Optional.of(Boolean.valueOf(value))).when(configuration).getBoolean(key);
}
}
return configuration;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@
*/
package org.sonar.java.model;

import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.sonar.api.config.Configuration;
import org.sonar.plugins.java.api.JavaVersion;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;

class JavaVersionImplTest {

Expand Down Expand Up @@ -182,5 +186,16 @@ void test_fromMap() {
assertThat(version.asInt()).isEqualTo(-1);
assertThat(version.arePreviewFeaturesEnabled()).isFalse();
}

@Test
void test_preview_features_without_max_version_from_config(){
var config = mock(Configuration.class);
doReturn(Optional.of("17")).when(config).get(JavaVersion.SOURCE_VERSION);
doReturn(Optional.of("true")).when(config).get(JavaVersion.ENABLE_PREVIEW);
var javaVersion = JavaVersionImpl.readFromConfiguration(config);
assertThat(javaVersion.asInt()).isEqualTo(17);
// Preview features should be disabled because 17 is not the max supported version
assertThat(javaVersion.arePreviewFeaturesEnabled()).isFalse();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.sonar.api.config.PropertyDefinition;
import org.sonar.java.AnalysisWarningsWrapper;
import org.sonar.java.DefaultJavaResourceLocator;
import org.sonar.java.DefaultModuleMetadata;
import org.sonar.java.JavaConstants;
import org.sonar.java.SonarComponents;
import org.sonar.java.classpath.ClasspathForMain;
Expand Down Expand Up @@ -117,6 +118,7 @@ public void define(Context context) {
PostAnalysisIssueFilter.class));

list.add(AnalysisWarningsWrapper.class);
list.add(DefaultModuleMetadata.class);
context.addExtensions(Collections.unmodifiableList(list));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public void execute(SensorContext context) {

Measurer measurer = new Measurer(context, noSonarFilter);

JavaVersion javaVersion = getJavaVersion();
JavaVersion javaVersion = JavaVersionImpl.readFromConfiguration(settings);
telemetry.aggregateAsSortedSet(JAVA_LANGUAGE_VERSION, javaVersion.toString());
telemetry.aggregateAsCounter(JAVA_MODULE_COUNT, 1L);

Expand Down Expand Up @@ -181,24 +181,6 @@ private Iterable<InputFile> javaFiles(InputFile.Type type) {
return fs.inputFiles(fs.predicates().and(fs.predicates().hasLanguage(Java.KEY), fs.predicates().hasType(type)));
}

private JavaVersion getJavaVersion() {
Optional<String> javaVersionAsString = settings.get(JavaVersion.SOURCE_VERSION);
if (!javaVersionAsString.isPresent()) {
return new JavaVersionImpl();
}
String enablePreviewAsString = settings.get(JavaVersion.ENABLE_PREVIEW).orElse("false");

JavaVersion javaVersion = JavaVersionImpl.fromString(javaVersionAsString.get(), enablePreviewAsString);
if (javaVersion.arePreviewFeaturesEnabled() && javaVersion.asInt() < JavaVersionImpl.MAX_SUPPORTED) {
LOG.warn("sonar.java.enablePreview is set but will be discarded as the Java version is less than the max" +
" supported version ({} < {})", javaVersion.asInt(), JavaVersionImpl.MAX_SUPPORTED);
javaVersion = new JavaVersionImpl(javaVersion.asInt(), false);
}
LOG.info("Configured Java source version ({}): {}, preview features enabled ({}): {}",
JavaVersion.SOURCE_VERSION, javaVersion.asInt(), JavaVersion.ENABLE_PREVIEW, javaVersion.arePreviewFeaturesEnabled());
return javaVersion;
}

@Override
public String toString() {
return getClass().getSimpleName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ void sonarLint_9_9_extensions() {
Plugin.Context context = new Plugin.Context(runtime);
javaPlugin.define(context);
assertThat(context.getExtensions())
.hasSize(19)
.hasSize(20)
.contains(SonarLintCache.class);
}

Expand All @@ -51,7 +51,7 @@ void sonarqube_9_9_extensions() {
Plugin.Context context = new Plugin.Context(sqCommunity);
javaPlugin.define(context);
assertThat(context.getExtensions())
.hasSize(35)
.hasSize(36)
.doesNotContain(Jasper.class);
}

Expand All @@ -61,7 +61,7 @@ void sonarqube_9_9_commercial_extensions() {
Plugin.Context context = new Plugin.Context(sqEnterprise);
javaPlugin.define(context);
assertThat(context.getExtensions())
.hasSize(36)
.hasSize(37)
.contains(Jasper.class);
}

Expand Down
Loading