From 9e1f7bcc56b3c9b5a94a683f723d224d3381a757 Mon Sep 17 00:00:00 2001 From: Alban Auzeill Date: Wed, 16 Jul 2025 18:10:37 +0200 Subject: [PATCH 1/2] SONARJAVA-5698 Report Eclipse parser type errors --- .../test/java/com/sonar/it/java/JspTest.java | 3 ++ .../sonar/it/java/suite/JavaTutorialTest.java | 1 + .../org/sonar/java/ast/JavaAstScanner.java | 4 ++- .../sonar/java/telemetry/TelemetryKey.java | 36 +++++++++++++------ .../sonar/plugins/java/JavaSensorTest.java | 17 +++++++-- 5 files changed, 47 insertions(+), 14 deletions(-) diff --git a/its/plugin/tests/src/test/java/com/sonar/it/java/JspTest.java b/its/plugin/tests/src/test/java/com/sonar/it/java/JspTest.java index c2e07099594..97963e5c59e 100644 --- a/its/plugin/tests/src/test/java/com/sonar/it/java/JspTest.java +++ b/its/plugin/tests/src/test/java/com/sonar/it/java/JspTest.java @@ -96,10 +96,13 @@ public void should_transpile_jsp() throws Exception { .matches(patternWithLiteralDot(""" Telemetry java.analysis.generated.success.size_chars: \\d{5} Telemetry java.analysis.generated.success.time_ms: \\d+ + Telemetry java.analysis.generated.success.type_error_count: 0 Telemetry java.analysis.main.success.size_chars: 969 Telemetry java.analysis.main.success.time_ms: \\d+ + Telemetry java.analysis.main.success.type_error_count: 0 Telemetry java.analysis.test.success.size_chars: 20 Telemetry java.analysis.test.success.time_ms: \\d+ + Telemetry java.analysis.test.success.type_error_count: 0 Telemetry java.dependency.lombok: absent Telemetry java.dependency.spring-boot: absent Telemetry java.dependency.spring-web: absent diff --git a/its/plugin/tests/src/test/java/com/sonar/it/java/suite/JavaTutorialTest.java b/its/plugin/tests/src/test/java/com/sonar/it/java/suite/JavaTutorialTest.java index f8396bd5211..de215447eb2 100644 --- a/its/plugin/tests/src/test/java/com/sonar/it/java/suite/JavaTutorialTest.java +++ b/its/plugin/tests/src/test/java/com/sonar/it/java/suite/JavaTutorialTest.java @@ -75,6 +75,7 @@ private void executeAndAssertBuild(MavenBuild build, String projectKey) { .matches(patternWithLiteralDot(""" Telemetry java.analysis.main.success.size_chars: \\d{4} Telemetry java.analysis.main.success.time_ms: \\d+ + Telemetry java.analysis.main.success.type_error_count: 0 Telemetry java.dependency.lombok: absent Telemetry java.dependency.spring-boot: absent Telemetry java.dependency.spring-web: 5.3.18 diff --git a/java-frontend/src/main/java/org/sonar/java/ast/JavaAstScanner.java b/java-frontend/src/main/java/org/sonar/java/ast/JavaAstScanner.java index 0aec8d0619c..11da05ea6b8 100644 --- a/java-frontend/src/main/java/org/sonar/java/ast/JavaAstScanner.java +++ b/java-frontend/src/main/java/org/sonar/java/ast/JavaAstScanner.java @@ -168,9 +168,11 @@ public void simpleScan(InputFile inputFile, JParserConfig.Result result, Consume modifyCompilationUnit.accept(ast); visitor.visitFile(ast, sonarComponents != null && sonarComponents.fileCanBeSkipped(inputFile)); String path = inputFile.toString(); - collectUndefinedTypes(path, ast.sema.undefinedTypes()); + Set undefinedTypes = ast.sema.undefinedTypes(); + collectUndefinedTypes(path, undefinedTypes); cleanUp.accept(ast); telemetryAnalysisKeys = javaAnalysisKeys.success(); + telemetry.aggregateAsCounter(javaAnalysisKeys.success().typeErrorCountKey(), undefinedTypes.size()); } catch (RecognitionException e) { checkInterrupted(e); LOG.error(String.format(LOG_ERROR_UNABLE_TO_PARSE_FILE, inputFile)); diff --git a/java-frontend/src/main/java/org/sonar/java/telemetry/TelemetryKey.java b/java-frontend/src/main/java/org/sonar/java/telemetry/TelemetryKey.java index c4f4df9a775..00b68d99eb9 100644 --- a/java-frontend/src/main/java/org/sonar/java/telemetry/TelemetryKey.java +++ b/java-frontend/src/main/java/org/sonar/java/telemetry/TelemetryKey.java @@ -25,18 +25,21 @@ public enum TelemetryKey { JAVA_IS_AUTOSCAN("java.is_autoscan"), JAVA_ANALYSIS_MAIN_SUCCESS_SIZE_CHARS("java.analysis.main.success.size_chars"), JAVA_ANALYSIS_MAIN_SUCCESS_TIME_MS("java.analysis.main.success.time_ms"), + JAVA_ANALYSIS_MAIN_SUCCESS_TYPE_ERROR_COUNT("java.analysis.main.success.type_error_count"), JAVA_ANALYSIS_MAIN_PARSE_ERRORS_SIZE_CHARS("java.analysis.main.parse_errors.size_chars"), JAVA_ANALYSIS_MAIN_PARSE_ERRORS_TIME_MS("java.analysis.main.parse_errors.time_ms"), JAVA_ANALYSIS_MAIN_EXCEPTIONS_SIZE_CHARS("java.analysis.main.exceptions.size_chars"), JAVA_ANALYSIS_MAIN_EXCEPTIONS_TIME_MS("java.analysis.main.exceptions.time_ms"), JAVA_ANALYSIS_TEST_SUCCESS_SIZE_CHARS("java.analysis.test.success.size_chars"), JAVA_ANALYSIS_TEST_SUCCESS_TIME_MS("java.analysis.test.success.time_ms"), + JAVA_ANALYSIS_TEST_SUCCESS_TYPE_ERROR_COUNT("java.analysis.test.success.type_error_count"), JAVA_ANALYSIS_TEST_PARSE_ERRORS_SIZE_CHARS("java.analysis.test.parse_errors.size_chars"), JAVA_ANALYSIS_TEST_PARSE_ERRORS_TIME_MS("java.analysis.test.parse_errors.time_ms"), JAVA_ANALYSIS_TEST_EXCEPTIONS_SIZE_CHARS("java.analysis.test.exceptions.size_chars"), JAVA_ANALYSIS_TEST_EXCEPTIONS_TIME_MS("java.analysis.test.exceptions.time_ms"), JAVA_ANALYSIS_GENERATED_SUCCESS_SIZE_CHARS("java.analysis.generated.success.size_chars"), JAVA_ANALYSIS_GENERATED_SUCCESS_TIME_MS("java.analysis.generated.success.time_ms"), + JAVA_ANALYSIS_GENERATED_SUCCESS_TYPE_ERROR_COUNT("java.analysis.generated.success.type_error_count"), JAVA_ANALYSIS_GENERATED_PARSE_ERRORS_SIZE_CHARS("java.analysis.generated.parse_errors.size_chars"), JAVA_ANALYSIS_GENERATED_PARSE_ERRORS_TIME_MS("java.analysis.generated.parse_errors.time_ms"), JAVA_ANALYSIS_GENERATED_EXCEPTIONS_SIZE_CHARS("java.analysis.generated.exceptions.size_chars"), @@ -48,26 +51,37 @@ public enum TelemetryKey { JAVA_DEPENDENCY_SPRING_BOOT("java.dependency.spring-boot"), JAVA_DEPENDENCY_SPRING_WEB("java.dependency.spring-web"); - public record SpeedKeys(TelemetryKey sizeCharsKey, TelemetryKey timeMsKey) { + public interface SpeedKeys { + TelemetryKey sizeCharsKey(); + TelemetryKey timeMsKey(); } - public record JavaAnalysisKeys(SpeedKeys success, SpeedKeys parseErrors, SpeedKeys exceptions) { + public record SizeAndTimeKeys(TelemetryKey sizeCharsKey, TelemetryKey timeMsKey) implements SpeedKeys { + } + + public record SizeTimeAndTypeErrorKeys(TelemetryKey sizeCharsKey, TelemetryKey timeMsKey, TelemetryKey typeErrorCountKey) implements SpeedKeys { + } + + public record JavaAnalysisKeys(SizeTimeAndTypeErrorKeys success, SpeedKeys parseErrors, SpeedKeys exceptions) { } public static final JavaAnalysisKeys JAVA_ANALYSIS_MAIN = new JavaAnalysisKeys( - new SpeedKeys(JAVA_ANALYSIS_MAIN_SUCCESS_SIZE_CHARS, JAVA_ANALYSIS_MAIN_SUCCESS_TIME_MS), - new SpeedKeys(JAVA_ANALYSIS_MAIN_PARSE_ERRORS_SIZE_CHARS, JAVA_ANALYSIS_MAIN_PARSE_ERRORS_TIME_MS), - new SpeedKeys(JAVA_ANALYSIS_MAIN_EXCEPTIONS_SIZE_CHARS, JAVA_ANALYSIS_MAIN_EXCEPTIONS_TIME_MS)); + new SizeTimeAndTypeErrorKeys(JAVA_ANALYSIS_MAIN_SUCCESS_SIZE_CHARS, JAVA_ANALYSIS_MAIN_SUCCESS_TIME_MS, + JAVA_ANALYSIS_MAIN_SUCCESS_TYPE_ERROR_COUNT), + new SizeAndTimeKeys(JAVA_ANALYSIS_MAIN_PARSE_ERRORS_SIZE_CHARS, JAVA_ANALYSIS_MAIN_PARSE_ERRORS_TIME_MS), + new SizeAndTimeKeys(JAVA_ANALYSIS_MAIN_EXCEPTIONS_SIZE_CHARS, JAVA_ANALYSIS_MAIN_EXCEPTIONS_TIME_MS)); public static final JavaAnalysisKeys JAVA_ANALYSIS_TEST = new JavaAnalysisKeys( - new SpeedKeys(JAVA_ANALYSIS_TEST_SUCCESS_SIZE_CHARS, JAVA_ANALYSIS_TEST_SUCCESS_TIME_MS), - new SpeedKeys(JAVA_ANALYSIS_TEST_PARSE_ERRORS_SIZE_CHARS, JAVA_ANALYSIS_TEST_PARSE_ERRORS_TIME_MS), - new SpeedKeys(JAVA_ANALYSIS_TEST_EXCEPTIONS_SIZE_CHARS, JAVA_ANALYSIS_TEST_EXCEPTIONS_TIME_MS)); + new SizeTimeAndTypeErrorKeys(JAVA_ANALYSIS_TEST_SUCCESS_SIZE_CHARS, JAVA_ANALYSIS_TEST_SUCCESS_TIME_MS, + JAVA_ANALYSIS_TEST_SUCCESS_TYPE_ERROR_COUNT), + new SizeAndTimeKeys(JAVA_ANALYSIS_TEST_PARSE_ERRORS_SIZE_CHARS, JAVA_ANALYSIS_TEST_PARSE_ERRORS_TIME_MS), + new SizeAndTimeKeys(JAVA_ANALYSIS_TEST_EXCEPTIONS_SIZE_CHARS, JAVA_ANALYSIS_TEST_EXCEPTIONS_TIME_MS)); public static final JavaAnalysisKeys JAVA_ANALYSIS_GENERATED = new JavaAnalysisKeys( - new SpeedKeys(JAVA_ANALYSIS_GENERATED_SUCCESS_SIZE_CHARS, JAVA_ANALYSIS_GENERATED_SUCCESS_TIME_MS), - new SpeedKeys(JAVA_ANALYSIS_GENERATED_PARSE_ERRORS_SIZE_CHARS, JAVA_ANALYSIS_GENERATED_PARSE_ERRORS_TIME_MS), - new SpeedKeys(JAVA_ANALYSIS_GENERATED_EXCEPTIONS_SIZE_CHARS, JAVA_ANALYSIS_GENERATED_EXCEPTIONS_TIME_MS)); + new SizeTimeAndTypeErrorKeys(JAVA_ANALYSIS_GENERATED_SUCCESS_SIZE_CHARS, JAVA_ANALYSIS_GENERATED_SUCCESS_TIME_MS, + JAVA_ANALYSIS_GENERATED_SUCCESS_TYPE_ERROR_COUNT), + new SizeAndTimeKeys(JAVA_ANALYSIS_GENERATED_PARSE_ERRORS_SIZE_CHARS, JAVA_ANALYSIS_GENERATED_PARSE_ERRORS_TIME_MS), + new SizeAndTimeKeys(JAVA_ANALYSIS_GENERATED_EXCEPTIONS_SIZE_CHARS, JAVA_ANALYSIS_GENERATED_EXCEPTIONS_TIME_MS)); private final String key; diff --git a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaSensorTest.java b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaSensorTest.java index 8b682033816..b9081fa7033 100644 --- a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaSensorTest.java +++ b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaSensorTest.java @@ -33,6 +33,7 @@ import org.slf4j.event.Level; import org.sonar.api.SonarEdition; import org.sonar.api.SonarQubeSide; +import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultFileSystem; import org.sonar.api.batch.fs.internal.DefaultInputFile; @@ -123,6 +124,7 @@ void test_issues_creation_on_main_file() throws IOException { assertThat(telemetryMap).containsOnlyKeys( "java.analysis.main.success.size_chars", "java.analysis.main.success.time_ms", + "java.analysis.main.success.type_error_count", "java.dependency.lombok", "java.dependency.spring-boot", "java.dependency.spring-web", @@ -132,6 +134,7 @@ void test_issues_creation_on_main_file() throws IOException { "java.scanner_app"); assertThat(telemetryMap.get("java.analysis.main.success.size_chars")).matches("\\d{5}"); assertThat(telemetryMap.get("java.analysis.main.success.time_ms")).matches("\\d+"); + assertThat(telemetryMap).containsEntry("java.analysis.main.success.type_error_count", "199"); } @Test @@ -142,6 +145,7 @@ void test_issues_creation_on_test_file() throws IOException { // NOSONAR require assertThat(telemetryMap).containsOnlyKeys( "java.analysis.test.success.size_chars", "java.analysis.test.success.time_ms", + "java.analysis.test.success.type_error_count", "java.dependency.lombok", "java.dependency.spring-boot", "java.dependency.spring-web", @@ -151,6 +155,15 @@ void test_issues_creation_on_test_file() throws IOException { // NOSONAR require "java.scanner_app"); assertThat(telemetryMap.get("java.analysis.test.success.size_chars")).matches("\\d{5}"); assertThat(telemetryMap.get("java.analysis.test.success.time_ms")).matches("\\d+"); + assertThat(telemetryMap).containsEntry("java.analysis.test.success.type_error_count", "199"); + } + + private static int lineNumberOfTheMethodWithNoSonar(FileSystem fs) throws IOException { + String[] lines = fs.inputFile(fs.predicates().hasPath("org/sonar/plugins/java/JavaSensorTest.java")).contents().split("\n"); + int zeroBasedLineIndex = (int) Stream.of(lines) + .takeWhile(line -> !line.contains("test_issues_creation_on_test_file")) + .count(); + return zeroBasedLineIndex + 1; } private void testIssueCreation(InputFile.Type onType, int expectedIssues) throws IOException { @@ -169,8 +182,8 @@ private void testIssueCreation(InputFile.Type onType, int expectedIssues) throws JavaSensor jss = new JavaSensor(sonarComponents, fs, javaResourceLocator, settings.asConfig(), noSonarFilter, null, telemetry); jss.execute(context); - // argument 138 refers to the comment on line #138 in this file, each time this file changes, this argument should be updated - verify(noSonarFilter, times(1)).noSonarInFile(fs.inputFiles().iterator().next(), Collections.singleton(138)); + int expectedNoSonarLine = lineNumberOfTheMethodWithNoSonar(fs); + verify(noSonarFilter, times(1)).noSonarInFile(fs.inputFiles().iterator().next(), Collections.singleton(expectedNoSonarLine)); verify(sonarComponents, times(expectedIssues)).reportIssue(any(AnalyzerMessage.class)); // There are additional entries, but we do not test them. From 38826c6a08c933ce8b5431e60dd9b57abad6b35a Mon Sep 17 00:00:00 2001 From: Alban Auzeill Date: Thu, 17 Jul 2025 11:45:15 +0200 Subject: [PATCH 2/2] Fix from review --- .../sonar/java/telemetry/TelemetryKey.java | 3 +- .../java/telemetry/TelemetryKeyTest.java | 42 ++++++++++++------- .../sonar/plugins/java/JavaSensorTest.java | 1 + 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/java-frontend/src/main/java/org/sonar/java/telemetry/TelemetryKey.java b/java-frontend/src/main/java/org/sonar/java/telemetry/TelemetryKey.java index 00b68d99eb9..7ad620af3fe 100644 --- a/java-frontend/src/main/java/org/sonar/java/telemetry/TelemetryKey.java +++ b/java-frontend/src/main/java/org/sonar/java/telemetry/TelemetryKey.java @@ -53,13 +53,14 @@ public enum TelemetryKey { public interface SpeedKeys { TelemetryKey sizeCharsKey(); + TelemetryKey timeMsKey(); } public record SizeAndTimeKeys(TelemetryKey sizeCharsKey, TelemetryKey timeMsKey) implements SpeedKeys { } - public record SizeTimeAndTypeErrorKeys(TelemetryKey sizeCharsKey, TelemetryKey timeMsKey, TelemetryKey typeErrorCountKey) implements SpeedKeys { + public record SizeTimeAndTypeErrorKeys(TelemetryKey sizeCharsKey, TelemetryKey timeMsKey, TelemetryKey typeErrorCountKey) implements SpeedKeys { } public record JavaAnalysisKeys(SizeTimeAndTypeErrorKeys success, SpeedKeys parseErrors, SpeedKeys exceptions) { diff --git a/java-frontend/src/test/java/org/sonar/java/telemetry/TelemetryKeyTest.java b/java-frontend/src/test/java/org/sonar/java/telemetry/TelemetryKeyTest.java index 38dcc9ab0f2..18ff098dde8 100644 --- a/java-frontend/src/test/java/org/sonar/java/telemetry/TelemetryKeyTest.java +++ b/java-frontend/src/test/java/org/sonar/java/telemetry/TelemetryKeyTest.java @@ -17,6 +17,7 @@ package org.sonar.java.telemetry; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.function.Function; import org.junit.jupiter.api.Test; @@ -31,8 +32,10 @@ class TelemetryKeyTest { @Test - void testKey() { - assertThat(TelemetryKey.JAVA_LANGUAGE_VERSION.key()).isEqualTo("java.language.version"); + void test_TelemetryKey_names() { + for (TelemetryKey enumEntry : TelemetryKey.values()) { + assertThat(enumEntry.name()).isEqualTo(enumNameFromTelemetryName(enumEntry.key())); + } } @Test @@ -40,15 +43,22 @@ void testCompoundKeys() { for (String artifact : List.of("main.", "test.", "generated.")) { for (String result : List.of("success.", "parse_errors.", "exceptions.")) { for (String metric : List.of("size_chars", "time_ms")) { - String telemetryName = "java.analysis." + artifact + result + metric; - String javaName = telemetryName.toUpperCase().replace(".", "_"); - TelemetryKey key = TelemetryKey.valueOf(javaName); - assertThat(key.key()).isEqualTo(telemetryName); + assertTelemetryNameExists("java.analysis." + artifact + result + metric); } } + assertTelemetryNameExists("java.analysis." + artifact + "success.type_error_count"); } } + private static String enumNameFromTelemetryName(String telemetryName) { + return telemetryName.toUpperCase(Locale.ROOT).replaceAll("[.\\-]", "_"); + } + + private static void assertTelemetryNameExists(String telemetryName) { + TelemetryKey enumEntry = TelemetryKey.valueOf(enumNameFromTelemetryName(telemetryName)); + assertThat(enumEntry.key()).isEqualTo(telemetryName); + } + @Test void testAnalysisKeys() { Map artifacts = Map.of( @@ -61,17 +71,21 @@ void testAnalysisKeys() { "parse_errors.", JavaAnalysisKeys::parseErrors, "exceptions.", JavaAnalysisKeys::exceptions ); - Map> metrics = Map.of( + Map> metrics = Map.of( "size_chars", SpeedKeys::sizeCharsKey, "time_ms", SpeedKeys::timeMsKey ); - artifacts.forEach((artifactName, artifact) -> results.forEach( - (resultName, result) -> metrics.forEach(( - metricName, metric) -> { - String telemetryName = artifactName + resultName + metricName; - TelemetryKey tk = metric.apply(result.apply(artifact)); - assertThat(tk.key()).isEqualTo(telemetryName); - }))); + artifacts.forEach((artifactName, artifact) -> { + String typeErrorName = artifactName + "success.type_error_count"; + assertThat(artifact.success().typeErrorCountKey().key()).isEqualTo(typeErrorName); + results.forEach( + (resultName, result) -> metrics.forEach(( + metricName, metric) -> { + String telemetryName = artifactName + resultName + metricName; + TelemetryKey tk = metric.apply(result.apply(artifact)); + assertThat(tk.key()).isEqualTo(telemetryName); + })); + }); } } diff --git a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaSensorTest.java b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaSensorTest.java index b9081fa7033..2a383b760c2 100644 --- a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaSensorTest.java +++ b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaSensorTest.java @@ -138,6 +138,7 @@ void test_issues_creation_on_main_file() throws IOException { } @Test + // Renaming this method will break lineNumberOfTheMethodWithNoSonar(fs). The name is used to locate the line number. void test_issues_creation_on_test_file() throws IOException { // NOSONAR required to test NOSONAR reporting on test files testIssueCreation(InputFile.Type.TEST, 0);