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 extends Annotation> 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();
}