Skip to content

Commit 1877c9a

Browse files
SONARJAVA-5670 Make SonarComponents in JavaFrontend not @nullable. (#5234)
Null SonarComponents is used only tests. JavaFrontend is created in JavaSensor, which calls a method on SonarComponents before it is passed the the constructor, therefore the parameter can never be null in production. Replacing the nulls in tests with mocks will simplify the production code.
1 parent 5197b88 commit 1877c9a

File tree

9 files changed

+62
-64
lines changed

9 files changed

+62
-64
lines changed

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

Lines changed: 18 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import org.slf4j.Logger;
3232
import org.slf4j.LoggerFactory;
3333
import org.sonar.api.batch.fs.InputFile;
34-
import org.sonar.java.annotations.VisibleForTesting;
3534
import org.sonar.java.ast.JavaAstScanner;
3635
import org.sonar.java.ast.visitors.FileLinesVisitor;
3736
import org.sonar.java.ast.visitors.SyntaxHighlighterVisitor;
@@ -59,7 +58,7 @@ public class JavaFrontend {
5958
private final JavaAstScanner astScannerForTests;
6059
private final JavaAstScanner astScannerForGeneratedFiles;
6160

62-
public JavaFrontend(JavaVersion javaVersion, @Nullable SonarComponents sonarComponents, Measurer measurer,
61+
public JavaFrontend(JavaVersion javaVersion, SonarComponents sonarComponents, Measurer measurer,
6362
JavaResourceLocator javaResourceLocator, @Nullable SonarJavaIssueFilter postAnalysisIssueFilter, JavaCheck... visitors) {
6463
this.javaVersion = javaVersion;
6564
this.sonarComponents = sonarComponents;
@@ -77,25 +76,17 @@ public JavaFrontend(JavaVersion javaVersion, @Nullable SonarComponents sonarComp
7776
List<JavaCheck> testCodeVisitors = new ArrayList<>(commonVisitors);
7877
testCodeVisitors.add(measurer.new TestFileMeasurer());
7978

80-
List<File> classpath = new ArrayList<>();
81-
List<File> testClasspath = new ArrayList<>();
82-
List<JavaCheck> jspCodeVisitors = new ArrayList<>();
83-
List<File> jspClasspath = new ArrayList<>();
84-
boolean inAndroidContext = false;
85-
86-
if (sonarComponents != null) {
87-
if (!sonarComponents.isSonarLintContext()) {
88-
codeVisitors.add(new FileLinesVisitor(sonarComponents));
89-
codeVisitors.add(new SyntaxHighlighterVisitor(sonarComponents));
90-
testCodeVisitors.add(new SyntaxHighlighterVisitor(sonarComponents));
91-
}
92-
classpath = sonarComponents.getJavaClasspath();
93-
testClasspath = sonarComponents.getJavaTestClasspath();
94-
jspClasspath = sonarComponents.getJspClasspath();
95-
testCodeVisitors.addAll(sonarComponents.testChecks());
96-
jspCodeVisitors = sonarComponents.jspChecks();
97-
inAndroidContext = sonarComponents.inAndroidContext();
79+
if (!sonarComponents.isSonarLintContext()) {
80+
codeVisitors.add(new FileLinesVisitor(sonarComponents));
81+
codeVisitors.add(new SyntaxHighlighterVisitor(sonarComponents));
82+
testCodeVisitors.add(new SyntaxHighlighterVisitor(sonarComponents));
9883
}
84+
List<File> classpath = sonarComponents.getJavaClasspath();
85+
List<File> testClasspath = sonarComponents.getJavaTestClasspath();
86+
List<File> jspClasspath = sonarComponents.getJspClasspath();
87+
testCodeVisitors.addAll(sonarComponents.testChecks());
88+
List<JavaCheck> jspCodeVisitors = sonarComponents.jspChecks();
89+
boolean inAndroidContext = sonarComponents.inAndroidContext();
9990

10091
globalClasspath = Stream.of(classpath, testClasspath, jspClasspath)
10192
.flatMap(Collection::stream).distinct().toList();
@@ -113,11 +104,6 @@ public JavaFrontend(JavaVersion javaVersion, @Nullable SonarComponents sonarComp
113104
astScannerForGeneratedFiles.setVisitorBridge(new VisitorsBridge(jspCodeVisitors, jspClasspath, sonarComponents, javaVersion, inAndroidContext));
114105
}
115106

116-
@VisibleForTesting
117-
boolean analysisCancelled() {
118-
return sonarComponents != null && sonarComponents.analysisCancelled();
119-
}
120-
121107
public void scan(Iterable<InputFile> sourceFiles, Iterable<InputFile> testFiles, Iterable<? extends InputFile> generatedFiles) {
122108
if (canOptimizeScanning()) {
123109
long successfullyScanned = 0L;
@@ -147,13 +133,12 @@ public void scan(Iterable<InputFile> sourceFiles, Iterable<InputFile> testFiles,
147133
}
148134

149135
// SonarLint is not compatible with batch mode, it needs InputFile#contents() and batch mode use InputFile#absolutePath()
150-
boolean isSonarLint = sonarComponents != null && sonarComponents.isSonarLintContext();
151-
boolean fileByFileMode = isSonarLint || isFileByFileEnabled();
136+
boolean fileByFileMode = sonarComponents.isSonarLintContext() || sonarComponents.isFileByFileEnabled();
152137
if (fileByFileMode) {
153138
scanAndMeasureTask(sourceFiles, astScanner::scan, "Main");
154139
scanAndMeasureTask(testFiles, astScannerForTests::scan, "Test");
155140
scanAndMeasureTask(generatedFiles, astScannerForGeneratedFiles::scan, "Generated");
156-
} else if (isAutoScan()) {
141+
} else if (sonarComponents.isAutoScan()) {
157142
scanAsBatch(new AutoScanBatchContext(), sourceFiles, testFiles);
158143
} else {
159144
scanAsBatch(new DefaultBatchModeContext(astScanner, "Main"), sourceFiles);
@@ -204,7 +189,7 @@ private void scanAsBatch(BatchModeContext context, Iterable<? extends InputFile>
204189
private void scanInBatches(BatchModeContext context, List<InputFile> allInputFiles) {
205190
String logUsingBatch = String.format("Using ECJ batch to parse %d %s java source files", allInputFiles.size(), context.descriptor());
206191
AnalysisProgress analysisProgress = new AnalysisProgress(allInputFiles.size());
207-
long batchModeSizeInKB = getBatchModeSizeInKB();
192+
long batchModeSizeInKB = sonarComponents.getBatchModeSizeInKB();
208193
if (batchModeSizeInKB < 0L || batchModeSizeInKB >= Long.MAX_VALUE / 1_000L) {
209194
LOG.info("{} in a single batch.", logUsingBatch);
210195
scanBatch(context, allInputFiles, analysisProgress);
@@ -222,10 +207,9 @@ private void scanInBatches(BatchModeContext context, List<InputFile> allInputFil
222207
private <T extends InputFile> void scanBatch(BatchModeContext context, List<T> batchFiles, AnalysisProgress analysisProgress) {
223208
analysisProgress.startBatch(batchFiles.size());
224209
Set<Runnable> environmentsCleaners = new HashSet<>();
225-
boolean shouldIgnoreUnnamedModuleForSplitPackage = sonarComponents!= null && sonarComponents.shouldIgnoreUnnamedModuleForSplitPackage();
226210
JParserConfig.Mode.BATCH
227-
.create(javaVersion, context.getClasspath(), shouldIgnoreUnnamedModuleForSplitPackage)
228-
.parse(batchFiles, this::analysisCancelled, analysisProgress, (input, result) -> scanAsBatchCallback(input, result, context, environmentsCleaners));
211+
.create(javaVersion, context.getClasspath(), sonarComponents.shouldIgnoreUnnamedModuleForSplitPackage())
212+
.parse(batchFiles, sonarComponents::analysisCancelled, analysisProgress, (input, result) -> scanAsBatchCallback(input, result, context, environmentsCleaners));
229213
// Due to a bug in ECJ, JAR files remain locked after the analysis on Windows, we unlock them manually, at the end of each batches. See SONARJAVA-3609.
230214
environmentsCleaners.forEach(Runnable::run);
231215
analysisProgress.endBatch();
@@ -320,28 +304,13 @@ public void endOfAnalysis() {
320304

321305
}
322306

323-
@VisibleForTesting
324-
boolean isFileByFileEnabled() {
325-
return sonarComponents != null && sonarComponents.isFileByFileEnabled();
326-
}
327-
328-
@VisibleForTesting
329-
boolean isAutoScan() {
330-
return sonarComponents != null && sonarComponents.isAutoScan();
331-
}
332-
333-
@VisibleForTesting
334-
long getBatchModeSizeInKB() {
335-
return sonarComponents == null ? -1L : sonarComponents.getBatchModeSizeInKB();
336-
}
337-
338307
private boolean isCacheEnabled() {
339-
return sonarComponents != null && CacheContextImpl.of(sonarComponents).isCacheEnabled();
308+
return CacheContextImpl.of(sonarComponents).isCacheEnabled();
340309
}
341310

342311
private boolean canOptimizeScanning() {
343312
try {
344-
return sonarComponents != null && sonarComponents.canSkipUnchangedFiles() && isCacheEnabled();
313+
return sonarComponents.canSkipUnchangedFiles() && isCacheEnabled();
345314
} catch (ApiMismatchException e) {
346315
return false;
347316
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,5 +687,5 @@ public SonarLintCache sonarLintCache() {
687687
public Configuration getConfiguration() {
688688
return context.config();
689689
}
690-
690+
691691
}

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.sonar.api.scan.issue.filter.IssueFilterChain;
5252
import org.sonar.api.testfixtures.log.LogTesterJUnit5;
5353
import org.sonar.api.utils.Version;
54+
import org.sonar.java.caching.CacheContextImpl;
5455
import org.sonar.java.classpath.ClasspathForMain;
5556
import org.sonar.java.classpath.ClasspathForTest;
5657
import org.sonar.java.exceptions.ApiMismatchException;
@@ -79,6 +80,7 @@
7980
import static org.mockito.Mockito.verify;
8081
import static org.mockito.Mockito.when;
8182
import static org.sonar.java.InputFileUtils.addFile;
83+
import static org.sonar.java.TestUtils.mockSonarComponents;
8284

8385
@EnableRuleMigrationSupport
8486
class JavaFrontendTest {
@@ -179,7 +181,7 @@ void scanning_empty_project_should_be_logged_in_file_by_file_sonarlint() {
179181

180182
@Test
181183
void scanning_empty_project_should_be_logged_in_batch() {
182-
JavaFrontend frontend = new JavaFrontend(new JavaVersionImpl(), sonarComponents, new Measurer(sensorContext, mock(NoSonarFilter.class)), mock(JavaResourceLocator.class), mainCodeIssueScannerAndFilter);
184+
JavaFrontend frontend = new JavaFrontend(new JavaVersionImpl(), mockSonarComponents(), new Measurer(sensorContext, mock(NoSonarFilter.class)), mock(JavaResourceLocator.class), mainCodeIssueScannerAndFilter);
183185
frontend.scan(Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
184186

185187
assertThat(logTester.logs(Level.INFO)).containsExactly(
@@ -405,7 +407,7 @@ void test_scan_logs_when_caching_is_disabled_when_sonar_components_is_null() {
405407

406408
JavaFrontend frontend = new JavaFrontend(
407409
new JavaVersionImpl(),
408-
null,
410+
mockSonarComponents(),
409411
mock(Measurer.class),
410412
mock(JavaResourceLocator.class),
411413
mainCodeIssueScannerAndFilter
@@ -422,13 +424,18 @@ void test_scan_logs_when_caching_is_disabled_when_sonar_components_is_null() {
422424
);
423425
}
424426

427+
/**
428+
* Ensure that changes the mock, which may be needed when it is shared with other tests,
429+
* do not affect JavaFrontendTest.
430+
*/
425431
@Test
426-
void test_getters_with_null_sonarComponents() {
427-
JavaFrontend frontend = new JavaFrontend(new JavaVersionImpl(), null, new Measurer(sensorContext, mock(NoSonarFilter.class)), mock(JavaResourceLocator.class), mainCodeIssueScannerAndFilter);
428-
assertThat(frontend.isAutoScan()).isFalse();
429-
assertThat(frontend.isFileByFileEnabled()).isFalse();
430-
assertThat(frontend.analysisCancelled()).isFalse();
431-
assertThat(frontend.getBatchModeSizeInKB()).isEqualTo(-1L);
432+
void test_validate_mockSonarComponents() {
433+
SonarComponents mock = mockSonarComponents();
434+
assertThat(mock.isAutoScan()).isFalse();
435+
assertThat(mock.isFileByFileEnabled()).isFalse();
436+
assertThat(mock.analysisCancelled()).isFalse();
437+
assertThat(mock.getBatchModeSizeInKB()).isEqualTo(-1L);
438+
assertThat(CacheContextImpl.of(mock).isCacheEnabled()).isFalse();
432439
}
433440

434441
@Test

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import static org.assertj.core.api.Assertions.assertThat;
3333
import static org.mockito.Mockito.mock;
34+
import static org.sonar.java.TestUtils.mockSonarComponents;
3435

3536
class JavaVersionAwareVisitorTest {
3637

@@ -77,7 +78,7 @@ void no_java_version_matching() {
7778

7879
private void checkIssues(JavaVersion version) {
7980
messages.clear();
80-
JavaFrontend frontend = new JavaFrontend(version, null, mock(Measurer.class), null, null, javaChecks);
81+
JavaFrontend frontend = new JavaFrontend(version, mockSonarComponents(), mock(Measurer.class), null, null, javaChecks);
8182
frontend.scan(Collections.singletonList(TestUtils.inputFile("src/test/files/JavaVersionAwareChecks.java")),
8283
Collections.emptyList(), Collections.emptyList());
8384
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import static org.assertj.core.api.Assertions.assertThat;
3131
import static org.mockito.Mockito.mock;
32+
import static org.sonar.java.TestUtils.mockSonarComponents;
3233

3334
class MeasurerTest {
3435

@@ -92,7 +93,7 @@ private void checkMetric(String filename, String metric, Number expectedValue) {
9293
context.fileSystem().add(inputFile);
9394

9495
Measurer measurer = new Measurer(context, mock(NoSonarFilter.class));
95-
JavaFrontend frontend = new JavaFrontend(new JavaVersionImpl(), null, measurer, null, null, new JavaCheck[0]);
96+
JavaFrontend frontend = new JavaFrontend(new JavaVersionImpl(), mockSonarComponents(), measurer, null, null);
9697

9798
frontend.scan(Collections.singletonList(inputFile), Collections.emptyList(), Collections.emptyList());
9899

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.sonar.plugins.java.api.JavaResourceLocator;
3535

3636
import static org.mockito.Mockito.mock;
37+
import static org.sonar.java.TestUtils.mockSonarComponents;
3738

3839
public abstract class MeasurerTester {
3940

@@ -50,7 +51,7 @@ public void setUp() {
5051
.forEach(fs::add);
5152

5253
Measurer measurer = new Measurer(context, mock(NoSonarFilter.class));
53-
JavaFrontend frontend = new JavaFrontend(new JavaVersionImpl(), null, measurer, mock(JavaResourceLocator.class), null, new JavaCheck[0]);
54+
JavaFrontend frontend = new JavaFrontend(new JavaVersionImpl(), mockSonarComponents(), measurer, mock(JavaResourceLocator.class), null);
5455
List<InputFile> files = StreamSupport.stream(fs.inputFiles().spliterator(), false).toList();
5556
frontend.scan(files, Collections.emptyList(), Collections.emptyList());
5657
}

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@
2121
import java.nio.file.Files;
2222
import java.util.List;
2323
import java.util.stream.Stream;
24-
2524
import org.sonar.api.batch.fs.InputFile;
2625
import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
2726

2827
import static java.nio.charset.StandardCharsets.UTF_8;
2928
import static org.junit.jupiter.api.Assertions.assertTrue;
29+
import static org.mockito.Mockito.mock;
30+
import static org.mockito.Mockito.when;
3031

3132
public class TestUtils {
3233
private TestUtils() {
@@ -127,4 +128,20 @@ private static List<String> filterOutAnalysisProgressLogLines(Stream<String> log
127128
public static List<String> filterOutAnalysisProgressLogLines(List<String> logs) {
128129
return filterOutAnalysisProgressLogLines(logs.stream());
129130
}
131+
132+
/**
133+
* Creates a Mockito test double for {@link SonarComponents}, pre-configured to avoid
134+
* nulls in unit tests and remove the need for null checks in production code.
135+
*/
136+
public static SonarComponents mockSonarComponents() {
137+
SonarComponents mock = mock(SonarComponents.class);
138+
when(mock.isSonarLintContext()).thenReturn(true);
139+
when(mock.getBatchModeSizeInKB()).thenReturn(-1L);
140+
when(mock.getJavaClasspath()).thenReturn(List.of());
141+
when(mock.getJavaTestClasspath()).thenReturn(List.of());
142+
when(mock.getJspClasspath()).thenReturn(List.of());
143+
when(mock.testChecks()).thenReturn(List.of());
144+
when(mock.jspChecks()).thenReturn(List.of());
145+
return mock;
146+
}
130147
}

java-frontend/src/test/java/org/sonar/java/ast/visitors/FileLinesVisitorTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import static org.mockito.Mockito.mock;
3535
import static org.mockito.Mockito.verify;
3636
import static org.mockito.Mockito.when;
37+
import static org.sonar.java.TestUtils.mockSonarComponents;
3738

3839
class FileLinesVisitorTest {
3940

@@ -50,7 +51,7 @@ private void checkLines(String filename, FileLinesContext context) {
5051
SonarComponents sonarComponents = mock(SonarComponents.class);
5152
when(sonarComponents.fileLinesContextFor(Mockito.any(InputFile.class))).thenReturn(context);
5253

53-
JavaFrontend frontend = new JavaFrontend(new JavaVersionImpl(), null, mock(Measurer.class), null, null, new FileLinesVisitor(sonarComponents));
54+
JavaFrontend frontend = new JavaFrontend(new JavaVersionImpl(), mockSonarComponents(), mock(Measurer.class), null, null, new FileLinesVisitor(sonarComponents));
5455

5556
frontend.scan(Collections.singletonList(inputFile), Collections.emptyList(), Collections.emptyList());
5657
}

java-frontend/src/test/java/org/sonar/java/ast/visitors/SyntaxHighlighterVisitorTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import static org.mockito.Mockito.never;
4848
import static org.mockito.Mockito.spy;
4949
import static org.mockito.Mockito.verify;
50+
import static org.sonar.java.TestUtils.mockSonarComponents;
5051

5152
@EnableRuleMigrationSupport
5253
class SyntaxHighlighterVisitorTest {
@@ -254,7 +255,7 @@ void sealed_classes() {
254255

255256
private void scan(InputFile inputFile) {
256257
JavaVersion javaVersion = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION;
257-
JavaFrontend frontend = new JavaFrontend(javaVersion, null, mock(Measurer.class), null, null, syntaxHighlighterVisitor);
258+
JavaFrontend frontend = new JavaFrontend(javaVersion, mockSonarComponents(), mock(Measurer.class), null, null, syntaxHighlighterVisitor);
258259
frontend.scan(Collections.singletonList(inputFile), Collections.emptyList(), Collections.emptyList());
259260
}
260261

0 commit comments

Comments
 (0)