Skip to content
Merged
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
Expand Up @@ -7,8 +7,7 @@
class SpringBootSanityTest {
// Compliant, first time we encounter a spring sanity test
@Test
void contextLoads() {
}
void contextLoads() {}

// Noncompliant@+2
@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.config.Configuration;
import org.sonar.java.model.JavaVersionImpl;
import org.sonar.java.utils.ModuleMetadataUtils;
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 static final Logger LOG = LoggerFactory.getLogger(DefaultModuleMetadata.class);

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 moduleKey = ModuleMetadataUtils.getModuleKey(projectDefinition);
if (moduleKey.isEmpty()) {
LOG.warn("Unable to determine module key, using empty string as fallback");
}
return moduleKey;
}

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

}
23 changes: 3 additions & 20 deletions java-frontend/src/main/java/org/sonar/java/SonarComponents.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import org.sonar.java.model.LineUtils;
import org.sonar.java.reporting.AnalyzerMessage;
import org.sonar.java.reporting.JavaIssue;
import org.sonar.java.utils.ModuleMetadataUtils;
import org.sonar.plugins.java.api.CheckRegistrar;
import org.sonar.plugins.java.api.JavaCheck;
import org.sonar.plugins.java.api.JavaFileScanner;
Expand Down Expand Up @@ -513,7 +514,7 @@ private static long computeIdealBatchSize() {
}

public File projectLevelWorkDir() {
var root = getRootProject();
var root = ModuleMetadataUtils.getRootProject(projectDefinition);
if (root != null) {
return root.getWorkDir();
} else {
Expand All @@ -527,25 +528,7 @@ public File projectLevelWorkDir() {
* @return A key representing the module
*/
public String getModuleKey() {
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 "";
}

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

public boolean canSkipUnchangedFiles() throws ApiMismatchException {
Expand Down
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,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.java.utils;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.batch.bootstrap.ProjectDefinition;

public class ModuleMetadataUtils {

private ModuleMetadataUtils() {
// utility class
}

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

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

}
21 changes: 21 additions & 0 deletions java-frontend/src/main/java/org/sonar/java/utils/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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/
*/
@ParametersAreNonnullByDefault
package org.sonar.java.utils;

import javax.annotation.ParametersAreNonnullByDefault;

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,82 @@
/*
* 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.util.Optional;
import org.junit.jupiter.api.Test;
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;
import static org.sonar.java.TestUtils.mockProjectDefinition;

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 testNullProjectDefinition() {
var config = mockConfiguration();
var defaultModuleMetadata = new DefaultModuleMetadata(null, config);

assertThat(defaultModuleMetadata.moduleKey()).isEmpty();
}

@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 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;
}

}
13 changes: 13 additions & 0 deletions java-frontend/src/test/java/org/sonar/java/TestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@
import java.nio.file.Files;
import java.util.List;
import java.util.stream.Stream;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.TestInputFileBuilder;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

Expand Down Expand Up @@ -144,4 +146,15 @@ public static SonarComponents mockSonarComponents() {
when(mock.jspChecks()).thenReturn(List.of());
return mock;
}

public static 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;
}

}
Loading
Loading