diff --git a/.cirrus.yml b/.cirrus.yml index 87152176818..ac21997d78e 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -52,7 +52,7 @@ log_develocity_url_script: &log_develocity_url_script | common_build_definition: &COMMON_BUILD_DEFINITION eks_container: <<: *CONTAINER_DEFINITION - image: ${CIRRUS_AWS_ACCOUNT}.dkr.ecr.eu-central-1.amazonaws.com/base:j22-latest + image: ${CIRRUS_AWS_ACCOUNT}.dkr.ecr.eu-central-1.amazonaws.com/base:j23-latest cpu: 4 memory: 4G env: @@ -127,14 +127,16 @@ ws_scan_task: qa_os_win_task: ec2_instance: - image: base-windows-jdk22-v* + image: base-windows-jdk21-v* <<: *WINDOWS_VM_DEFINITION maven_cache: folder: ${CIRRUS_WORKING_DIR}/.m2/repository + java_download_cache: + folder: ${CIRRUS_WORKING_DIR}/.java_download_cache build_script: - *log_develocity_url_script - source cirrus-env CI - - mvn.cmd clean verify + - ps: .cirrus/install-latest-java-on-windows.ps1 ; if ($?) { & mvn.cmd --batch-mode clean verify } cleanup_before_cache_script: cleanup_maven_repository plugin_qa_task: @@ -175,7 +177,7 @@ sanity_task: <<: *ONLY_SONARSOURCE_QA eks_container: <<: *CONTAINER_DEFINITION - image: ${CIRRUS_AWS_ACCOUNT}.dkr.ecr.eu-central-1.amazonaws.com/base:j22-latest + image: ${CIRRUS_AWS_ACCOUNT}.dkr.ecr.eu-central-1.amazonaws.com/base:j23-latest cpu: 4 memory: 4G maven_cache: @@ -256,9 +258,9 @@ autoscan_task: eks_container: <<: *CONTAINER_WITH_DOCKER_DEFINITION # For now, this autoscan_task need to execute two mvn commands: - # * The build of java-checks-test-sources module which requires Java 22. - # * The tests using Orchestrator and SonarQube that, for now, fail to work using Java 22 - # This is why we have a local Dockerfile that provide the 2 versions of Java, 17 and 22. + # * The build of java-checks-test-sources module which requires Java 23. + # * The tests using Orchestrator and SonarQube that, for now, fail to work using Java 23 + # This is why we have a local Dockerfile that provide the 2 versions of Java, 17 and 23. cpu: 14 memory: 8G maven_cache: diff --git a/.cirrus/Dockerfile.jdk17AndLatest b/.cirrus/Dockerfile.jdk17AndLatest index 06e5a94eb37..e767bcb8635 100644 --- a/.cirrus/Dockerfile.jdk17AndLatest +++ b/.cirrus/Dockerfile.jdk17AndLatest @@ -8,21 +8,21 @@ USER root ENV DEBIAN_FRONTEND=noninteractive # The current image is `FROM public.ecr.aws/docker/library/eclipse-temurin:17-jammy` -# Use a similar method to install Java 22 copied from https://github.com/adoptium/containers/blob/main/22/jdk/ubuntu/jammy/Dockerfile +# Use a similar method to install Java 23 copied from https://github.com/adoptium/containers/blob/main/22/jdk/ubuntu/jammy/Dockerfile -ENV JAVA_LATEST_HOME /opt/java/openjdk22 -ENV JAVA_LATEST_VERSION jdk-22.0.2+9 +ENV JAVA_LATEST_HOME /opt/java/openjdk23 +ENV JAVA_LATEST_VERSION jdk-23.0.1+11 RUN set -eux; \ ARCH="$(dpkg --print-architecture)"; \ case "${ARCH}" in \ amd64) \ - ESUM='05cd9359dacb1a1730f7c54f57e0fed47942a5292eb56a3a0ee6b13b87457a43'; \ - BINARY_URL='https://github.com/adoptium/temurin22-binaries/releases/download/jdk-22.0.2%2B9/OpenJDK22U-jdk_x64_linux_hotspot_22.0.2_9.tar.gz'; \ + ESUM='2400267e4e9c0f6ae880a4d763af6caf18c673714bdee5debf8388b0b5d52886'; \ + BINARY_URL='https://github.com/adoptium/temurin23-binaries/releases/download/jdk-23.0.1%2B11/OpenJDK23U-jdk_x64_linux_hotspot_23.0.1_11.tar.gz'; \ ;; \ arm64) \ - ESUM='dac62747b5158c4bf4c4636432e3bdb9dea47f80f0c9d1d007f19bd5483b7d29'; \ - BINARY_URL='https://github.com/adoptium/temurin22-binaries/releases/download/jdk-22.0.2%2B9/OpenJDK22U-jdk_aarch64_linux_hotspot_22.0.2_9.tar.gz'; \ + ESUM='0b498a5b673cb50fe9cfd0a13bd39c7259b4fad4d930d614e1563aeb8bca7f0e'; \ + BINARY_URL='https://github.com/adoptium/temurin23-binaries/releases/download/jdk-23.0.1%2B11/OpenJDK23U-jre_aarch64_linux_hotspot_23.0.1_11.tar.gz'; \ ;; \ *) \ echo "Unsupported arch: ${ARCH}"; \ diff --git a/.cirrus/install-latest-java-on-windows.ps1 b/.cirrus/install-latest-java-on-windows.ps1 new file mode 100644 index 00000000000..2a30e934466 --- /dev/null +++ b/.cirrus/install-latest-java-on-windows.ps1 @@ -0,0 +1,100 @@ +$javaVersion = "23.0.1+11" +$sha256_x64 = "eb1bc62060f17566b160fae8ce876ada5d639e2fd4781009a1219e971b9937dd" +$sha256_aarch64 = "00ea8896d42ac26cb6887eef08cd6e3b2a54f30e9e87b0fc965e66813567ac87" + +Write-Output "Installing Java ${javaVersion}" + +$javaMajorVersion = ($javaVersion -split '\.')[0] +Write-Output "Major version: ${javaMajorVersion}" + +$javaUriVersion = $javaVersion -replace '\+', '%2B' +$javaFileVersion = $javaVersion -replace '\+', '_' +$arch = (Get-WmiObject -Class Win32_Processor).AddressWidth +if ($arch -eq 64) { + $arch = "x64" + $sha256 = $sha256_x64 +} else { + $arch = "aarch64" + $sha256 = $sha256_aarch64 +} +$zipFileName = "OpenJDK${javaMajorVersion}U-jdk_${arch}_windows_hotspot_${javaFileVersion}.zip" +$binaryUrl = "https://github.com/adoptium/temurin${javaMajorVersion}-binaries/releases/download/jdk-${javaUriVersion}/${zipFileName}" +$javaDownloadDirectory = "${env:CIRRUS_WORKING_DIR}/.java_download_cache" +$zipPath = "${javaDownloadDirectory}\${zipFileName}" +$javaHomeParent = "${env:CIRRUS_WORKING_DIR}/.openjdk" +$javaHome = "${javaHomeParent}\jdk-${javaVersion}" + +Write-Output "Prepare download directory: ${javaDownloadDirectory}" +if (-not (Test-Path "${javaDownloadDirectory}")) { + New-Item -ItemType Directory -Path $javaDownloadDirectory -Force +} +$itemsToDelete = Get-ChildItem -Path $javaDownloadDirectory | Where-Object { $_.Name -ne $zipFileName } +foreach ($item in $itemsToDelete) { + Write-Output "Remove: ${item}" + Remove-Item -Path $item.FullName -Recurse -Force +} + +Write-Output "Prepare installation directory: ${javaHomeParent}" +if (-not (Test-Path "${javaHomeParent}")) { + New-Item -ItemType Directory -Path $javaHomeParent -Force +} +Write-Output "Remove other jdk in ${javaHomeParent}" +$itemsToDelete = Get-ChildItem -Path $javaHomeParent | Where-Object { $_.Name -ne "jdk-${javaVersion}" } +foreach ($item in $itemsToDelete) { + Write-Output "Remove: $item" + Remove-Item -Path $item.FullName -Recurse -Force +} + +if (-not (Test-Path "${javaHome}\bin\java.exe")) { + if (Test-Path $zipPath) { + Write-Output "Zip '$zipPath' already exists." + } else { + Write-Output "Download from '$binaryUrl' into '$zipPath'" + Invoke-WebRequest -Uri $binaryUrl -OutFile $zipPath -UseBasicParsing > $null + + # Verify the checksum + Write-Output "Check the sha256 checksum of $zipPath" + $actualChecksum = Get-FileHash -Path $zipPath -Algorithm SHA256 | Select-Object -ExpandProperty Hash + if ($actualChecksum -ne $sha256) { + Write-Error "Checksum verification failed. Expected: $expectedChecksum, Actual: $actualChecksum" + exit 1 + } else { + Write-Output "Checksum verification passed." + } + } + + # Extract the zip file + Write-Output "Extract JDK archive" + $global:ProgressPreference = "SilentlyContinue" + Expand-Archive -Path $zipPath -DestinationPath $javaHomeParent -Force > $null + + # Check if java is present + if (-not (Test-Path "${javaHome}\bin\java.exe")) { + Write-Error "Fail to find ${javaHome}\bin\java.exe in the extracted directory" + exit 1 + } +} else { + Write-Output "Java already installed in ${javaHome}" +} + +# Set JAVA_HOME +Write-Output "Set JAVA_HOME to $javaHome" +$env:JAVA_HOME = "${javaHome}" + +# Set PATH +$javaBinPath = "${env:JAVA_HOME}\bin" + +if ($env:Path -split ';' -contains $javaBinPath) { + Write-Output "The path $javaBinPath is already in the Path environment variable." +} else { + Write-Output "Adding $javaBinPath to the Path environment variable." + $env:Path = "$javaBinPath;$env:Path" +} + +# Print the version +Write-Output "java.exe --version" +& "${env:JAVA_HOME}\bin\java.exe" --version + +Write-Output "-- Java ${javaVersion} Installed Successfully --" + +exit 0 diff --git a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java index 293d3fe5c86..4b06c5a89fb 100644 --- a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java +++ b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java @@ -122,7 +122,7 @@ public void javaCheckTestSources() throws Exception { .setSourceEncoding("UTF-8") .setSourceDirs("aws/src/main/java/,default/src/main/java/,java-17/src/main/java/,spring-3.2/src/main/java/,spring-web-4.0/src/main/java/") .setTestDirs("default/src/test/java/,test-classpath-reader/src/test/java") - .setProperty("sonar.java.source", "22") + .setProperty("sonar.java.source", "23") // common properties .setProperty("sonar.cpd.exclusions", "**/*") .setProperty("sonar.skipPackageDesign", "true") @@ -195,7 +195,7 @@ public void javaCheckTestSources() throws Exception { SoftAssertions softly = new SoftAssertions(); softly.assertThat(newDiffs).containsExactlyInAnyOrderElementsOf(knownDiffs.values()); softly.assertThat(newTotal).isEqualTo(knownTotal); - softly.assertThat(rulesCausingFPs).hasSize(8); + softly.assertThat(rulesCausingFPs).hasSize(9); softly.assertThat(rulesNotReporting).hasSize(11); /** diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json index 899504c8096..72e08d5c64f 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S1128.json @@ -1,6 +1,6 @@ { "ruleKey": "S1128", "hasTruePositives": true, - "falseNegatives": 33, + "falseNegatives": 36, "falsePositives": 0 } diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S5738.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S5738.json index 4e20fbc52d1..83100f9ae86 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S5738.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S5738.json @@ -1,6 +1,6 @@ { "ruleKey": "S5738", "hasTruePositives": true, - "falseNegatives": 1, + "falseNegatives": 4, "falsePositives": 0 } diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6204.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6204.json index 686f9c0afc5..796390e4084 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6204.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6204.json @@ -2,5 +2,5 @@ "ruleKey": "S6204", "hasTruePositives": true, "falseNegatives": 0, - "falsePositives": 0 -} \ No newline at end of file + "falsePositives": 1 +} diff --git a/java-checks-test-sources/aws/src/main/java/checks/aws/AwsReusableResourcesInitializedOnceCheckSample.java b/java-checks-test-sources/aws/src/main/java/checks/aws/AwsReusableResourcesInitializedOnceCheckSample.java index 2bfee82d1a6..c607459edf4 100644 --- a/java-checks-test-sources/aws/src/main/java/checks/aws/AwsReusableResourcesInitializedOnceCheckSample.java +++ b/java-checks-test-sources/aws/src/main/java/checks/aws/AwsReusableResourcesInitializedOnceCheckSample.java @@ -31,21 +31,25 @@ public RequestHandlerImpl() throws SQLException { @SneakyThrows @Override public Void handleRequest(Object o, Context context) { - S3Client s3Client = S3Client.builder().region(Region.EU_CENTRAL_1).build(); // Noncompliant {{Instantiate this client outside the Lambda function.}} -// ^^^^^ - S3Client.builder().build(); // Noncompliant {{Instantiate this client outside the Lambda function.}} -// ^^^^^ - MachineLearningClient.builder().build(); // Noncompliant {{Instantiate this client outside the Lambda function.}} -// ^^^^^ - DriverManager.getConnection("foo"); // Noncompliant {{Instantiate this database connection outside the Lambda function.}} -// ^^^^^^^^^^^^^ - var customClient = new FooClient(); // Noncompliant {{Instantiate this client outside the Lambda function.}} -// ^^^^^^^^^ - var compliant = new Object(); - build(); - - called(); - return null; + try { + S3Client s3Client = S3Client.builder().region(Region.EU_CENTRAL_1).build(); // Noncompliant {{Instantiate this client outside the Lambda function.}} +// ^^^^^ + S3Client.builder().build(); // Noncompliant {{Instantiate this client outside the Lambda function.}} +// ^^^^^ + MachineLearningClient.builder().build(); // Noncompliant {{Instantiate this client outside the Lambda function.}} +// ^^^^^ + DriverManager.getConnection("foo"); // Noncompliant {{Instantiate this database connection outside the Lambda function.}} +// ^^^^^^^^^^^^^ + var customClient = new FooClient(); // Noncompliant {{Instantiate this client outside the Lambda function.}} +// ^^^^^^^^^ + var compliant = new Object(); + build(); + + called(); + return null; + } catch (SQLException e) { + throw new RuntimeException(e); + } } // Similar signature but not same: @@ -110,13 +114,17 @@ public RequestStreamHandlerImpl() throws SQLException { @SneakyThrows @Override public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { - S3Client s3Client = S3Client.builder().region(Region.EU_CENTRAL_1).build(); // Noncompliant - S3Client.builder().build(); // Noncompliant - MachineLearningClient.builder().build(); // Noncompliant - var customClient = new FooClient(); // Noncompliant - DriverManager.getConnection("foo"); // Noncompliant - - called(); + try { + S3Client s3Client = S3Client.builder().region(Region.EU_CENTRAL_1).build(); // Noncompliant + S3Client.builder().build(); // Noncompliant + MachineLearningClient.builder().build(); // Noncompliant + var customClient = new FooClient(); // Noncompliant + DriverManager.getConnection("foo"); // Noncompliant + + called(); + } catch (SQLException e) { + throw new RuntimeException(e); + } } // Similar signature but not same: diff --git a/java-checks-test-sources/default/pom.xml b/java-checks-test-sources/default/pom.xml index c213e681b66..d9083807fda 100644 --- a/java-checks-test-sources/default/pom.xml +++ b/java-checks-test-sources/default/pom.xml @@ -394,7 +394,7 @@ org.projectlombok lombok - 1.18.30 + 1.18.38 jar provided @@ -1025,9 +1025,16 @@ org.apache.maven.plugins maven-compiler-plugin - 22 - 22 - 22 + 23 + 23 + 23 + + + org.projectlombok + lombok + 1.18.38 + + diff --git a/java-checks-test-sources/default/src/main/java/a/b/c/ReferencedFromMarkdown.java b/java-checks-test-sources/default/src/main/java/a/b/c/ReferencedFromMarkdown.java new file mode 100644 index 00000000000..e636a29258c --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/a/b/c/ReferencedFromMarkdown.java @@ -0,0 +1,4 @@ +package a.b.c; + +public class ReferencedFromMarkdown { +} diff --git a/java-checks-test-sources/default/src/main/java/checks/DisallowedThreadGroupCheck.java b/java-checks-test-sources/default/src/main/java/checks/DisallowedThreadGroupCheck.java index c29b042d243..263153a88d4 100644 --- a/java-checks-test-sources/default/src/main/java/checks/DisallowedThreadGroupCheck.java +++ b/java-checks-test-sources/default/src/main/java/checks/DisallowedThreadGroupCheck.java @@ -32,10 +32,7 @@ void foo( tg.setDaemon(true); // Compliant tg.list(); // Compliant tg.parentOf(tg); // Compliant - tg.resume(); // Compliant tg.setMaxPriority(0); // Compliant - tg.stop(); // Compliant - tg.suspend(); // Compliant tg.uncaughtException(new Thread(), new Exception()); // Compliant tg.toString(); // Compliant tg.equals(o); // Compliant - not overridden in ThreadGroup diff --git a/java-checks-test-sources/default/src/main/java/checks/TodoTagPresenceCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/TodoTagPresenceCheckSample.java index ae282f7fcfb..f4619fa7ba6 100644 --- a/java-checks-test-sources/default/src/main/java/checks/TodoTagPresenceCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/TodoTagPresenceCheckSample.java @@ -16,11 +16,17 @@ // TODO // Noncompliant@+1 -// TODO Explenation +// TODO Explanation // Noncompliant@+1 // [TODO] + // Noncompliant@+1 +/// TODO Explanation + + // Noncompliant@+1 +//// TODO Explanation + // PreTodo // toDomain package checks; diff --git a/java-checks-test-sources/default/src/main/java/checks/UselessImportCheck/WithinPackage.java b/java-checks-test-sources/default/src/main/java/checks/UselessImportCheck/WithinPackage.java index d5669b937c7..589183728b8 100644 --- a/java-checks-test-sources/default/src/main/java/checks/UselessImportCheck/WithinPackage.java +++ b/java-checks-test-sources/default/src/main/java/checks/UselessImportCheck/WithinPackage.java @@ -5,6 +5,7 @@ import a.b.c.Baz; import a.b.c.Qux; import a.b.c.ReferencedFromJavadoc; +import a.b.c.ReferencedFromMarkdown; import java.util.Map; import java.util.Map.Entry; import a.b.c.NotReferencedFromJavadoc; // Noncompliant @@ -84,6 +85,8 @@ public Class annotationType() { } }; } + /// This method reference has + /// a reference on [ReferencedFromMarkdown] void foo(@Nullable int x){ System.out.println(FLUP);; } diff --git a/java-checks-test-sources/java-17/src/main/java/checks/DisallowedThreadGroupCheck.java b/java-checks-test-sources/java-17/src/main/java/checks/DisallowedThreadGroupCheck.java index e184530764b..a38291a9f98 100644 --- a/java-checks-test-sources/java-17/src/main/java/checks/DisallowedThreadGroupCheck.java +++ b/java-checks-test-sources/java-17/src/main/java/checks/DisallowedThreadGroupCheck.java @@ -3,5 +3,8 @@ abstract class DisallowedThreadGroupCheck { void method_removed_in_java_21(ThreadGroup tg) { // Noncompliant tg.allowThreadSuspension(true); // Compliant, Note: the "allowThreadSuspension" has been removed in Java 21 + tg.resume(); // Compliant, Note: the "resume" has been removed in Java 23 + tg.stop(); // Compliant, Note: the "stop" has been removed in Java 23 + tg.suspend(); // Compliant, Note: the "suspend" has been removed in Java 23 } } diff --git a/java-checks-test-sources/spring-3.2/pom.xml b/java-checks-test-sources/spring-3.2/pom.xml index 89c81aa3c7d..999ac1fb611 100644 --- a/java-checks-test-sources/spring-3.2/pom.xml +++ b/java-checks-test-sources/spring-3.2/pom.xml @@ -85,9 +85,9 @@ org.apache.maven.plugins maven-compiler-plugin - 22 - 22 - 22 + 23 + 23 + 23 diff --git a/java-checks/src/main/java/org/sonar/java/checks/CommentedOutCodeLineCheck.java b/java-checks/src/main/java/org/sonar/java/checks/CommentedOutCodeLineCheck.java index 3fd44b50c89..907a3d7d4a9 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/CommentedOutCodeLineCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/CommentedOutCodeLineCheck.java @@ -32,6 +32,7 @@ import org.sonar.plugins.java.api.location.Range; import org.sonar.plugins.java.api.tree.SyntaxToken; import org.sonar.plugins.java.api.tree.SyntaxTrivia; +import org.sonar.plugins.java.api.tree.SyntaxTrivia.CommentKind; import org.sonar.plugins.java.api.tree.Tree; import org.sonarsource.analyzer.commons.annotations.DeprecatedRuleKey; import org.sonarsource.analyzer.commons.recognizers.CodeRecognizer; @@ -84,7 +85,8 @@ public void visitToken(SyntaxToken syntaxToken) { currentCommentLine != previousCommentLine) { previousRelatedIssue = null; } - if (!isHeader(syntaxTrivia) && !isJavadoc(syntaxTrivia.comment()) && !isJSNI(syntaxTrivia.comment())) { + boolean isJavadocOrMarkdownComment = syntaxTrivia.isComment(CommentKind.JAVADOC, CommentKind.MARKDOWN); + if (!isHeader(syntaxTrivia) && !isJavadocOrMarkdownComment && !isJSNI(syntaxTrivia.comment())) { previousRelatedIssue = collectIssues(issues, syntaxTrivia, previousRelatedIssue); previousCommentLine = currentCommentLine; } @@ -147,15 +149,6 @@ private static boolean isJavadocLink(String line) { return line.contains("{@link"); } - /** - * From documentation for Javadoc-tool: - * Documentation comments should be recognized only when placed - * immediately before class, interface, constructor, method, or field declarations. - */ - private static boolean isJavadoc(String comment) { - return StringUtils.startsWith(comment, "/**"); - } - /** * From GWT documentation: * JSNI methods are declared native and contain JavaScript code in a specially formatted comment block diff --git a/java-checks/src/main/java/org/sonar/java/checks/TrailingCommentCheck.java b/java-checks/src/main/java/org/sonar/java/checks/TrailingCommentCheck.java index 42f5d600c67..a35b3875b05 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/TrailingCommentCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/TrailingCommentCheck.java @@ -91,8 +91,7 @@ public void visitToken(SyntaxToken syntaxToken) { if (tokenLine != previousTokenLine) { syntaxToken.trivias().stream() .filter(trivia -> LineUtils.startLine(trivia) == previousTokenLine) - .map(SyntaxTrivia::comment) - .map(comment -> comment.startsWith("//") ? comment.substring(2) : comment.substring(2, comment.length() - 2)) + .map(SyntaxTrivia::commentContent) .map(String::trim) .filter(comment -> !pattern.matcher(comment).matches() && !containsExcludedPattern(comment)) .forEach(comment -> addIssue(previousTokenLine, "Move this trailing comment on the previous empty line.")); diff --git a/java-checks/src/main/java/org/sonar/java/checks/UselessImportCheck.java b/java-checks/src/main/java/org/sonar/java/checks/UselessImportCheck.java index fe00ddef480..8d8dd82ae6b 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/UselessImportCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/UselessImportCheck.java @@ -43,6 +43,7 @@ import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.PackageDeclarationTree; import org.sonar.plugins.java.api.tree.SyntaxTrivia; +import org.sonar.plugins.java.api.tree.SyntaxTrivia.CommentKind; import org.sonar.plugins.java.api.tree.Tree; import org.sonarsource.analyzer.commons.annotations.DeprecatedRuleKey; @@ -50,9 +51,10 @@ @Rule(key = "S1128") public class UselessImportCheck extends IssuableSubscriptionVisitor { - private static final Pattern COMPILER_WARNING = Pattern.compile("The import ([$\\w]+(\\.[$\\w]+)*+) is never used"); + private static final Pattern COMPILER_WARNING = Pattern.compile("The import ([$\\w]++(\\.[$\\w]++)*+) is never used"); private static final Pattern NON_WORDS_CHARACTERS = Pattern.compile("\\W+"); - private static final Pattern JAVADOC_REFERENCE = Pattern.compile("\\{@link[^\\}]*\\}|(@see|@throws)[^\n]*\n"); + private static final Pattern JAVADOC_REFERENCE = Pattern.compile("\\{@link[^\\{\\}]++\\}|(@see|@throws)[^\r\n]++[\r\n]"); + private static final Pattern MARKDOWN_REFERENCE = Pattern.compile("\\[[^\\[\\]\\r\\n]++\\]"); private String currentPackage = ""; private final List imports = new ArrayList<>(); @@ -162,10 +164,15 @@ private static boolean isJavaLangImport(String reference) { @Override public void visitTrivia(SyntaxTrivia syntaxTrivia) { String comment = syntaxTrivia.comment(); - if (!comment.startsWith("/**")) { + Matcher matcher; + if (syntaxTrivia.isComment(CommentKind.JAVADOC)) { + matcher = JAVADOC_REFERENCE.matcher(comment); + } else if (syntaxTrivia.isComment(CommentKind.MARKDOWN)) { + matcher = MARKDOWN_REFERENCE.matcher(comment); + } else { + // other comment types don't have references return; } - Matcher matcher = JAVADOC_REFERENCE.matcher(comment); while (matcher.find()) { String line = matcher.group(0); Set words = NON_WORDS_CHARACTERS.splitAsStream(line) diff --git a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java index 90a90638902..bb24e3b085c 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java @@ -51,6 +51,7 @@ import org.sonar.plugins.java.api.tree.Modifier; import org.sonar.plugins.java.api.tree.SyntaxToken; import org.sonar.plugins.java.api.tree.SyntaxTrivia; +import org.sonar.plugins.java.api.tree.SyntaxTrivia.CommentKind; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.TypeTree; import org.sonar.plugins.java.api.tree.VariableTree; @@ -272,7 +273,7 @@ private static AnalyzerMessage.TextSpan computeTextSpan(VariableTree tree) { List trivias = tree.firstToken().trivias(); if (!trivias.isEmpty()) { SyntaxTrivia lastTrivia = trivias.get(trivias.size() - 1); - if (lastTrivia.comment().startsWith("/**")) { + if (lastTrivia.isComment(CommentKind.JAVADOC, CommentKind.MARKDOWN)) { SyntaxToken lastToken = tree.lastToken(); Position start = Position.startOf(lastTrivia); Position end = Position.endOf(lastToken); diff --git a/java-frontend/pom.xml b/java-frontend/pom.xml index 84b99b6e5e5..ed79848f8a4 100644 --- a/java-frontend/pom.xml +++ b/java-frontend/pom.xml @@ -16,7 +16,7 @@ ${project.groupId} jdt-package - 1.3.0.89 + 1.4.0.135 diff --git a/java-frontend/src/main/java/org/sonar/java/ast/visitors/CommentLinesVisitor.java b/java-frontend/src/main/java/org/sonar/java/ast/visitors/CommentLinesVisitor.java index 11f7289b495..9078c81c9d3 100644 --- a/java-frontend/src/main/java/org/sonar/java/ast/visitors/CommentLinesVisitor.java +++ b/java-frontend/src/main/java/org/sonar/java/ast/visitors/CommentLinesVisitor.java @@ -63,7 +63,7 @@ public void visitToken(SyntaxToken syntaxToken) { } private void handleCommentsForTrivia(SyntaxTrivia trivia) { - String[] commentLines = getContents(trivia.comment()).split("(\r)?\n|\r", -1); + String[] commentLines = trivia.commentContent().split("\\R", -1); int line = LineUtils.startLine(trivia); for (String commentLine : commentLines) { if (commentLine.contains("NOSONAR")) { @@ -103,7 +103,4 @@ private static boolean isBlank(String line) { return true; } - private static String getContents(String comment) { - return comment.startsWith("//") ? comment.substring(2) : comment.substring(2, comment.length() - 2); - } } diff --git a/java-frontend/src/main/java/org/sonar/java/ast/visitors/PublicApiChecker.java b/java-frontend/src/main/java/org/sonar/java/ast/visitors/PublicApiChecker.java index 97206cf406b..302d4a1c83b 100644 --- a/java-frontend/src/main/java/org/sonar/java/ast/visitors/PublicApiChecker.java +++ b/java-frontend/src/main/java/org/sonar/java/ast/visitors/PublicApiChecker.java @@ -21,6 +21,7 @@ import java.util.Objects; import java.util.Optional; import javax.annotation.Nullable; +import org.sonar.plugins.java.api.tree.SyntaxTrivia.CommentKind; import org.sonarsource.analyzer.commons.collections.ListUtils; import org.sonar.java.model.ModifiersUtils; import org.sonar.plugins.java.api.tree.ClassTree; @@ -136,13 +137,9 @@ public static Optional getApiJavadoc(Tree tree) { return tree.firstToken() .trivias() .stream() + .filter(trivia -> trivia.isComment(CommentKind.JAVADOC, CommentKind.MARKDOWN)) .map(SyntaxTrivia::comment) - .filter(PublicApiChecker::isJavadoc) // Get last element of stream, as the last javadoc comment is the one we are looking for. .reduce((first, second) -> second); } - - private static boolean isJavadoc(String comment) { - return comment.startsWith("/**"); - } } diff --git a/java-frontend/src/main/java/org/sonar/java/ast/visitors/SyntaxHighlighterVisitor.java b/java-frontend/src/main/java/org/sonar/java/ast/visitors/SyntaxHighlighterVisitor.java index ab5cb681113..6235c110c85 100644 --- a/java-frontend/src/main/java/org/sonar/java/ast/visitors/SyntaxHighlighterVisitor.java +++ b/java-frontend/src/main/java/org/sonar/java/ast/visitors/SyntaxHighlighterVisitor.java @@ -41,6 +41,7 @@ import org.sonar.plugins.java.api.tree.ModifiersTree; import org.sonar.plugins.java.api.tree.SyntaxToken; import org.sonar.plugins.java.api.tree.SyntaxTrivia; +import org.sonar.plugins.java.api.tree.SyntaxTrivia.CommentKind; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.YieldStatementTree; @@ -188,8 +189,8 @@ private boolean isRestrictedKeyword(SyntaxToken syntaxToken) { @Override public void visitTrivia(SyntaxTrivia syntaxTrivia) { - boolean isJavadoc = syntaxTrivia.comment().startsWith("/**"); - TypeOfText typeOfText = isJavadoc ? TypeOfText.STRUCTURED_COMMENT : TypeOfText.COMMENT; + boolean isJavadocOrMarkdown = syntaxTrivia.isComment(CommentKind.JAVADOC, CommentKind.MARKDOWN); + TypeOfText typeOfText = isJavadocOrMarkdown ? TypeOfText.STRUCTURED_COMMENT : TypeOfText.COMMENT; Position start = Position.startOf(syntaxTrivia); Position end = Position.endOf(syntaxTrivia); highlighting.highlight( diff --git a/java-frontend/src/main/java/org/sonar/java/model/InternalSyntaxTrivia.java b/java-frontend/src/main/java/org/sonar/java/model/InternalSyntaxTrivia.java index d39c3ca254f..20936bd750a 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/InternalSyntaxTrivia.java +++ b/java-frontend/src/main/java/org/sonar/java/model/InternalSyntaxTrivia.java @@ -26,16 +26,31 @@ public class InternalSyntaxTrivia extends JavaTree implements SyntaxTrivia { + + private final CommentKind commentKind; + private final String comment; @Nonnull private final Range range; - public InternalSyntaxTrivia(String comment, int line, int columnOffset) { + public InternalSyntaxTrivia(CommentKind commentKind, String comment, int line, int columnOffset) { + this.commentKind = commentKind; this.comment = comment; - range = comment.startsWith("/*") + boolean mayHaveLineBreaks = commentKind != CommentKind.LINE; + range = mayHaveLineBreaks ? Range.at(InternalPosition.atOffset(line, columnOffset), comment) : Range.at(InternalPosition.atOffset(line, columnOffset), comment.length()); + + boolean validKind = switch (commentKind) { + case LINE -> comment.startsWith("//"); + case BLOCK -> comment.startsWith("/*") && comment.endsWith("*/"); + case JAVADOC -> comment.startsWith("/**") && comment.endsWith("*/"); + case MARKDOWN -> comment.startsWith("///"); + }; + if (!validKind) { + throw new IllegalArgumentException("Invalid comment kind: " + commentKind + " for comment: " + comment); + } } @Override @@ -43,6 +58,36 @@ public String comment() { return comment; } + @Override + public String commentContent() { + return switch (commentKind) { + case LINE -> comment.substring(2); + case BLOCK -> comment.substring(2, comment.length() - 2); + case JAVADOC -> comment.substring(3, comment.length() - 2); + case MARKDOWN -> comment.substring(3).replaceAll("\\R[ \t\f]*+///", "\n"); + }; + } + + @Override + public CommentKind commentKind() { + return commentKind; + } + + @Override + public boolean isComment(CommentKind kind) { + return commentKind == kind; + } + + @Override + public boolean isComment(CommentKind... kinds) { + for (CommentKind kind : kinds) { + if (commentKind == kind) { + return true; + } + } + return false; + } + @Override public int startLine() { return range.start().line(); @@ -68,10 +113,6 @@ public void accept(TreeVisitor visitor) { // do nothing } - public static SyntaxTrivia create(String comment, int startLine, int column) { - return new InternalSyntaxTrivia(comment, startLine, column); - } - @Override public int getLine() { return range.start().line(); diff --git a/java-frontend/src/main/java/org/sonar/java/model/JParser.java b/java-frontend/src/main/java/org/sonar/java/model/JParser.java index 4d7e251da23..8d549cfc507 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JParser.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JParser.java @@ -245,11 +245,17 @@ import org.sonar.plugins.java.api.tree.StatementTree; import org.sonar.plugins.java.api.tree.SyntaxToken; import org.sonar.plugins.java.api.tree.SyntaxTrivia; +import org.sonar.plugins.java.api.tree.SyntaxTrivia.CommentKind; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.TypeParameterTree; import org.sonar.plugins.java.api.tree.TypeTree; import org.sonar.plugins.java.api.tree.VariableTree; +import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_BLOCK; +import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_JAVADOC; +import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_LINE; +import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_MARKDOWN; + @SuppressWarnings({"rawtypes", "unchecked"}) public class JParser { @@ -415,7 +421,7 @@ private void usageLabel(@Nullable IdentifierTreeImpl node) { private int firstTokenIndexAfter(ASTNode e) { int index = tokenManager.firstIndexAfter(e, ANY_TOKEN); - while (tokenManager.get(index).isComment()) { + while (isComment(tokenManager.get(index))) { index++; } return index; @@ -505,14 +511,14 @@ private InternalSyntaxToken createSpecialToken(int tokenIndex) { private List collectComments(int tokenIndex) { int commentIndex = tokenIndex; - while (commentIndex > 0 && tokenManager.get(commentIndex - 1).isComment()) { + while (commentIndex > 0 && isComment(tokenManager.get(commentIndex - 1))) { commentIndex--; } List comments = new ArrayList<>(); for (int i = commentIndex; i < tokenIndex; i++) { Token t = tokenManager.get(i); LineColumnConverter.Pos pos = lineColumnConverter.toPos(t.originalStart); - comments.add(new InternalSyntaxTrivia( + comments.add(new InternalSyntaxTrivia(convertTokenTypeToCommentKind(t), t.toString(tokenManager.getSource()), pos.line(), pos.columnOffset() @@ -521,13 +527,36 @@ private List collectComments(int tokenIndex) { return comments; } + @VisibleForTesting + static CommentKind convertTokenTypeToCommentKind(Token token) { + return switch (token.tokenType) { + case TokenNameCOMMENT_BLOCK -> CommentKind.BLOCK; + case TokenNameCOMMENT_JAVADOC -> CommentKind.JAVADOC; + case TokenNameCOMMENT_LINE -> CommentKind.LINE; + case TokenNameCOMMENT_MARKDOWN -> CommentKind.MARKDOWN; + default -> throw new IllegalStateException("Unexpected value: " + token.tokenType); + }; + } + + /** + * {@link Token#isComment()} has an issue https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3914 + * it does not support Markdown comments. This method has to be used instead. + */ + @VisibleForTesting + static boolean isComment(Token token) { + return switch (token.tokenType) { + case TokenNameCOMMENT_BLOCK, TokenNameCOMMENT_JAVADOC, TokenNameCOMMENT_LINE, TokenNameCOMMENT_MARKDOWN -> true; + default -> false; + }; + } + private void addEmptyStatementsToList(int tokenIndex, List list) { while (true) { Token token; do { tokenIndex++; token = tokenManager.get(tokenIndex); - } while (token.isComment()); + } while (isComment(token)); if (token.tokenType != TerminalTokens.TokenNameSEMICOLON) { break; @@ -1140,7 +1169,7 @@ private TypeArgumentListTreeImpl convertTypeArguments(List list) { } ASTNode last = (ASTNode) list.get(list.size() - 1); int tokenIndex = tokenManager.firstIndexAfter(last, ANY_TOKEN); - while (tokenManager.get(tokenIndex).isComment()) { + while (isComment(tokenManager.get(tokenIndex))) { tokenIndex++; } return convertTypeArguments( @@ -1169,7 +1198,7 @@ private TypeParameterListTreeImpl convertTypeParameters(List list) { } ASTNode last = (ASTNode) list.get(list.size() - 1); int tokenIndex = tokenManager.firstIndexAfter(last, ANY_TOKEN); - while (tokenManager.get(tokenIndex).isComment()) { + while (isComment(tokenManager.get(tokenIndex))) { tokenIndex++; } TypeParameterListTreeImpl t = new TypeParameterListTreeImpl( @@ -1370,8 +1399,6 @@ private void addStatementToList(Statement node, List statements) declaration(t.variableBinding, t); statements.add(t); } - } else if (node.getNodeType() == ASTNode.BREAK_STATEMENT && node.getLength() < "break".length()) { - // skip implicit break-statement } else { statements.add(createStatement(node)); } @@ -1750,7 +1777,7 @@ private void addSeparatorToList(TryStatement tryStatement, Expression resource, do { tokenIndex--; token = tokenManager.get(tokenIndex); - } while (token.isComment()); + } while (isComment(token)); if (token.tokenType != TerminalTokens.TokenNameSEMICOLON) { break; diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java index e986e6e16b7..c245c7bc332 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java @@ -354,6 +354,7 @@ public final SymbolMetadata metadata() { metadata = convertMetadata(); } catch (RuntimeException e) { // ECJ raises exception in rare occasions, when it is the case, we don't want to prevent the whole analysis of the file + // ECJ 3.41.0: no longer throws the exception on the code that was used in the test metadata = Symbols.EMPTY_METADATA; } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JavaVersionImpl.java b/java-frontend/src/main/java/org/sonar/java/model/JavaVersionImpl.java index 1750a2b7b5a..742c5b805ca 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JavaVersionImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JavaVersionImpl.java @@ -40,7 +40,8 @@ public class JavaVersionImpl implements JavaVersion { private static final int JAVA_20 = 20; private static final int JAVA_21 = 21; private static final int JAVA_22 = 22; - public static final int MAX_SUPPORTED = JAVA_22; + private static final int JAVA_23 = 23; + public static final int MAX_SUPPORTED = JAVA_23; private final int javaVersion; private final boolean previewFeaturesEnabled; @@ -153,6 +154,11 @@ public boolean isJava22Compatible() { return JAVA_22 <= javaVersion; } + @Override + public boolean isJava23Compatible() { + return JAVA_23 <= javaVersion; + } + private boolean notSetOrAtLeast(int requiredJavaVersion) { return isNotSet() || requiredJavaVersion <= javaVersion; } diff --git a/java-frontend/src/main/java/org/sonar/plugins/java/api/JavaVersion.java b/java-frontend/src/main/java/org/sonar/plugins/java/api/JavaVersion.java index 0901b4bfc75..bf6bb60a786 100644 --- a/java-frontend/src/main/java/org/sonar/plugins/java/api/JavaVersion.java +++ b/java-frontend/src/main/java/org/sonar/plugins/java/api/JavaVersion.java @@ -149,6 +149,14 @@ public interface JavaVersion { */ boolean isJava22Compatible(); + /** + * Test if java version of the project is greater than or equal to 23. + * Remark - Contrary to other isJava*Compatible methods, this one will NOT return true if version is not set + * @return true if java version used is >= 23 + * @since SonarJava 8.0: Support of Java 23 + */ + boolean isJava23Compatible(); + /** * get java version as integer * @return an int representing the java version diff --git a/java-frontend/src/main/java/org/sonar/plugins/java/api/tree/SyntaxTrivia.java b/java-frontend/src/main/java/org/sonar/plugins/java/api/tree/SyntaxTrivia.java index 6165e190078..f8962590417 100644 --- a/java-frontend/src/main/java/org/sonar/plugins/java/api/tree/SyntaxTrivia.java +++ b/java-frontend/src/main/java/org/sonar/plugins/java/api/tree/SyntaxTrivia.java @@ -27,8 +27,23 @@ @Beta public interface SyntaxTrivia extends Tree { + enum CommentKind { + LINE, + BLOCK, + JAVADOC, + MARKDOWN + } + String comment(); + String commentContent(); + + CommentKind commentKind(); + + boolean isComment(CommentKind kind); + + boolean isComment(CommentKind... kinds); + /** * @deprecated for removal, since = 7.3, use range().start().line() */ diff --git a/java-frontend/src/test/files/highlighter/ExampleWithModuleKeywords.java b/java-frontend/src/test/files/highlighter/ExampleWithModuleKeywords.java index 41beaeaff84..7b935a60f68 100644 --- a/java-frontend/src/test/files/highlighter/ExampleWithModuleKeywords.java +++ b/java-frontend/src/test/files/highlighter/ExampleWithModuleKeywords.java @@ -17,5 +17,7 @@ void requires(Object exports, Object opens) { provides(); } + /// Markedown javadoc begin + /// Markedown javadoc end abstract void provides(); } diff --git a/java-frontend/src/test/java/org/sonar/java/ast/JavaAstScannerTest.java b/java-frontend/src/test/java/org/sonar/java/ast/JavaAstScannerTest.java index 92b02a0c73e..21582c21954 100644 --- a/java-frontend/src/test/java/org/sonar/java/ast/JavaAstScannerTest.java +++ b/java-frontend/src/test/java/org/sonar/java/ast/JavaAstScannerTest.java @@ -47,8 +47,13 @@ import org.sonar.java.classpath.ClasspathForTest; import org.sonar.java.exceptions.ApiMismatchException; import org.sonar.java.model.JParserConfig; +import org.sonar.java.model.JParserTestUtils; +import org.sonar.java.model.JavaTree; import org.sonar.java.model.JavaVersionImpl; import org.sonar.java.model.VisitorsBridge; +import org.sonar.java.model.declaration.ClassTreeImpl; +import org.sonar.java.model.declaration.MethodTreeImpl; +import org.sonar.java.model.statement.BlockTreeImpl; import org.sonar.java.notchecks.VisitorNotInChecksPackage; import org.sonar.java.testing.ThreadLocalLogTester; import org.sonar.plugins.java.api.JavaFileScanner; @@ -58,6 +63,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; @@ -175,12 +181,44 @@ void test_should_use_java_version() { } @Test - void test_should_log_fail_parsing_with_incorrect_version() { + void test_do_not_log_fail_parsing_with_incorrect_version() { scanWithJavaVersion(8, Collections.singletonList(TestUtils.inputFile("src/test/files/metrics/Java15SwitchExpression.java"))); - assertThat(logTester.logs(Level.ERROR)).containsExactly( - "Unable to parse source file : 'src/test/files/metrics/Java15SwitchExpression.java'", - "Parse error at line 3 column 13: Switch Expressions are supported from Java 14 onwards only" - ); + // read test: ecj_does_not_raise_problems_on_non_compiling_switch_expression + // to understand why logTester.logs(Level.ERROR) is empty + assertThat(logTester.logs(Level.ERROR)).isEmpty(); + } + + @Test + void ecj_does_not_raise_problems_on_non_compiling_switch_expression() { + var source = """ + public class File { + void java15SwitchExpression() { + int h = 24; + int i = switch (1) { + case 2 -> 1; + default -> 2; + }; + int j = 42; + } + } + """; + // BUG in ECJ 3.41: When the Java source version is 8, ECJ incorrectly does not report errors for switch expressions (introduced in later Java versions). + // Affected tests: + // - module_info_should_not_be_analyzed_or_change_the_version + // - remove_info_ro_warning_log_related_to_module_info + // - test_should_log_fail_parsing_with_incorrect_version + // Once the ECJ bug is fixed, these tests should be updated to expect logging error. + + // JParserTestUtils.parse throw an error in case of non compiling code, as java source version is 8 + // JParserTestUtils.parse should throw an error + assertDoesNotThrow(()->{ + var cu = (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(source, new JavaVersionImpl(8)); + var clazz = (ClassTreeImpl) cu.types().get(0); + var method = (MethodTreeImpl) clazz.members().get(0); + var block = (BlockTreeImpl) method.block(); + // The only consequence of the non-compiling code is that the block is empty + assertThat(block.body()).isEmpty(); + }); } @ParameterizedTest @@ -283,10 +321,9 @@ void module_info_should_not_be_analyzed_or_change_the_version() { List filteredLogs = TestUtils.filterOutAnalysisProgressLogLines(logs); assertThat(filteredLogs).contains("1/1 source file has been analyzed"); assertThat(filteredLogs.size()).isBetween(3,4); - assertThat(logTester.logs(Level.ERROR)).containsExactly( - "Unable to parse source file : 'src/test/files/metrics/Java15SwitchExpression.java'", - "Parse error at line 3 column 13: Switch Expressions are supported from Java 14 onwards only" - ); + // read test: ecj_does_not_raise_problems_on_non_compiling_switch_expression + // to understand why logTester.logs(Level.ERROR) is empty + assertThat(logTester.logs(Level.ERROR)).isEmpty(); assertThat(logTester.logs(Level.WARN)) // two files, only one log .hasSize(1) @@ -306,10 +343,9 @@ void remove_info_ro_warning_log_related_to_module_info() { )); assertThat(logTester.logs(Level.INFO)).isEmpty(); assertThat(logTester.logs(Level.WARN)).isEmpty(); - assertThat(logTester.logs(Level.ERROR)).containsExactly( - "Unable to parse source file : 'src/test/files/metrics/Java15SwitchExpression.java'", - "Parse error at line 3 column 13: Switch Expressions are supported from Java 14 onwards only" - ); + // read test: ecj_does_not_raise_problems_on_non_compiling_switch_expression + // to understand why logTester.logs(Level.ERROR) is empty + assertThat(logTester.logs(Level.ERROR)).isEmpty(); } @Test diff --git a/java-frontend/src/test/java/org/sonar/java/ast/visitors/SyntaxHighlighterVisitorTest.java b/java-frontend/src/test/java/org/sonar/java/ast/visitors/SyntaxHighlighterVisitorTest.java index 31bbc65813a..a8333cdbbfb 100644 --- a/java-frontend/src/test/java/org/sonar/java/ast/visitors/SyntaxHighlighterVisitorTest.java +++ b/java-frontend/src/test/java/org/sonar/java/ast/visitors/SyntaxHighlighterVisitorTest.java @@ -152,9 +152,10 @@ void test_restricted_keywords_outside_module() { assertThatHasNotBeenHighlighted(componentKey, 15, 12, 15, 16); // with assertThatHasNotBeenHighlighted(componentKey, 16, 12, 16, 16); // uses assertThatHasNotBeenHighlighted(componentKey, 17, 5, 17, 13); // provides - assertThatHasBeenHighlighted(componentKey, 20, 3, 20, 11, TypeOfText.KEYWORD); // abstract - assertThatHasBeenHighlighted(componentKey, 20, 12, 20, 16, TypeOfText.KEYWORD); // void - assertThatHasNotBeenHighlighted(componentKey, 20, 17, 20, 25); // provides + assertThatHasBeenHighlighted(componentKey, 20, 3, 21, 28, TypeOfText.STRUCTURED_COMMENT); // markdown javadoc + assertThatHasBeenHighlighted(componentKey, 22, 3, 22, 11, TypeOfText.KEYWORD); // abstract + assertThatHasBeenHighlighted(componentKey, 22, 12, 22, 16, TypeOfText.KEYWORD); // void + assertThatHasNotBeenHighlighted(componentKey, 22, 17, 22, 25); // provides } @Test diff --git a/java-frontend/src/test/java/org/sonar/java/classpath/DependencyVersionInferenceTest.java b/java-frontend/src/test/java/org/sonar/java/classpath/DependencyVersionInferenceTest.java index 720c0996ad7..4bca3a997ab 100644 --- a/java-frontend/src/test/java/org/sonar/java/classpath/DependencyVersionInferenceTest.java +++ b/java-frontend/src/test/java/org/sonar/java/classpath/DependencyVersionInferenceTest.java @@ -40,7 +40,7 @@ void inferLombok() { // Assert Assertions.assertTrue(version.isPresent()); - assertEquals(new VersionImpl(1, 18, 30, null), version.get()); + assertEquals(new VersionImpl(1, 18, 38, null), version.get()); } diff --git a/java-frontend/src/test/java/org/sonar/java/model/InternalSyntaxTriviaTest.java b/java-frontend/src/test/java/org/sonar/java/model/InternalSyntaxTriviaTest.java index 4498224346f..05bffe8f763 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/InternalSyntaxTriviaTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/InternalSyntaxTriviaTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test; import org.sonar.plugins.java.api.location.Range; import org.sonar.plugins.java.api.tree.SyntaxTrivia; +import org.sonar.plugins.java.api.tree.SyntaxTrivia.CommentKind; import org.sonar.plugins.java.api.tree.Tree; import static org.assertj.core.api.Assertions.assertThat; @@ -27,9 +28,13 @@ class InternalSyntaxTriviaTest { @Test - void single_line_comment() { - SyntaxTrivia trivia = InternalSyntaxTrivia.create("// comment", 42, 21); + void line_comment() { + SyntaxTrivia trivia = new InternalSyntaxTrivia(CommentKind.LINE, "// comment", 42, 21); assertThat(trivia.comment()).isEqualTo("// comment"); + assertThat(trivia.commentContent()).isEqualTo(" comment"); + assertThat(trivia.commentKind()).isEqualTo(CommentKind.LINE); + assertThat(trivia.isComment(CommentKind.LINE)).isTrue(); + assertThat(trivia.isComment(CommentKind.BLOCK)).isFalse(); assertThat(trivia.startLine()).isEqualTo(42); assertThat(trivia.column()).isEqualTo(21); assertThat(trivia.range()).isEqualTo(Range.at(42, 22, 42, 32)); @@ -43,9 +48,11 @@ void single_line_comment() { } @Test - void multi_line_comment() { - SyntaxTrivia trivia = InternalSyntaxTrivia.create("/* line1\n line2 */", 42, 21); + void block_comment() { + SyntaxTrivia trivia = new InternalSyntaxTrivia(CommentKind.BLOCK, "/* line1\n line2 */", 42, 21); assertThat(trivia.comment()).isEqualTo("/* line1\n line2 */"); + assertThat(trivia.commentContent()).isEqualTo(" line1\n line2 "); + assertThat(trivia.commentKind()).isEqualTo(CommentKind.BLOCK); assertThat(trivia.startLine()).isEqualTo(42); assertThat(trivia.column()).isEqualTo(21); assertThat(trivia.range()).isEqualTo(Range.at(42, 22, 43, 12)); @@ -54,4 +61,51 @@ void multi_line_comment() { assertThat(tree.getLine()).isEqualTo(42); } + @Test + void javadoc_comment() { + SyntaxTrivia trivia = new InternalSyntaxTrivia(CommentKind.JAVADOC, "/** method\n * foo */", 42, 21); + assertThat(trivia.comment()).isEqualTo("/** method\n * foo */"); + assertThat(trivia.commentContent()).isEqualTo(" method\n * foo "); + assertThat(trivia.commentKind()).isEqualTo(CommentKind.JAVADOC); + assertThat(trivia.startLine()).isEqualTo(42); + assertThat(trivia.column()).isEqualTo(21); + assertThat(trivia.range()).isEqualTo(Range.at(42, 22, 43, 11)); + + JavaTree tree = (JavaTree) trivia; + assertThat(tree.getLine()).isEqualTo(42); + } + + @Test + void markdown_comment() { + SyntaxTrivia trivia = new InternalSyntaxTrivia(CommentKind.MARKDOWN, "/// line1\r\n /// line2\r /// line3\n ///", 42, 21); + assertThat(trivia.comment()).isEqualTo("/// line1\r\n /// line2\r /// line3\n ///"); + assertThat(trivia.commentContent()).isEqualTo(" line1\n line2\n line3\n"); + assertThat(trivia.commentKind()).isEqualTo(CommentKind.MARKDOWN); + assertThat(trivia.startLine()).isEqualTo(42); + assertThat(trivia.column()).isEqualTo(21); + assertThat(trivia.range()).isEqualTo(Range.at(42, 22, 45, 6)); + + JavaTree tree = (JavaTree) trivia; + assertThat(tree.getLine()).isEqualTo(42); + } + + @Test + void invalid_comment_kind() { + assertThatThrownBy(() -> new InternalSyntaxTrivia(CommentKind.LINE, "/* invalid */", 42, 21)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid comment kind: LINE for comment: /* invalid */"); + assertThatThrownBy(() -> new InternalSyntaxTrivia(CommentKind.BLOCK, "// invalid", 42, 21)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid comment kind: BLOCK for comment: // invalid"); + assertThatThrownBy(() -> new InternalSyntaxTrivia(CommentKind.BLOCK, "/* broken", 42, 21)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid comment kind: BLOCK for comment: /* broken"); + assertThatThrownBy(() -> new InternalSyntaxTrivia(CommentKind.JAVADOC, "/* invalid */", 42, 21)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid comment kind: JAVADOC for comment: /* invalid */"); + assertThatThrownBy(() -> new InternalSyntaxTrivia(CommentKind.JAVADOC, "/** broken", 42, 21)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid comment kind: JAVADOC for comment: /** broken"); + } + } diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java index 16be8f4988e..99c9a3ce3bb 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java @@ -24,7 +24,6 @@ import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.Block; -import org.eclipse.jdt.core.dom.BreakStatement; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.ITypeBinding; @@ -1270,7 +1269,7 @@ private java.util.Collection> samples() { } @Test - void ecj_exception_when_computing_metadata_should_be_caught() { + void no_metadata_annotations_on_noncompiling_annotation_code() { String source = "" + " public class C {\n" + " interface I1 {}\n" + @@ -1291,7 +1290,7 @@ void ecj_exception_when_computing_metadata_should_be_caught() { ExpressionStatementTree expression = (ExpressionStatementTree) m.block().body().get(0); SymbolMetadata metadata = assertDoesNotThrow(() -> ((MethodInvocationTreeImpl) expression.expression()).methodSymbol().metadata()); - assertThat(metadata).isEqualTo(Symbols.EMPTY_METADATA); + assertThat(metadata.annotations()).isEmpty(); } @Test @@ -1669,17 +1668,19 @@ private static JavaTree.CompilationUnitTreeImpl test(String source) { return (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(source); } + /** + * Previously ECJ was adding an implicit break statement for empty switch cases blocks. + * This is not the case anymore, and we do not need to test this. + */ @Test - void should_skip_implicit_break_statement() { + void not_need_to_skip_implicit_break_statement() { final String source = "class C { void m() { switch (0) { case 0 -> { } } } }"; CompilationUnit cu = createAST(source); TypeDeclaration c = (TypeDeclaration) cu.types().get(0); MethodDeclaration m = c.getMethods()[0]; SwitchStatement s = (SwitchStatement) m.getBody().statements().get(0); Block block = (Block) s.statements().get(1); - BreakStatement breakStatement = (BreakStatement) block.statements().get(0); - assertThat(breakStatement.getLength()) - .isEqualTo(2); + assertThat(block.statements()).isEmpty(); CompilationUnitTree compilationUnit = test(source); ClassTree cls = (ClassTree) compilationUnit.types().get(0); diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java index 0101775b01b..33b5de209fc 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java @@ -28,7 +28,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Deque; -import java.util.EmptyStackException; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -46,10 +45,13 @@ import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.internal.compiler.parser.TerminalTokens; +import org.eclipse.jdt.internal.formatter.Token; import org.eclipse.jdt.internal.formatter.TokenManager; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InOrder; import org.mockito.Mockito; import org.slf4j.event.Level; @@ -59,6 +61,7 @@ import org.sonar.java.model.JavaTree.CompilationUnitTreeImpl; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.testing.ThreadLocalLogTester; +import org.sonar.plugins.java.api.JavaVersion; import org.sonar.plugins.java.api.location.Range; import org.sonar.plugins.java.api.tree.ArrayTypeTree; import org.sonar.plugins.java.api.tree.BlockTree; @@ -75,6 +78,7 @@ import org.sonar.plugins.java.api.tree.RecordPatternTree; import org.sonar.plugins.java.api.tree.ReturnStatementTree; import org.sonar.plugins.java.api.tree.SwitchExpressionTree; +import org.sonar.plugins.java.api.tree.SyntaxTrivia.CommentKind; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.TryStatementTree; import org.sonar.plugins.java.api.tree.TypePatternTree; @@ -82,6 +86,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; @@ -90,6 +95,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import static org.sonar.java.model.JParser.convertTokenTypeToCommentKind; +import static org.sonar.java.model.JParser.isComment; import static org.sonar.java.model.JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; import static org.sonar.java.model.JParserConfig.Mode.BATCH; import static org.sonar.java.model.JParserConfig.Mode.FILE_BY_FILE; @@ -252,6 +259,15 @@ void declaration_enum() { ); } + @Test + void processEnumConstantDeclaration_sets_enum_initializer_following_a_markdown_comment_as_next_token() { + CompilationUnitTree cu = test("enum E { C /// comment before initializer\n (3); E(int a) {} }"); + ClassTree t = (ClassTree) cu.types().get(0); + EnumConstantTree c = (EnumConstantTree) t.members().get(0); + assertThat(c.initializer().arguments().openParenToken()).isNotNull(); + assertThat(c.initializer().arguments()).hasSize(1); + } + @Test void statement_variable_declaration() { CompilationUnitTree t = test("class C { void m() { int a, b; } }"); @@ -267,16 +283,9 @@ void statement_variable_declaration() { } @Test - void fail_to_parse_static_method_invocation_on_a_conditional_expression_with_null_literal_on_the_else_operand() { - // Due to a bug in ECJ 3.39.0, the bellow Java code can not be parsed, the parser fails throwing: - // java.util.EmptyStackException: null - // at java.base/java.util.Stack.peek(Stack.java:103) - // at java.base/java.util.Stack.pop(Stack.java:85) - // at org.eclipse.jdt.internal.compiler.codegen.OperandStack.pop(OperandStack.java:85) - // at org.eclipse.jdt.internal.compiler.codegen.OperandStack.pop(OperandStack.java:109) - // at org.eclipse.jdt.internal.compiler.ast.ConditionalExpression.generateCode(ConditionalExpression.java:322) + void parse_static_method_invocation_on_a_conditional_expression_with_null_literal_on_the_else_operand() { List classpath = List.of(); - assertThatThrownBy(() -> JParserTestUtils.parse("Reproducer.java", """ + assertDoesNotThrow(() -> JParserTestUtils.parse("Reproducer.java", """ package checks; public class Reproducer { @@ -286,10 +295,7 @@ void foo(Reproducer o) { static void bar() { } } - """, classpath)) - .isInstanceOf(RecognitionException.class) - .hasMessage("ECJ: Unable to parse file.") - .hasCauseInstanceOf(EmptyStackException.class); + """, classpath)); } @Test @@ -516,7 +522,65 @@ void test_first_index_of_tokens_in_eclipse_ast() { assertThat(JParser.firstIndexIn(tokenManager, compilationUnit, TerminalTokens.TokenNameRBRACE, TerminalTokens.TokenNameLBRACE)).isEqualTo(2); assertThatThrownBy(() -> JParser.firstIndexIn(tokenManager, compilationUnit, TerminalTokens.TokenNamebreak, TerminalTokens.TokenNameconst)) .isInstanceOf(IllegalStateException.class) - .hasMessage("Failed to find token 83 or 138 in the tokens of a org.eclipse.jdt.core.dom.CompilationUnit"); + .hasMessage("Failed to find token 82 or 136 in the tokens of a org.eclipse.jdt.core.dom.CompilationUnit"); + } + + @Test + void test_comment_tokens() { + String version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION.effectiveJavaVersionAsString(); + String unitName = "C.java"; + String source = """ + class A { + // line comment + /* block comment */ + /// markdown comment 1 + /// markdown comment 2 + /** + * javadoc comment + */ + void foo() {} + } + """; + TokenManager tokens = JParser.createTokenManager(version, unitName, source); + + assertThat(tokens.size()).isEqualTo(15); + + Token token0 = tokens.get(0); + assertThat(token0.toString(source)).isEqualTo("class"); + assertThat(token0.isComment()).isFalse(); + assertThat(isComment(token0)).isFalse(); + assertThatThrownBy(() -> convertTokenTypeToCommentKind(token0)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Unexpected value: 71"); + + Token token3 = tokens.get(3); + assertThat(token3.toString(source)).isEqualTo("// line comment"); + assertThat(token3.isComment()).isTrue(); + assertThat(isComment(token3)).isTrue(); + assertThat(convertTokenTypeToCommentKind(token3)).isEqualTo(CommentKind.LINE); + + Token token4 = tokens.get(4); + assertThat(token4.toString(source)).isEqualTo("/* block comment */"); + assertThat(token4.isComment()).isTrue(); + assertThat(isComment(token4)).isTrue(); + assertThat(convertTokenTypeToCommentKind(token4)).isEqualTo(CommentKind.BLOCK); + + Token token5 = tokens.get(5); + assertThat(token5.toString(source)).isEqualTo("/// markdown comment 1\n /// markdown comment 2"); + assertThat(token5.isComment()).isFalse(); // JDT issue https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3914 + assertThat(isComment(token5)).isTrue(); + assertThat(convertTokenTypeToCommentKind(token5)).isEqualTo(CommentKind.MARKDOWN); + + Token token6 = tokens.get(6); + assertThat(token6.toString(source)).isEqualTo("/**\n * javadoc comment\n */"); + assertThat(token6.isComment()).isTrue(); + assertThat(isComment(token6)).isTrue(); + assertThat(convertTokenTypeToCommentKind(token6)).isEqualTo(CommentKind.JAVADOC); + + Token token7 = tokens.get(7); + assertThat(token7.toString(source)).isEqualTo("void"); + assertThat(token7.isComment()).isFalse(); + assertThat(isComment(token7)).isFalse(); } @Test @@ -530,15 +594,21 @@ void dont_include_running_VM_Bootclasspath_if_jvm_rt_jar_already_provided_in_cla assertThat(s1.type().symbolType().fullyQualifiedName()).isEqualTo("Recovered#typeBindingLString;0"); } - @Test - void dont_include_running_VM_Bootclasspath_if_android_runtime_already_provided_in_classpath(@TempDir Path tempFolder) throws IOException { - VariableTree s1 = parseAndGetVariable("class C { void m() { String a; } }"); - assertThat(s1.type().symbolType().fullyQualifiedName()).isEqualTo("java.lang.String"); + @ParameterizedTest + @ValueSource(ints = {8, 9, 17}) + void dont_include_running_VM_Bootclasspath_if_android_runtime_already_provided_in_classpath(int javaVersion) throws IOException { + JavaVersion androidVersion = new JavaVersionImpl(javaVersion); + String source = "class C { void m() { String a; Integer b; } }"; + VariableTree a = parseAndGetVariable(source, androidVersion); + VariableTree b = (VariableTree) ((BlockTree) a.parent()).body().get(1); + assertThat(a.type().symbolType().fullyQualifiedName()).isEqualTo("java.lang.String"); + assertThat(b.type().symbolType().fullyQualifiedName()).isEqualTo("java.lang.Integer"); - Path fakeAndroidSdk = tempFolder.resolve("android.jar"); - Files.createFile(fakeAndroidSdk); - s1 = parseAndGetVariable("class C { void m() { String a; } }", fakeAndroidSdk.toFile()); - assertThat(s1.type().symbolType().fullyQualifiedName()).isEqualTo("Recovered#typeBindingLString;0"); + Path fakeAndroidSdk = Path.of("src", "test", "resources", "android.jar").toRealPath(); + a = parseAndGetVariable(source, androidVersion, fakeAndroidSdk.toFile()); + b = (VariableTree) ((BlockTree) a.parent()).body().get(1); + assertThat(a.type().symbolType().fullyQualifiedName()).isEqualTo("Recovered#typeBindingLString;0"); + assertThat(b.type().symbolType().fullyQualifiedName()).isEqualTo("java.lang.Integer"); } @Test @@ -924,7 +994,11 @@ private static void add(File source, File baseDir, JarOutputStream target) { } private VariableTree parseAndGetVariable(String code, File... classpath) { - CompilationUnitTree t = JParserTestUtils.parse("Foo.java", code, Arrays.asList(classpath)); + return parseAndGetVariable(code, JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION, classpath); + } + + private VariableTree parseAndGetVariable(String code, JavaVersion javaVersion, File... classpath) { + CompilationUnitTree t = JParserTestUtils.parse("Foo.java", code, Arrays.asList(classpath), javaVersion); ClassTree c = (ClassTree) t.types().get(0); MethodTree m = (MethodTree) c.members().get(0); BlockTree s = m.block(); diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java b/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java index 1a99a0afebc..990bd1326e7 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java @@ -72,8 +72,15 @@ private static CompilationUnitTree parse(String unitName, String source) { return parse(unitName, source, DEFAULT_CLASSPATH); } + public static CompilationUnitTree parse(String source, JavaVersion version) { + return parse("File.java", source, DEFAULT_CLASSPATH, version); + } + public static CompilationUnitTree parse(String unitName, String source, List classpath) { - JavaVersion version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; + return parse(unitName, source, classpath, JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION); + } + + public static CompilationUnitTree parse(String unitName, String source, List classpath, JavaVersion version) { return JParser.parse(JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).astParser(), version.toString(), unitName, source); } diff --git a/java-frontend/src/test/java/org/sonar/java/model/JavaVersionImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/JavaVersionImplTest.java index 90539101e78..cd5890b949f 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JavaVersionImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JavaVersionImplTest.java @@ -47,6 +47,7 @@ void no_version_set() { assertThat(version.isJava20Compatible()).isFalse(); assertThat(version.isJava21Compatible()).isFalse(); assertThat(version.isJava22Compatible()).isFalse(); + assertThat(version.isJava23Compatible()).isFalse(); assertThat(version.asInt()).isEqualTo(-1); } @@ -71,6 +72,7 @@ void java_versions(int javaVersionAsInt) { assertThat(version.isJava20Compatible()).isEqualTo(javaVersionAsInt >= 20); assertThat(version.isJava21Compatible()).isEqualTo(javaVersionAsInt >= 21); assertThat(version.isJava22Compatible()).isEqualTo(javaVersionAsInt >= 22); + assertThat(version.isJava23Compatible()).isEqualTo(javaVersionAsInt >= 23); assertThat(version.asInt()).isEqualTo(javaVersionAsInt); } @@ -91,9 +93,9 @@ void compatibilityMesssages() { @Test void test_effective_java_version() { - assertThat(new JavaVersionImpl().effectiveJavaVersionAsString()).isEqualTo("22"); + assertThat(new JavaVersionImpl().effectiveJavaVersionAsString()).isEqualTo("23"); assertThat(new JavaVersionImpl(10).effectiveJavaVersionAsString()).isEqualTo("10"); - assertThat(new JavaVersionImpl(-1).effectiveJavaVersionAsString()).isEqualTo("22"); + assertThat(new JavaVersionImpl(-1).effectiveJavaVersionAsString()).isEqualTo("23"); } @Test diff --git a/java-frontend/src/test/resources/android.jar b/java-frontend/src/test/resources/android.jar new file mode 100644 index 00000000000..bf14efb7516 Binary files /dev/null and b/java-frontend/src/test/resources/android.jar differ diff --git a/java-frontend/src/test/resources/android.jar.create.sh b/java-frontend/src/test/resources/android.jar.create.sh new file mode 100755 index 00000000000..99e33aace44 --- /dev/null +++ b/java-frontend/src/test/resources/android.jar.create.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# This script create an android.jar file from the Android SDK by only keeping the following files: +# AndroidManifest.xml +# META-INF/MANIFEST.MF +# java/lang/Integer.class +# java/lang/Object.class +# +# Note: we don't keep "module-info.class" because it does not exist and the ".class" files version are Java 8. +# When the generated android.jar is added to the classpath of the Sonar Java analyzer (e.g. using "sonar.java.libraries") +# It should be considered as system classes. In this context the semantic information of "java.lang.Object" and +# "java.lang.Integer" should exists but not other classes like "java.lang.String". + +docker run \ + --rm \ + --interactive \ + --tty \ + --name android_sdk \ + --memory "1g" \ + --cpus "2" \ + --volume "${SCRIPT_DIR}:/home/mobiledevops/tmp:rw" \ + -- \ + "mobiledevops/android-sdk-image:34.0.0" \ + "/bin/bash" -c "\ + mkdir /home/mobiledevops/android && \ + cd /home/mobiledevops/android && \ + jar xf /opt/android-sdk-linux/platforms/android-33/android.jar && \ + mkdir /home/mobiledevops/android-min && \ + cd /home/mobiledevops/android-min && \ + cp ../android/AndroidManifest.xml AndroidManifest.xml && \ + cp -r ../android/META-INF META-INF && \ + mkdir -p java/lang && \ + cp -r ../android/java/lang/Object.class java/lang/Object.class && \ + cp -r ../android/java/lang/Integer.class java/lang/Integer.class && \ + jar cf ../android-min.jar . && \ + cp /home/mobiledevops/android-min.jar /home/mobiledevops/tmp/android.jar" diff --git a/java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/ExplodedGraphWalkerTest.java b/java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/ExplodedGraphWalkerTest.java index c26d3efe23b..1ac459e2d88 100644 --- a/java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/ExplodedGraphWalkerTest.java +++ b/java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/ExplodedGraphWalkerTest.java @@ -168,7 +168,7 @@ void switchWithPatterns() { .onFile(TestUtils.nonCompilingTestSourcesPath("symbolicexecution/engine/SwitchWithPatterns.java")) .withChecks(seChecks()) .withClassPath(SETestUtils.CLASS_PATH) - .withJavaVersion(22, true) + .withJavaVersion(23, true) .verifyNoIssues(); }