Skip to content

Commit 63edb77

Browse files
SONARJAVA-6000 New public API to access module fully qualified key in ModuleScannerContext (#5394)
1 parent 4de3bce commit 63edb77

File tree

9 files changed

+108
-6
lines changed

9 files changed

+108
-6
lines changed

java-frontend/src/main/java/org/sonar/java/SonarComponents.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,15 @@ public String getModuleKey() {
531531
return ModuleMetadataUtils.getModuleKey(projectDefinition);
532532
}
533533

534+
/**
535+
* Returns an OS-independent key that should identify the module and its hierarchy within the project
536+
*
537+
* @return A fully-qualified key representing the module
538+
*/
539+
public Optional<String> getFullyQualifiedModuleKey() {
540+
return ModuleMetadataUtils.getFullyQualifiedModuleKey(projectDefinition);
541+
}
542+
534543
public boolean canSkipUnchangedFiles() throws ApiMismatchException {
535544
if (context == null) {
536545
return false;

java-frontend/src/main/java/org/sonar/java/model/DefaultModuleScannerContext.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.sonar.java.model;
1818

1919
import java.io.File;
20+
import java.util.Optional;
2021
import javax.annotation.CheckForNull;
2122
import javax.annotation.Nullable;
2223
import org.sonar.api.SonarProduct;
@@ -87,6 +88,11 @@ public String getModuleKey() {
8788
return sonarComponents.getModuleKey();
8889
}
8990

91+
@Override
92+
public Optional<String> getFullyQualifiedModuleKey() {
93+
return sonarComponents.getFullyQualifiedModuleKey();
94+
}
95+
9096
@CheckForNull
9197
@Override
9298
public SonarProduct sonarProduct() {

java-frontend/src/main/java/org/sonar/java/utils/ModuleMetadataUtils.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package org.sonar.java.utils;
1818

19+
import java.util.Optional;
1920
import javax.annotation.CheckForNull;
2021
import javax.annotation.Nullable;
2122
import org.sonar.api.batch.bootstrap.ProjectDefinition;
@@ -39,13 +40,33 @@ public static String getModuleKey(@Nullable ProjectDefinition projectDefinition)
3940
@CheckForNull
4041
public static ProjectDefinition getRootProject(@Nullable ProjectDefinition projectDefinition) {
4142
ProjectDefinition current = projectDefinition;
42-
if (current == null) {
43-
return null;
44-
}
45-
while (current.getParent() != null) {
43+
while (current != null && current.getParent() != null) {
4644
current = current.getParent();
4745
}
4846
return current;
4947
}
5048

49+
public static Optional<String> getFullyQualifiedModuleKey(@Nullable ProjectDefinition current) {
50+
StringBuilder builder = new StringBuilder();
51+
// we do not want to include root module as this is usually the sonar project key
52+
while (current != null && current.getParent() != null) {
53+
// get module key property
54+
var property = current.properties().get("sonar.moduleKey");
55+
if (property != null) {
56+
// prepend separator if not first module
57+
if (!builder.isEmpty()) {
58+
// as modules can have dots in names, separator should be :
59+
builder.insert(0, ":");
60+
}
61+
var leafModule = property.lastIndexOf(":") >= 0
62+
? property.substring(property.lastIndexOf(":") + 1)
63+
: property;
64+
builder.insert(0, leafModule);
65+
current = current.getParent();
66+
} else {
67+
break;
68+
}
69+
}
70+
return builder.isEmpty() ? Optional.empty() : Optional.of(builder.toString());
71+
}
5172
}

java-frontend/src/main/java/org/sonar/plugins/java/api/ModuleScannerContext.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.sonar.plugins.java.api;
1818

1919
import java.io.File;
20+
import java.util.Optional;
2021
import javax.annotation.CheckForNull;
2122
import org.sonar.api.SonarProduct;
2223
import org.sonar.api.SonarRuntime;
@@ -79,9 +80,17 @@ public interface ModuleScannerContext {
7980

8081
/**
8182
* @return A key that uniquely identifies the current module, provided that this project consists of multiple modules.
83+
* This key is not fully qualified, meaning that it does not contain the parent modules keys.
8284
*/
8385
String getModuleKey();
84-
86+
87+
/**
88+
* @return A key that uniquely identifies the current module, provided that this project consists of multiple modules.
89+
* This key is fully qualified, meaning that it contains the parent modules keys as well.
90+
* E.g. instead of "level4", it will return the full hierarchy key "level1:level2:level3:level4"
91+
*/
92+
Optional<String> getFullyQualifiedModuleKey();
93+
8594
/**
8695
* @return The Sonar product (SONARQUBE/SONARLINT) which forms the current execution context of the scan.
8796
* See also {@link SonarRuntime#getProduct()}.

java-frontend/src/test/java/org/sonar/java/SonarComponentsTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Arrays;
2828
import java.util.Collection;
2929
import java.util.Collections;
30+
import java.util.HashMap;
3031
import java.util.List;
3132
import java.util.Optional;
3233
import java.util.function.LongSupplier;
@@ -1308,20 +1309,29 @@ void should_return_generated_code_visitors() {
13081309
void moduleKey_empty() {
13091310
var sonarComponents = new SonarComponents(null, null, null, null, null, null);
13101311
assertThat(sonarComponents.getModuleKey()).isEmpty();
1312+
assertThat(sonarComponents.getFullyQualifiedModuleKey()).isEmpty();
13111313
}
13121314

13131315
@Test
13141316
void moduleKey_non_empty() {
13151317
var rootProj = mock(ProjectDefinition.class);
13161318
doReturn(new File("/foo/bar/proj")).when(rootProj).getBaseDir();
13171319
var parentModule = mock(ProjectDefinition.class);
1320+
var parentProperties = new HashMap<String, String>();
1321+
parentProperties.put("sonar.moduleKey", "proj1:pmodule");
1322+
when(parentModule.properties()).thenReturn(parentProperties);
13181323
doReturn(rootProj).when(parentModule).getParent();
13191324
var childModule = mock(ProjectDefinition.class);
1325+
var childProperties = new HashMap<String, String>();
1326+
childProperties.put("sonar.moduleKey", "proj1:pmodule:cmodule");
1327+
when(childModule.properties()).thenReturn(childProperties);
13201328
doReturn(new File("/foo/bar/proj/pmodule/cmodule")).when(childModule).getBaseDir();
13211329
doReturn(parentModule).when(childModule).getParent();
13221330

13231331
var sonarComponents = new SonarComponents(null, null, null, null, null, null, childModule);
13241332
assertThat(sonarComponents.getModuleKey()).isEqualTo("pmodule/cmodule");
1333+
assertThat(sonarComponents.getFullyQualifiedModuleKey()).isPresent();
1334+
assertThat(sonarComponents.getFullyQualifiedModuleKey()).contains("pmodule:cmodule");
13251335
}
13261336

13271337
@Rule(key = "jsp")

java-frontend/src/test/java/org/sonar/java/TestUtils.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.File;
2020
import java.io.IOException;
2121
import java.nio.file.Files;
22+
import java.util.HashMap;
2223
import java.util.List;
2324
import java.util.stream.Stream;
2425
import org.sonar.api.batch.bootstrap.ProjectDefinition;
@@ -157,4 +158,24 @@ public static ProjectDefinition mockProjectDefinition() {
157158
return childModule;
158159
}
159160

161+
public static ProjectDefinition mockProjectDefinitionWithModuleKeys() {
162+
var rootProj = mock(ProjectDefinition.class);
163+
doReturn(new File("/foo/bar/proj")).when(rootProj).getBaseDir();
164+
165+
var child1Module = mock(ProjectDefinition.class);
166+
doReturn(new File("/foo/bar/proj/pmodule/cmodule")).when(child1Module).getBaseDir();
167+
doReturn(rootProj).when(child1Module).getParent();
168+
var child1Properties = new HashMap<String, String>();
169+
child1Properties.put("sonar.moduleKey", "propj:module1");
170+
when(child1Module.properties()).thenReturn(child1Properties);
171+
172+
var child2Module = mock(ProjectDefinition.class);
173+
doReturn(new File("/foo/bar/proj/pmodule/cmodule/c2module")).when(child2Module).getBaseDir();
174+
doReturn(child1Module).when(child2Module).getParent();
175+
var child2Properties = new HashMap<String, String>();
176+
child2Properties.put("sonar.moduleKey", "module2");
177+
when(child2Module.properties()).thenReturn(child2Properties);
178+
179+
return child2Module;
180+
}
160181
}

java-frontend/src/test/java/org/sonar/java/model/DefaultInputFileScannerContextTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.File;
2020
import java.util.Arrays;
21+
import java.util.Optional;
2122
import org.junit.jupiter.api.BeforeEach;
2223
import org.junit.jupiter.api.Test;
2324
import org.sonar.api.batch.fs.InputComponent;
@@ -78,6 +79,16 @@ void getModuleKey() {
7879
assertThat(ctx.getModuleKey()).isEqualTo(moduleKey);
7980
}
8081

82+
@Test
83+
void getFullyQualifiedModuleKey() {
84+
var moduleKey = Optional.of("some/random/module/key");
85+
doReturn(moduleKey).when(sonarComponents).getFullyQualifiedModuleKey();
86+
var ctx = new DefaultJavaFileScannerContext(null, null, null, sonarComponents, null, false, false);
87+
assertThat(ctx.getFullyQualifiedModuleKey())
88+
.isPresent()
89+
.hasValue(moduleKey.get());
90+
}
91+
8192
private SonarComponents createSonarComponentsMock() {
8293
SonarComponents specificSonarComponents = mock(SonarComponents.class);
8394
doAnswer(invocation -> {

java-frontend/src/test/java/org/sonar/java/utils/ModuleMetadataUtilsTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.junit.jupiter.api.Assertions.assertNull;
2323
import static org.junit.jupiter.api.Assertions.assertSame;
2424
import static org.sonar.java.TestUtils.mockProjectDefinition;
25+
import static org.sonar.java.TestUtils.mockProjectDefinitionWithModuleKeys;
2526

2627
class ModuleMetadataUtilsTest {
2728

@@ -30,8 +31,18 @@ void getModuleKey() {
3031
var projectDefinition = mockProjectDefinition();
3132
assertThat(ModuleMetadataUtils.getModuleKey(projectDefinition)).isEqualTo("pmodule/cmodule");
3233
assertThat(ModuleMetadataUtils.getModuleKey(null)).isEmpty();
34+
assertThat(ModuleMetadataUtils.getFullyQualifiedModuleKey(projectDefinition)).isEmpty();
35+
assertThat(ModuleMetadataUtils.getFullyQualifiedModuleKey(null)).isEmpty();
3336
}
34-
37+
38+
@Test
39+
void getFullyQualifiedModuleKey() {
40+
var projectDefinition = mockProjectDefinitionWithModuleKeys();
41+
assertThat(ModuleMetadataUtils.getFullyQualifiedModuleKey(projectDefinition)).isPresent();
42+
assertThat(ModuleMetadataUtils.getFullyQualifiedModuleKey(projectDefinition)).contains("module1:module2");
43+
assertThat(ModuleMetadataUtils.getFullyQualifiedModuleKey(null)).isEmpty();
44+
}
45+
3546
@Test
3647
void getRootProject() {
3748
var projectDefinition = mockProjectDefinition();

sonar-java-plugin/src/main/resources/static/documentation.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ The tutorial [Writing Custom Java Rules 101](https://redirect.sonarsource.com/do
151151

152152
### API changes
153153

154+
#### **8.22**
155+
156+
* New method: `ModuleMetadataUtils#getFullyQualifiedModuleKey(@Nullable ProjectDefinition current)`.
157+
154158
#### **8.19**
155159

156160
* New methods: `SymbolMetadata#symbolAnnotations()` and `SymbolMetadata#parametersMetadata()`.

0 commit comments

Comments
 (0)