diff --git a/.cirrus.star b/.cirrus.star deleted file mode 100644 index 462c380..0000000 --- a/.cirrus.star +++ /dev/null @@ -1,5 +0,0 @@ -load("github.com/SonarSource/cirrus-modules@v3", "load_features") - - -def main(ctx): - return load_features(ctx, only_if=dict()) diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index 12d3738..0000000 --- a/.cirrus.yml +++ /dev/null @@ -1,110 +0,0 @@ -env: - CIRRUS_CLONE_DEPTH: "20" - CIRRUS_SHELL: bash - - ARTIFACTORY_URL: VAULT[development/kv/data/repox data.url] - ARTIFACTORY_PRIVATE_USERNAME: vault-${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-private-reader - ARTIFACTORY_PRIVATE_PASSWORD: VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-private-reader access_token] - DEVELOCITY_TOKEN: VAULT[development/kv/data/develocity data.token] - DEVELOCITY_ACCESS_KEY: develocity.sonar.build=${DEVELOCITY_TOKEN} - SLACK_TOKEN: VAULT[development/kv/data/slack data.token] - SLACK_CHANNEL: squad-ide-mcp-server-bots - GRADLE_VERSION: "8.13" - GRADLE_USER_HOME: ${CIRRUS_WORKING_DIR}/.gradle_cache - -only_pr_and_maintained_branches: &ONLY_PR_AND_MAINTAINED_BRANCHES - skip: "changesIncludeOnly('README.md')" - only_if: $CIRRUS_USER_COLLABORATOR == 'true' && $CIRRUS_TAG == "" && $CIRRUS_BUILD_SOURCE != "cron" - && ($CIRRUS_PR != "" || $CIRRUS_BRANCH == $CIRRUS_DEFAULT_BRANCH || $CIRRUS_BRANCH =~ "branch-.*" || $CIRRUS_BRANCH =~ "dogfood-on-.*") - -only_main_branches: &ONLY_MAIN_BRANCHES - skip: "changesIncludeOnly('*.txt', '**/README.md')" - only_if: $CIRRUS_USER_COLLABORATOR == 'true' && $CIRRUS_TAG == "" && ($CIRRUS_BRANCH == $CIRRUS_DEFAULT_BRANCH || $CIRRUS_BRANCH =~ "branch-.*") && $CIRRUS_BUILD_SOURCE != "cron" - -eks_container_definition: &CONTAINER_DEFINITION - image: ${CIRRUS_AWS_ACCOUNT}.dkr.ecr.eu-central-1.amazonaws.com/base:j21-latest - cluster_name: ${CIRRUS_CLUSTER_NAME} - region: eu-central-1 - namespace: default - -setup_gradle_cache_template: &SETUP_GRADLE_CACHE - gradle_cache: - folder: ${GRADLE_USER_HOME}/caches - fingerprint_script: - - echo $CIRRUS_OS - - cat **/*.gradle* gradle/wrapper/gradle-wrapper.properties || true - populate_script: - - mkdir -p ${GRADLE_USER_HOME}/caches - -cleanup_gradle_cache_script_template: &CLEANUP_GRADLE_CACHE_SCRIPT - cleanup_gradle_script: | - rm -rf ${GRADLE_USER_HOME}/caches/$GRADLE_VERSION/ - rm -rf ${GRADLE_USER_HOME}/daemon/ - rm -rf ${GRADLE_USER_HOME}/caches/transforms-* - rm -rf ${GRADLE_USER_HOME}/caches/journal-* - /usr/bin/find ${GRADLE_USER_HOME}/caches/ -name "*.lock" -type f -delete - -build_task: - eks_container: - <<: *CONTAINER_DEFINITION - cpu: 1 - memory: 4G - env: - ARTIFACTORY_DEPLOY_REPO: sonarsource-public-qa - ARTIFACTORY_DEPLOY_USERNAME: VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-qa-deployer username] - ARTIFACTORY_DEPLOY_PASSWORD: VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-qa-deployer access_token] - DEPLOY_PULL_REQUEST: "true" - SONAR_HOST_URL: VAULT[development/kv/data/sonarcloud data.url] - SONAR_TOKEN: VAULT[development/kv/data/sonarcloud data.token] - build_script: - - source cirrus-env BUILD - - source .cirrus/use-gradle-wrapper.sh - - source set_gradle_build_version - - regular_gradle_build_deploy_analyze :cyclonedxBom jacocoTestReport - -mend_scan_task: - depends_on: - - build - <<: *ONLY_MAIN_BRANCHES - eks_container: - <<: *CONTAINER_DEFINITION - cpu: 1 - memory: 2G - env: - WS_APIKEY: VAULT[development/kv/data/mend data.apikey] - <<: *SETUP_GRADLE_CACHE - mend_script: | - source cirrus-env QA - source .cirrus/use-gradle-wrapper.sh - source set_gradle_build_version - source ws_scan.sh - <<: *CLEANUP_GRADLE_CACHE_SCRIPT - allow_failures: "true" - always: - ws_scan_artifacts: - path: "whitesource/**/*" - on_failure: - slack_notification_script: | - source slack-failure-notification - -promote_task: - depends_on: - - build - - mend_scan - <<: *ONLY_PR_AND_MAINTAINED_BRANCHES - eks_container: - <<: *CONTAINER_DEFINITION - cpu: 1 - memory: 1G - env: - ARTIFACTORY_PROMOTE_ACCESS_TOKEN: VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-promoter access_token] - GITHUB_TOKEN: VAULT[development/github/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-promotion token] - JDK_VERSION: "21" - <<: *SETUP_GRADLE_CACHE - promote_script: | - source .cirrus/use-gradle-wrapper.sh - cirrus_promote_gradle - <<: *CLEANUP_GRADLE_CACHE_SCRIPT - on_failure: - slack_notification_script: | - source slack-failure-notification diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..9723fe5 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,52 @@ +name: Build +on: + push: + branches: + - master + - branch-* + - dogfood-on-* + pull_request: + merge_group: + workflow_dispatch: + +jobs: + build: + concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.ref_name != github.event.repository.default_branch }} + runs-on: ubuntu-24.04-large + name: Build + permissions: + id-token: write + contents: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2.4.4 + with: + version: 2025.7.12 + - uses: SonarSource/ci-github-actions/build-gradle@master + with: + sonar-platform: sqc-eu + deploy-pull-request: true + artifactory-reader-role: private-reader + artifactory-deployer-role: qa-deployer + + promote: + needs: [ build ] + concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.ref_name != github.event.repository.default_branch }} + runs-on: ubuntu-24.04-large + name: Promote + permissions: + id-token: write + contents: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2.4.4 + with: + cache_save: false + version: 2025.7.12 + - uses: SonarSource/ci-github-actions/promote@v1 + with: + promote-pull-request: true diff --git a/README.md b/README.md index 7398778..88ac0df 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SonarQube MCP Server -[![Build Status](https://api.cirrus-ci.com/github/SonarSource/sonarqube-mcp-server.svg?branch=master)](https://cirrus-ci.com/github/SonarSource/sonarqube-mcp-server) +[![Build](https://github.com/SonarSource/sonarqube-mcp-server/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/SonarSource/sonarqube-mcp-server/actions/workflows/build.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=SonarSource_sonar-mcp-server&metric=alert_status&token=364a508a1e77096460f8571d8e66b41c99c95bea)](https://sonarcloud.io/summary/new_code?id=SonarSource_sonar-mcp-server) The SonarQube MCP Server is a Model Context Protocol (MCP) server that enables seamless integration with SonarQube Server or Cloud for code quality and security. diff --git a/build.gradle.kts b/build.gradle.kts index 44d0619..422ab01 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,18 +1,12 @@ -import java.nio.file.Files -import java.nio.file.Paths -import java.util.zip.ZipFile -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream -import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream - plugins { - application - jacoco - `maven-publish` - signing - alias(libs.plugins.sonarqube) - alias(libs.plugins.license) - alias(libs.plugins.artifactory) - alias(libs.plugins.cyclonedx) + application + jacoco + `maven-publish` + signing + alias(libs.plugins.sonarqube) + alias(libs.plugins.license) + alias(libs.plugins.artifactory) + alias(libs.plugins.cyclonedx) } group = "org.sonarsource.sonarqube.mcp.server" @@ -23,211 +17,216 @@ val mainClassName = "org.sonarsource.sonarqube.mcp.SonarQubeMcpServer" // The environment variables ARTIFACTORY_PRIVATE_USERNAME and ARTIFACTORY_PRIVATE_PASSWORD are used on CI env // On local box, please add artifactoryUsername and artifactoryPassword to ~/.gradle/gradle.properties val artifactoryUsername = System.getenv("ARTIFACTORY_PRIVATE_USERNAME") - ?: (if (project.hasProperty("artifactoryUsername")) project.property("artifactoryUsername").toString() else "") + ?: (if (project.hasProperty("artifactoryUsername")) project.property("artifactoryUsername").toString() else "") val artifactoryPassword = System.getenv("ARTIFACTORY_PRIVATE_PASSWORD") - ?: (if (project.hasProperty("artifactoryPassword")) project.property("artifactoryPassword").toString() else "") + ?: (if (project.hasProperty("artifactoryPassword")) project.property("artifactoryPassword").toString() else "") java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } } repositories { - maven("https://repox.jfrog.io/repox/sonarsource") { - if (artifactoryUsername.isNotEmpty() && artifactoryPassword.isNotEmpty()) { - credentials { - username = artifactoryUsername - password = artifactoryPassword - } - } - } - mavenCentral { - content { - // avoid dependency confusion - excludeGroupByRegex("com\\.sonarsource.*") - } - } + maven("https://repox.jfrog.io/repox/sonarsource") { + if (artifactoryUsername.isNotEmpty() && artifactoryPassword.isNotEmpty()) { + credentials { + username = artifactoryUsername + password = artifactoryPassword + } + } + } + mavenCentral { + content { + // avoid dependency confusion + excludeGroupByRegex("com\\.sonarsource.*") + } + } } license { - header = rootProject.file("HEADER") - mapping( - mapOf( - "java" to "SLASHSTAR_STYLE", - "kt" to "SLASHSTAR_STYLE", - "svg" to "XML_STYLE", - "form" to "XML_STYLE" - ) - ) - excludes( - listOf("**/*.jar", "**/*.png", "**/README", "**/logback.xml") - ) - strictCheck = true + header = rootProject.file("HEADER") + mapping( + mapOf( + "java" to "SLASHSTAR_STYLE", + "kt" to "SLASHSTAR_STYLE", + "svg" to "XML_STYLE", + "form" to "XML_STYLE" + ) + ) + excludes( + listOf("**/*.jar", "**/*.png", "**/README", "**/logback.xml") + ) + strictCheck = true } val mockitoAgent = configurations.create("mockitoAgent") configurations { - val sqplugins = create("sqplugins") { isTransitive = false } - create("sqplugins_deps") { - extendsFrom(sqplugins) - isTransitive = true - } + val sqplugins = create("sqplugins") { isTransitive = false } + create("sqplugins_deps") { + extendsFrom(sqplugins) + isTransitive = true + } } dependencies { - implementation(libs.mcp.server) - implementation(libs.sonarlint.java.client.utils) - implementation(libs.sonarlint.rpc.java.client) - implementation(libs.sonarlint.rpc.impl) - implementation(libs.commons.langs3) - implementation(libs.commons.text) - implementation(libs.sslcontext.kickstart) - runtimeOnly(libs.logback.classic) - testImplementation(platform(libs.junit.bom)) - testImplementation(libs.junit.jupiter) - testImplementation(libs.mockito.core) - testImplementation(libs.assertj) - testImplementation(libs.awaitility) - testImplementation(libs.wiremock) - testRuntimeOnly(libs.junit.launcher) - "sqplugins"(libs.bundles.sonar.analyzers) - mockitoAgent(libs.mockito.core) { isTransitive = false } + implementation(libs.mcp.server) + implementation(libs.sonarlint.java.client.utils) + implementation(libs.sonarlint.rpc.java.client) + implementation(libs.sonarlint.rpc.impl) + implementation(libs.commons.langs3) + implementation(libs.commons.text) + implementation(libs.sslcontext.kickstart) + runtimeOnly(libs.logback.classic) + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.junit.jupiter) + testImplementation(libs.mockito.core) + testImplementation(libs.assertj) + testImplementation(libs.awaitility) + testImplementation(libs.wiremock) + testRuntimeOnly(libs.junit.launcher) + "sqplugins"(libs.bundles.sonar.analyzers) + mockitoAgent(libs.mockito.core) { isTransitive = false } } +tasks.getByName("sonar").dependsOn(tasks.jacocoTestReport) + tasks { - test { - useJUnitPlatform() - systemProperty("TELEMETRY_DISABLED", "true") - systemProperty("sonarqube.mcp.server.version", project.version) - doNotTrackState("Tests should always run") - maxHeapSize = "2g" - jvmArgs("-javaagent:${mockitoAgent.asPath}", "-XX:MaxMetaspaceSize=512m") - dependsOn("prepareTestPlugins") - } - - jar { - manifest { - attributes["Main-Class"] = mainClassName - attributes["Implementation-Version"] = project.version - } - - from({ - configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) } - }) { - exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", - // module-info comes from sslcontext-kickstart and is looking for slf4j - "META-INF/versions/**/module-info.class", "module-info.class") - } - - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - } - - jacocoTestReport { - reports { - xml.required.set(true) - } - } - - register("prepareTestPlugins") { - val destinationDir = file(layout.buildDirectory) - description = "Prepare SonarQube test plugins" - group = "build" - - // Incremental build support - inputs.files(configurations["sqplugins"]) - outputs.dir("$destinationDir/$pluginName/plugins") - - doLast { - copyTestPlugins(destinationDir, pluginName) - } - } + test { + useJUnitPlatform() + systemProperty("TELEMETRY_DISABLED", "true") + systemProperty("sonarqube.mcp.server.version", project.version) + doNotTrackState("Tests should always run") + maxHeapSize = "2g" + jvmArgs("-javaagent:${mockitoAgent.asPath}", "-XX:MaxMetaspaceSize=512m") + dependsOn("prepareTestPlugins") + } + + jar { + manifest { + attributes["Main-Class"] = mainClassName + attributes["Implementation-Version"] = project.version + } + + from({ + configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) } + }) { + exclude( + "META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", + // module-info comes from sslcontext-kickstart and is looking for slf4j + "META-INF/versions/**/module-info.class", "module-info.class" + ) + } + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } + + jacocoTestReport { + reports { + xml.required.set(true) + } + } + + register("prepareTestPlugins") { + val destinationDir = file(layout.buildDirectory) + description = "Prepare SonarQube test plugins" + group = "build" + + // Incremental build support + inputs.files(configurations["sqplugins"]) + outputs.dir("$destinationDir/$pluginName/plugins") + + doLast { + copyTestPlugins(destinationDir, pluginName) + } + } } fun copyTestPlugins(destinationDir: File, pluginName: String) { - copy { - from(project.configurations["sqplugins"]) - into(file("$destinationDir/$pluginName/plugins")) - } + copy { + from(project.configurations["sqplugins"]) + into(file("$destinationDir/$pluginName/plugins")) + } } application { - mainClass = mainClassName + mainClass = mainClassName } artifactory { - clientConfig.info.buildName = "sonarqube-mcp-server" - clientConfig.info.buildNumber = System.getenv("BUILD_NUMBER") - clientConfig.isIncludeEnvVars = true - clientConfig.envVarsExcludePatterns = "*password*,*PASSWORD*,*secret*,*MAVEN_CMD_LINE_ARGS*,sun.java.command,*token*,*TOKEN*,*LOGIN*,*login*,*key*,*KEY*,*PASSPHRASE*,*signing*" - clientConfig.info.addEnvironmentProperty("PROJECT_VERSION", version.toString()) - clientConfig.info.addEnvironmentProperty("ARTIFACTS_TO_DOWNLOAD", "") - setContextUrl(System.getenv("ARTIFACTORY_URL")) - publish { - repository { - repoKey = System.getenv("ARTIFACTORY_DEPLOY_REPO") - username = System.getenv("ARTIFACTORY_DEPLOY_USERNAME") - password = System.getenv("ARTIFACTORY_DEPLOY_PASSWORD") - } - defaults { - publications("mavenJava") - setProperties( - mapOf( - "vcs.revision" to System.getenv("CIRRUS_CHANGE_IN_REPO"), - "vcs.branch" to (System.getenv("CIRRUS_BASE_BRANCH") - ?: System.getenv("CIRRUS_BRANCH")), - "build.name" to "sonarqube-mcp-server", - "build.number" to System.getenv("BUILD_NUMBER") - ) - ) - setPublishPom(true) - setPublishIvy(false) - } - } + clientConfig.info.buildName = "sonarqube-mcp-server" + clientConfig.info.buildNumber = System.getenv("BUILD_NUMBER") + clientConfig.isIncludeEnvVars = true + clientConfig.envVarsExcludePatterns = + "*password*,*PASSWORD*,*secret*,*MAVEN_CMD_LINE_ARGS*,sun.java.command,*token*,*TOKEN*,*LOGIN*,*login*,*key*,*KEY*,*PASSPHRASE*,*signing*" + clientConfig.info.addEnvironmentProperty("PROJECT_VERSION", version.toString()) + clientConfig.info.addEnvironmentProperty("ARTIFACTS_TO_DOWNLOAD", "") + setContextUrl(System.getenv("ARTIFACTORY_URL")) + publish { + repository { + repoKey = System.getenv("ARTIFACTORY_DEPLOY_REPO") + username = System.getenv("ARTIFACTORY_DEPLOY_USERNAME") + password = System.getenv("ARTIFACTORY_DEPLOY_PASSWORD") + } + defaults { + publications("mavenJava") + setProperties( + mapOf( + "vcs.revision" to System.getenv("GITHUB_SHA"), + "vcs.branch" to (System.getenv("GITHUB_BASE_REF") + ?: System.getenv("GITHUB_REF_NAME")), + "build.name" to "sonarqube-mcp-server", + "build.number" to System.getenv("BUILD_NUMBER") + ) + ) + setPublishPom(true) + setPublishIvy(false) + } + } } publishing { - publications { - create("mavenJava") { - from(components["java"]) - pom { - name.set("sonarqube-mcp-server") - description.set(project.description) - url.set("https://www.sonarqube.org/") - organization { - name.set("SonarSource") - url.set("https://www.sonarqube.org/") - } - licenses { - license { - name.set("SSALv1") - url.set("https://sonarsource.com/license/ssal/") - distribution.set("repo") - } - } - scm { - url.set("https://github.com/SonarSource/sonarqube-mcp-server") - } - developers { - developer { - id.set("sonarsource-team") - name.set("SonarSource Team") - } - } - } - } - } + publications { + create("mavenJava") { + from(components["java"]) + pom { + name.set("sonarqube-mcp-server") + description.set(project.description) + url.set("https://www.sonarqube.org/") + organization { + name.set("SonarSource") + url.set("https://www.sonarqube.org/") + } + licenses { + license { + name.set("SSALv1") + url.set("https://sonarsource.com/license/ssal/") + distribution.set("repo") + } + } + scm { + url.set("https://github.com/SonarSource/sonarqube-mcp-server") + } + developers { + developer { + id.set("sonarsource-team") + name.set("SonarSource Team") + } + } + } + } + } } sonar { - properties { - property("sonar.organization", "sonarsource") - property("sonar.projectKey", "SonarSource_sonar-mcp-server") - property("sonar.projectName", "SonarQube MCP Server") - property("sonar.links.ci", "https://cirrus-ci.com/github/SonarSource/sonarqube-mcp-server") - property("sonar.links.scm", "https://github.com/SonarSource/sonarqube-mcp-server") - property("sonar.links.issue", "https://jira.sonarsource.com/browse/MCP") - property("sonar.exclusions", "**/build/**/*") - } + properties { + property("sonar.organization", "sonarsource") + property("sonar.projectKey", "SonarSource_sonar-mcp-server") + property("sonar.projectName", "SonarQube MCP Server") + property("sonar.links.ci", "https://github.com/SonarSource/sonarqube-mcp-server/actions") + property("sonar.links.scm", "https://github.com/SonarSource/sonarqube-mcp-server") + property("sonar.links.issue", "https://jira.sonarsource.com/browse/MCP") + property("sonar.exclusions", "**/build/**/*") + } } diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..c98ab38 --- /dev/null +++ b/mise.toml @@ -0,0 +1,3 @@ +[tools] +java = "21.0" +gradle = "8.13" diff --git a/src/main/java/org/sonarsource/sonarqube/mcp/tools/analysis/ANewClass.java b/src/main/java/org/sonarsource/sonarqube/mcp/tools/analysis/ANewClass.java new file mode 100644 index 0000000..9e078b5 --- /dev/null +++ b/src/main/java/org/sonarsource/sonarqube/mcp/tools/analysis/ANewClass.java @@ -0,0 +1,24 @@ +/* + * SonarQube MCP Server + * Copyright (C) 2025 SonarSource + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonarsource.sonarqube.mcp.tools.analysis; + +class ANewClass { + public String returnAString() { + // TODO: This should be detected by SQC + return "Hello, World!"; + } +} \ No newline at end of file diff --git a/src/test/java/org/sonarsource/sonarqube/mcp/tools/analysis/ANewClassTests.java b/src/test/java/org/sonarsource/sonarqube/mcp/tools/analysis/ANewClassTests.java new file mode 100644 index 0000000..23ec0dc --- /dev/null +++ b/src/test/java/org/sonarsource/sonarqube/mcp/tools/analysis/ANewClassTests.java @@ -0,0 +1,29 @@ +/* + * SonarQube MCP Server + * Copyright (C) 2025 SonarSource + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonarsource.sonarqube.mcp.tools.analysis; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ANewClassTests { + @Test + public void testReturnAString() { + ANewClass aNewClass = new ANewClass(); + assertEquals("Hello, World!", aNewClass.returnAString()); + } +}